summaryrefslogtreecommitdiffstats
path: root/daemon
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:55:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:55:53 +0000
commit3d0386f27ca66379acf50199e1d1298386eeeeb8 (patch)
treef87bd4a126b3a843858eb447e8fd5893c3ee3882 /daemon
parentInitial commit. (diff)
downloadknot-resolver-3d0386f27ca66379acf50199e1d1298386eeeeb8.tar.xz
knot-resolver-3d0386f27ca66379acf50199e1d1298386eeeeb8.zip
Adding upstream version 3.2.1.upstream/3.2.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'daemon')
-rw-r--r--daemon/README.rst1394
-rw-r--r--daemon/bindings.c1849
-rw-r--r--daemon/bindings.h88
-rw-r--r--daemon/cache.test/clear.test.lua211
-rw-r--r--daemon/cache.test/testroot.zone1256
-rw-r--r--daemon/cache.test/testroot.zone.unsigned215
-rw-r--r--daemon/daemon.mk79
-rw-r--r--daemon/engine.c1055
-rw-r--r--daemon/engine.h89
-rw-r--r--daemon/ffimodule.c289
-rw-r--r--daemon/ffimodule.h33
-rw-r--r--daemon/io.c494
-rw-r--r--daemon/io.h46
-rw-r--r--daemon/lua/config.lua35
-rw-r--r--daemon/lua/kres-gen.lua430
-rwxr-xr-xdaemon/lua/kres-gen.sh216
-rw-r--r--daemon/lua/kres.lua952
-rw-r--r--daemon/lua/sandbox.lua458
-rw-r--r--daemon/lua/trust_anchors.lua.in663
-rw-r--r--daemon/lua/trust_anchors.test/bootstrap.test.lua108
-rw-r--r--daemon/lua/trust_anchors.test/err_attr_extra_attr.xml16
-rw-r--r--daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml16
-rw-r--r--daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml16
-rw-r--r--daemon/lua/trust_anchors.test/err_elem_extra.xml17
-rw-r--r--daemon/lua/trust_anchors.test/err_elem_missing.xml16
-rw-r--r--daemon/lua/trust_anchors.test/err_multi_ta.xml19
-rw-r--r--daemon/lua/trust_anchors.test/ok0_badtimes.xml16
-rw-r--r--daemon/lua/trust_anchors.test/ok1.xml10
-rw-r--r--daemon/lua/trust_anchors.test/ok1_expired1.xml16
-rw-r--r--daemon/lua/trust_anchors.test/ok1_notyet1.xml16
-rw-r--r--daemon/lua/trust_anchors.test/ok2.xml16
-rwxr-xr-xdaemon/lua/trust_anchors.test/regen.sh2
-rw-r--r--daemon/lua/trust_anchors.test/unsupp_nonroot.xml10
-rw-r--r--daemon/lua/trust_anchors.test/unsupp_xml_v11.xml10
-rw-r--r--daemon/lua/trust_anchors.test/webserv.lua234
-rw-r--r--daemon/lua/trust_anchors.test/x509/ca-key.pem182
-rw-r--r--daemon/lua/trust_anchors.test/x509/ca.pem24
-rw-r--r--daemon/lua/trust_anchors.test/x509/ca.tmpl3
-rwxr-xr-xdaemon/lua/trust_anchors.test/x509/gen.sh11
-rw-r--r--daemon/lua/trust_anchors.test/x509/server-key.pem182
-rw-r--r--daemon/lua/trust_anchors.test/x509/server.pem26
-rw-r--r--daemon/lua/trust_anchors.test/x509/server.tmpl6
-rw-r--r--daemon/lua/trust_anchors.test/x509/wrongca-key.pem182
-rw-r--r--daemon/lua/trust_anchors.test/x509/wrongca.pem24
-rw-r--r--daemon/lua/trust_anchors.test/x509/wrongca.tmpl3
-rw-r--r--daemon/lua/zonefile.lua91
-rw-r--r--daemon/main.c840
-rw-r--r--daemon/network.c446
-rw-r--r--daemon/network.h70
-rw-r--r--daemon/session.c767
-rw-r--r--daemon/session.h147
-rw-r--r--daemon/tls.c1282
-rw-r--r--daemon/tls.h224
-rw-r--r--daemon/tls_ephemeral_credentials.c249
-rw-r--r--daemon/tls_session_ticket-srv.c262
-rw-r--r--daemon/worker.c2010
-rw-r--r--daemon/worker.h171
-rw-r--r--daemon/zimport.c818
-rw-r--r--daemon/zimport.h68
59 files changed, 18478 insertions, 0 deletions
diff --git a/daemon/README.rst b/daemon/README.rst
new file mode 100644
index 0000000..51f95fe
--- /dev/null
+++ b/daemon/README.rst
@@ -0,0 +1,1394 @@
+************************
+Knot Resolver daemon
+************************
+
+The server is in the `daemon` directory, it works out of the box without any configuration.
+
+.. code-block:: bash
+
+ $ kresd -h # Get help
+ $ kresd -a ::1
+
+If you're using our packages, they also provide systemd integration. To start the resolver under systemd, you can use the ``kresd@1.service`` service. By default, the resolver only binds to local interfaces.
+
+.. code-block:: bash
+
+ $ man kresd.systemd # Help for systemd integration configuration
+ $ systemctl start kresd@1.service
+
+
+Configuration
+=============
+
+.. contents::
+ :depth: 2
+ :local:
+
+In its simplest form the server requires just a working directory in which it can set up persistent files like
+cache and the process state. If you don't provide the working directory by parameter, it is going to make itself
+comfortable in the current working directory.
+
+.. code-block:: sh
+
+ $ kresd /var/cache/knot-resolver
+
+And you're good to go for most use cases! If you want to use modules or configure daemon behavior, read on.
+
+There are several choices on how you can configure the daemon, a RPC interface, a CLI, and a configuration file.
+Fortunately all share common syntax and are transparent to each other.
+
+Configuration example
+---------------------
+.. code-block:: lua
+
+ -- interfaces
+ net = { '127.0.0.1', '::1' }
+ -- load some modules
+ modules = { 'policy' }
+ -- 10MB cache
+ cache.size = 10*MB
+
+.. tip:: There are more configuration examples in `etc/` directory for personal, ISP, company internal and resolver cluster use cases.
+
+Configuration syntax
+--------------------
+
+The configuration is kept in the ``config`` file in the daemon working directory, and it's going to get loaded automatically.
+If there isn't one, the daemon is going to start with sane defaults, listening on `localhost`.
+The syntax for options is like follows: ``group.option = value`` or ``group.action(parameters)``.
+You can also comment using a ``--`` prefix.
+
+A simple example would be to load static hints.
+
+.. code-block:: lua
+
+ modules = {
+ 'hints' -- no configuration
+ }
+
+If the module accepts configuration, you can call the ``module.config({...})`` or provide options table.
+The syntax for table is ``{ key1 = value, key2 = value }``, and it represents the unpacked `JSON-encoded`_ string, that
+the modules use as the :ref:`input configuration <mod-properties>`.
+
+.. code-block:: lua
+
+ modules = {
+ hints = '/etc/hosts'
+ }
+
+.. warning:: Modules specified including their configuration may not load exactly in the same order as specified.
+
+Modules are inherently ordered by their declaration. Some modules are built-in, so it would be normally impossible to place for example *hints* before *cache*. You can enforce specific order by precedence operators **>** and **<**.
+
+.. code-block:: lua
+
+ modules = {
+ 'hints > iterate', -- Hints AFTER iterate
+ 'policy > hints', -- Policy AFTER hints
+ 'view < cache' -- View BEFORE cache
+ }
+ modules.list() -- Check module call order
+
+This is useful if you're writing a module with a layer, that evaluates an answer before writing it into cache for example.
+
+.. tip:: The configuration and CLI syntax is Lua language, with which you may already be familiar with.
+ If not, you can read the `Learn Lua in 15 minutes`_ for a syntax overview. Spending just a few minutes
+ will allow you to break from static configuration, write more efficient configuration with iteration, and
+ leverage events and hooks. Lua is heavily used for scripting in applications ranging from embedded to game engines,
+ but in DNS world notably in `PowerDNS Recursor`_. Knot Resolver does not simply use Lua modules, but it is
+ the heart of the daemon for everything from configuration, internal events and user interaction.
+
+Dynamic configuration
+^^^^^^^^^^^^^^^^^^^^^
+
+Knowing that the the configuration is a Lua in disguise enables you to write dynamic rules. It also helps you to avoid repetitive templating that is unavoidable with static configuration.
+
+.. code-block:: lua
+
+ if hostname() == 'hidden' then
+ net.listen(net.eth0, 5353)
+ else
+ net = { '127.0.0.1', net.eth1.addr[1] }
+ end
+
+Another example would show how it is possible to bind to all interfaces, using iteration.
+
+.. code-block:: lua
+
+ for name, addr_list in pairs(net.interfaces()) do
+ net.listen(addr_list)
+ end
+
+.. tip:: Some users observed a considerable, close to 100%, performance gain in Docker containers when they bound the daemon to a single interface:ip address pair. One may expand the aforementioned example with browsing available addresses as:
+
+ .. code-block:: lua
+
+ addrpref = env.EXPECTED_ADDR_PREFIX
+ for k, v in pairs(addr_list["addr"]) do
+ if string.sub(v,1,string.len(addrpref)) == addrpref then
+ net.listen(v)
+ ...
+
+You can also use third-party packages (available for example through LuaRocks_) as on this example
+to download cache from parent, to avoid cold-cache start.
+
+.. code-block:: lua
+
+ local http = require('socket.http')
+ local ltn12 = require('ltn12')
+
+ local cache_size = 100*MB
+ local cache_path = '/var/cache/knot-resolver'
+ cache.open(cache_size, 'lmdb://' .. cache_path)
+ if cache.count() == 0 then
+ cache.close()
+ -- download cache from parent
+ http.request {
+ url = 'http://parent/data.mdb',
+ sink = ltn12.sink.file(io.open(cache_path .. '/data.mdb', 'w'))
+ }
+ -- reopen cache with 100M limit
+ cache.open(cache_size, 'lmdb://' .. cache_path)
+ end
+
+Asynchronous events
+^^^^^^^^^^^^^^^^^^^
+
+Lua supports a concept called closures_, this is extremely useful for scripting actions upon various events,
+say for example - publish statistics each minute and so on.
+Here's an example of an anonymous function with :func:`event.recurrent()`.
+
+Note that each scheduled event is identified by a number valid for the duration of the event,
+you may use it to cancel the event at any time.
+
+.. code-block:: lua
+
+ modules.load('stats')
+
+ -- log statistics every second
+ local stat_id = event.recurrent(1 * second, function(evid)
+ log(table_print(stats.list()))
+ end)
+
+ -- stop printing statistics after first minute
+ event.after(1 * minute, function(evid)
+ event.cancel(stat_id)
+ end)
+
+If you need to persist state between events, encapsulate even handle in closure function which will provide persistent variable (called ``previous``):
+
+.. code-block:: lua
+
+ modules.load('stats')
+
+ -- make a closure, encapsulating counter
+ function speed_monitor()
+ local previous = stats.list()
+ -- monitoring function
+ return function(evid)
+ local now = stats.list()
+ local total_increment = now['answer.total'] - previous['answer.total']
+ local slow_increment = now['answer.slow'] - previous['answer.slow']
+ if slow_increment / total_increment > 0.05 then
+ log('WARNING! More than 5 %% of queries was slow!')
+ end
+ previous = now -- store current value in closure
+ end
+ end
+
+ -- monitor every minute
+ local monitor_id = event.recurrent(1 * minute, speed_monitor())
+
+Another type of actionable event is activity on a file descriptor. This allows you to embed other
+event loops or monitor open files and then fire a callback when an activity is detected.
+This allows you to build persistent services like HTTP servers or monitoring probes that cooperate
+well with the daemon internal operations. See :func:`event.socket()`
+
+
+File watchers are possible with :func:`worker.coroutine()` and cqueues_, see the cqueues documentation for more information.
+
+.. code-block:: lua
+
+ local notify = require('cqueues.notify')
+ local watcher = notify.opendir('/etc')
+ watcher:add('hosts')
+
+ -- Watch changes to /etc/hosts
+ worker.coroutine(function ()
+ for flags, name in watcher:changes() do
+ for flag in notify.flags(flags) do
+ print(name, notify[flag])
+ end
+ end
+ end)
+
+.. _closures: https://www.lua.org/pil/6.1.html
+
+Configuration reference
+-----------------------
+
+This is a reference for variables and functions available to both configuration file and CLI.
+
+.. contents::
+ :depth: 1
+ :local:
+
+Environment
+^^^^^^^^^^^
+
+.. envvar:: env (table)
+
+ Return environment variable.
+
+ .. code-block:: lua
+
+ env.USER -- equivalent to $USER in shell
+
+.. function:: hostname([fqdn])
+
+ :return: Machine hostname.
+
+ If called with a parameter, it will set kresd's internal
+ hostname. If called without a parameter, it will return kresd's
+ internal hostname, or the system's POSIX hostname (see
+ gethostname(2)) if kresd's internal hostname is unset.
+
+ This affects ephemeral certificates for kresd serving DNS over TLS.
+
+.. function:: moduledir([dir])
+
+ :return: Modules directory.
+
+ If called with a parameter, it will change kresd's directory for
+ looking up the dynamic modules. If called without a parameter, it
+ will return kresd's modules directory.
+
+.. function:: verbose(true | false)
+
+ :return: Toggle verbose logging.
+
+.. function:: mode('strict' | 'normal' | 'permissive')
+
+ :return: 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.
+
+.. function:: reorder_RR([true | false])
+
+ :param boolean value: New value for the option *(optional)*
+ :return: The (new) value of the option
+
+ If set, resolver will vary the order of resource records within RR-sets.
+ It is disabled by default.
+
+.. function:: user(name, [group])
+
+ :param string name: user name
+ :param string group: group name (optional)
+ :return: boolean
+
+ Drop privileges and run as given user (and group, if provided).
+
+ .. tip:: Note that you should bind to required network addresses before changing user. At the same time, you should open the cache **AFTER** you change the user (so it remains accessible). A good practice is to divide configuration in two parts:
+
+ .. code-block:: lua
+
+ -- privileged
+ net = { '127.0.0.1', '::1' }
+ -- unprivileged
+ cache.size = 100*MB
+ trust_anchors.file = 'root.key'
+
+ Example output:
+
+ .. code-block:: lua
+
+ > user('baduser')
+ invalid user name
+ > user('knot-resolver', 'netgrp')
+ true
+ > user('root')
+ Operation not permitted
+
+.. function:: resolve(name, type[, class = kres.class.IN, options = {}, finish = nil, init = nil])
+
+ :param string name: Query name (e.g. 'com.')
+ :param number type: Query type (e.g. ``kres.type.NS``)
+ :param number class: Query class *(optional)* (e.g. ``kres.class.IN``)
+ :param strings options: Resolution options (see :c:type:`kr_qflags`)
+ :param function finish: Callback to be executed when resolution completes (e.g. `function cb (pkt, req) end`). The callback gets a packet containing the final answer and doesn't have to return anything.
+ :param function init: Callback to be executed with the :c:type:`kr_request` before resolution starts.
+ :return: boolean
+
+ The function can also be executed with a table of arguments instead. This is useful if you'd like to skip some arguments, for example:
+
+ .. code-block:: lua
+
+ resolve {
+ name = 'example.com',
+ type = kres.type.AAAA,
+ init = function (req)
+ end,
+ }
+
+ Example:
+
+ .. code-block:: lua
+
+ -- Send query for root DNSKEY, ignore cache
+ resolve('.', kres.type.DNSKEY, kres.class.IN, 'NO_CACHE')
+
+ -- Query for AAAA record
+ resolve('example.com', kres.type.AAAA, kres.class.IN, 0,
+ function (answer, req)
+ -- Check answer RCODE
+ local pkt = kres.pkt_t(answer)
+ if pkt:rcode() == kres.rcode.NOERROR then
+ -- Print matching records
+ local records = pkt:section(kres.section.ANSWER)
+ for i = 1, #records do
+ local rr = records[i]
+ if rr.type == kres.type.AAAA then
+ print ('record:', kres.rr2str(rr))
+ end
+ end
+ else
+ print ('rcode: ', pkt:rcode())
+ end
+ end)
+
+.. function:: package_version()
+
+ :return: Current package version.
+
+ This returns current package version (the version of the binary) as a string.
+
+ .. code-block:: lua
+
+ > package_version()
+ 2.1.1
+
+
+.. _network-configuration:
+
+Network configuration
+^^^^^^^^^^^^^^^^^^^^^
+
+For when listening on ``localhost`` just doesn't cut it.
+
+**Systemd socket configuration**
+
+If you're using our packages with systemd with sockets support (not supported
+on CentOS 7), network interfaces are configured using systemd drop-in files for
+``kresd.socket`` and ``kresd-tls.socket``.
+
+To configure kresd to listen on public interface, create a drop-in file:
+
+.. code-block:: bash
+
+ $ systemctl edit kresd.socket
+
+.. code-block:: none
+
+ # /etc/systemd/system/kresd.socket.d/override.conf
+ [Socket]
+ ListenDatagram=192.0.2.115:53
+ ListenStream=192.0.2.115:53
+
+.. _kresd-socket-override-port:
+
+The default port can also be overriden by using an empty ``ListenDatagram=`` or ``ListenStream=`` directive. This can be useful if you want to use the Knot DNS with the `dnsproxy module`_ to have both resolver and authoritative server running on the same machine.
+
+.. code-block:: none
+
+ # /etc/systemd/system/kresd.socket.d/override.conf
+ [Socket]
+ ListenDatagram=
+ ListenStream=
+ ListenDatagram=127.0.0.1:53000
+ ListenStream=127.0.0.1:53000
+ ListenDatagram=[::1]:53000
+ ListenStream=[::1]:53000
+
+The ``kresd-tls.socket`` can also be configured to listen for TLS connections.
+
+.. code-block:: bash
+
+ $ systemctl edit kresd-tls.socket
+
+.. code-block:: none
+
+ # /etc/systemd/system/kresd-tls.socket.d/override.conf
+ [Socket]
+ ListenStream=192.0.2.115:853
+
+**Daemon network configuration**
+
+If you don't use systemd with sockets to run kresd, network interfaces are
+configured in the config file.
+
+.. tip:: Use declarative interface for network.
+
+ .. code-block:: lua
+
+ net = { '127.0.0.1', net.eth0, net.eth1.addr[1] }
+ net.ipv4 = false
+
+.. warning:: On machines with multiple IP addresses avoid binding to wildcard ``0.0.0.0`` or ``::`` (see example below). Knot Resolver could answer from different IP in case the ranges overlap and client will probably refuse such a response.
+
+ .. code-block:: lua
+
+ net = { '0.0.0.0' }
+
+
+.. envvar:: net.ipv6 = true|false
+
+ :return: boolean (default: true)
+
+ Enable/disable using IPv6 for contacting upstream nameservers.
+
+.. envvar:: net.ipv4 = true|false
+
+ :return: boolean (default: true)
+
+ Enable/disable using IPv4 for contacting upstream nameservers.
+
+.. function:: net.listen(addresses, [port = 53, flags = {tls = (port == 853)}])
+
+ :return: boolean
+
+ Listen on addresses; port and flags are optional.
+ The addresses can be specified as a string or device,
+ or a list of addresses (recursively).
+ The command can be given multiple times, but note that it silently skips
+ any addresses that have already been bound.
+
+ Examples:
+
+ .. code-block:: lua
+
+ net.listen('::1')
+ net.listen(net.lo, 5353)
+ net.listen({net.eth0, '127.0.0.1'}, 53853, {tls = true})
+
+.. function:: net.close(address, [port = 53])
+
+ :return: boolean
+
+ Close opened address/port pair, noop if not listening.
+
+.. function:: net.list()
+
+ :return: Table of bound interfaces.
+
+ Example output:
+
+ .. code-block:: none
+
+ [127.0.0.1] => {
+ [port] => 53
+ [tcp] => true
+ [udp] => true
+ }
+
+.. function:: net.interfaces()
+
+ :return: Table of available interfaces and their addresses.
+
+ Example output:
+
+ .. code-block:: none
+
+ [lo0] => {
+ [addr] => {
+ [1] => ::1
+ [2] => 127.0.0.1
+ }
+ [mac] => 00:00:00:00:00:00
+ }
+ [eth0] => {
+ [addr] => {
+ [1] => 192.168.0.1
+ }
+ [mac] => de:ad:be:ef:aa:bb
+ }
+
+ .. tip:: You can use ``net.<iface>`` as a shortcut for specific interface, e.g. ``net.eth0``
+
+.. function:: net.bufsize([udp_bufsize])
+
+ Get/set maximum EDNS payload available. Default is 4096.
+ You cannot set less than 512 (512 is DNS packet size without EDNS, 1220 is minimum size for DNSSEC) or more than 65535 octets.
+
+ Example output:
+
+ .. code-block:: lua
+
+ > net.bufsize 4096
+ > net.bufsize()
+ 4096
+
+.. function:: net.tcp_pipeline([len])
+
+ Get/set per-client TCP pipeline limit, i.e. the number of outstanding queries that a single client connection can make in parallel. Default is 100.
+
+ .. code-block:: lua
+
+ > net.tcp_pipeline()
+ 100
+ > net.tcp_pipeline(50)
+ 50
+
+ .. warning:: Please note that too large limit may have negative impact on performance and can lead to increased number of SERVFAIL answers.
+
+.. function:: net.outgoing_v4([string address])
+
+ Get/set the IPv4 address used to perform queries. There is also ``net.outgoing_v6`` for IPv6.
+ The default is ``nil``, which lets the OS choose any address.
+
+
+.. _tls-server-config:
+
+TLS server configuration
+^^^^^^^^^^^^^^^^^^^^^^^^
+.. note:: Installations using systemd should be configured using systemd-specific procedures
+ described in manual page ``kresd.systemd(7)``.
+
+DNS-over-TLS server (:rfc:`7858`) can be enabled using ``{tls = true}`` parameter
+in :c:func:`net.listen()` function call. For example:
+
+.. code-block:: lua
+
+ > net.listen("::", 53) -- plain UDP+TCP on port 53 (standard DNS)
+ > net.listen("::", 853, {tls = true}) -- DNS-over-TLS on port 853 (standard DoT)
+ > net.listen("::", 443, {tls = true}) -- DNS-over-TLS on port 443 (non-standard)
+
+By default an self-signed certificate will be generated. For serious deployments
+it is strongly recommended to provide TLS certificates signed by a trusted CA
+using :c:func:`net.tls()`.
+
+.. function:: net.tls([cert_path], [key_path])
+
+ Get/set path to a server TLS certificate and private key for DNS/TLS.
+
+ Example output:
+
+ .. code-block:: lua
+
+ > net.tls("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem")
+ > net.tls() -- print configured paths
+ ("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem")
+
+.. function:: net.tls_padding([true | false])
+
+ Get/set EDNS(0) padding of answers to queries that arrive over TLS
+ transport. If set to `true` (the default), it will use a sensible
+ default padding scheme, as implemented by libknot if available at
+ compile time. If set to a numeric value >= 2 it will pad the
+ answers to nearest *padding* boundary, e.g. if set to `64`, the
+ answer will have size of a multiple of 64 (64, 128, 192, ...). If
+ set to `false` (or a number < 2), it will disable padding entirely.
+
+.. function:: net.tls_sticket_secret([string with pre-shared secret])
+
+ Set secret for TLS session resumption via tickets, by :rfc:`5077`.
+
+ The server-side key is rotated roughly once per hour.
+ By default or if called without secret, the key is random.
+ That is good for long-term forward secrecy, but multiple kresd instances
+ won't be able to resume each other's sessions.
+
+ If you provide the same secret to multiple instances, they will be able to resume
+ each other's sessions *without* any further communication between them.
+ This synchronization works only among instances having the same endianess
+ and time_t structure and size (`sizeof(time_t)`).
+
+ **For good security** the secret must have enough entropy to be hard to guess,
+ and it should still be occasionally rotated manually and securely forgotten,
+ to reduce the scope of privacy leak in case the
+ `secret leaks eventually <https://en.wikipedia.org/wiki/Forward_secrecy>`_.
+
+ .. warning:: **Setting the secret is probably too risky with TLS <= 1.2**.
+ GnuTLS stable release supports TLS 1.3 since 3.6.3 (summer 2018).
+ Therefore setting the secrets should be considered experimental for now
+ and might not be available on your system.
+
+.. function:: net.tls_sticket_secret_file([string with path to a file containing pre-shared secret])
+
+ The same as :func:`net.tls_sticket_secret`,
+ except the secret is read from a (binary) file.
+
+.. _dnssec-config:
+
+Trust anchors and DNSSEC
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. function:: trust_anchors.config(keyfile, readonly)
+
+ Alias for `add_file`. It is also equivalent to CLI parameter ``-k <keyfile>``
+ and ``trust_anchors.file = keyfile``.
+
+.. function:: trust_anchors.add_file(keyfile, readonly)
+
+ :param string keyfile: path to the file.
+ :param readonly: if true, do not attempt to update the file.
+
+ The format is standard zone file, though additional information may be persisted in comments.
+ Either DS or DNSKEY records can be used for TAs.
+ If the file does not exist, bootstrapping of *root* TA will be attempted.
+
+ Each file can only contain records for a single domain.
+ The TAs will be updated according to :rfc:`5011` and persisted in the file (if allowed).
+
+ Example output:
+
+ .. code-block:: lua
+
+ > trust_anchors.add_file('root.key')
+ [ ta ] new state of trust anchors for a domain:
+ . 165488 DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
+ nil
+
+ [ ta ] key: 19036 state: Valid
+
+.. envvar:: trust_anchors.keyfile_default = KEYFILE_DEFAULT
+
+ Set by ``KEYFILE_DEFAULT`` during compilation (by default ``nil``). This can be explicitly
+ set to ``nil`` to override the value set during compilation in order to disable DNSSEC.
+
+.. envvar:: trust_anchors.hold_down_time = 30 * day
+
+ :return: int (default: 30 * day)
+
+ Modify RFC5011 hold-down timer to given value. Example: ``30 * sec``
+
+.. envvar:: trust_anchors.refresh_time = nil
+
+ :return: int (default: nil)
+
+ Modify RFC5011 refresh timer to given value (not set by default), this will force trust anchors
+ to be updated every N seconds periodically instead of relying on RFC5011 logic and TTLs.
+ Example: ``10 * sec``
+
+.. envvar:: trust_anchors.keep_removed = 0
+
+ :return: int (default: 0)
+
+ How many ``Removed`` keys should be held in history (and key file) before being purged.
+ Note: all ``Removed`` keys will be purged from key file after restarting the process.
+
+
+.. function:: trust_anchors.set_insecure(nta_set)
+
+ :param table nta_list: List of domain names (text format) representing NTAs.
+
+ When you use a domain name as an NTA, DNSSEC validation will be turned off at/below these names.
+ Each function call replaces the previous NTA set. You can find the current active set in ``trust_anchors.insecure`` variable.
+
+ .. tip:: Use the `trust_anchors.negative = {}` alias for easier configuration.
+
+ Example output:
+
+ .. code-block:: lua
+
+ > trust_anchors.negative = { 'bad.boy', 'example.com' }
+ > trust_anchors.insecure
+ [1] => bad.boy
+ [2] => example.com
+
+ .. warning:: If you set NTA on a name that is not a zone cut,
+ it may not always affect names not separated from the NTA by a zone cut.
+
+.. function:: trust_anchors.add(rr_string)
+
+ :param string rr_string: DS/DNSKEY records in presentation format (e.g. ``. 3600 IN DS 19036 8 2 49AAC11...``)
+
+ Inserts DS/DNSKEY record(s) into current keyset. These will not be managed or updated, use it only for testing
+ or if you have a specific use case for not using a keyfile.
+
+ Example output:
+
+ .. code-block:: lua
+
+ > trust_anchors.add('. 3600 IN DS 19036 8 2 49AAC11...')
+
+.. function:: trust_anchors.summary()
+
+ Return string with summary of configured DNSSEC trust anchors, including negative TAs.
+
+Modules configuration
+^^^^^^^^^^^^^^^^^^^^^
+
+The daemon provides an interface for dynamic loading of :ref:`daemon modules <modules-implemented>`.
+
+.. tip:: Use declarative interface for module loading.
+
+ .. code-block:: lua
+
+ modules = {
+ hints = {file = '/etc/hosts'}
+ }
+
+ Equals to:
+
+ .. code-block:: lua
+
+ modules.load('hints')
+ hints.config({file = '/etc/hosts'})
+
+
+.. function:: modules.list()
+
+ :return: List of loaded modules.
+
+.. function:: modules.load(name)
+
+ :param string name: Module name, e.g. "hints"
+ :return: boolean
+
+ Load a module by name.
+
+.. function:: modules.unload(name)
+
+ :param string name: Module name
+ :return: boolean
+
+ Unload a module by name.
+
+Cache configuration
+^^^^^^^^^^^^^^^^^^^
+
+The default cache in Knot Resolver is persistent with LMDB backend, this means that the daemon doesn't lose
+the cached data on restart or crash to avoid cold-starts. The cache may be reused between cache
+daemons or manipulated from other processes, making for example synchronized load-balanced recursors possible.
+
+.. function:: cache.open(max_size[, config_uri])
+
+ :param number max_size: Maximum cache size in bytes.
+ :return: ``true`` if cache was opened
+
+ Open cache with a size limit. The cache will be reopened if already open.
+ Note that the max_size cannot be lowered, only increased due to how cache is implemented.
+
+ .. tip:: Use ``kB, MB, GB`` constants as a multiplier, e.g. ``100*MB``.
+
+ As of now, the built-in backend with URI ``lmdb://`` allows you to change the cache directory.
+
+ Example:
+
+ .. code-block:: lua
+
+ cache.open(100 * MB, 'lmdb:///var/cache/knot-resolver')
+
+.. envvar:: cache.size
+
+ Set the cache maximum size in bytes. Note that this is only a hint to the backend,
+ which may or may not respect it. See :func:`cache.open()`.
+
+ .. code-block:: lua
+
+ cache.size = 100 * MB -- equivalent to `cache.open(100 * MB)`
+
+.. envvar:: cache.current_size
+
+ Get the maximum size in bytes.
+
+ .. code-block:: lua
+
+ print(cache.current_size)
+
+.. envvar:: cache.storage
+
+ Set the cache storage backend configuration, see :func:`cache.backends()` for
+ more information. If the new storage configuration is invalid, it is not set.
+
+ .. code-block:: lua
+
+ cache.storage = 'lmdb://.'
+
+.. envvar:: cache.current_storage
+
+ Get the storage backend configuration.
+
+ .. code-block:: lua
+
+ print(cache.storage)
+
+.. function:: cache.backends()
+
+ :return: map of backends
+
+ The cache supports runtime-changeable backends, using the optional :rfc:`3986` URI, where the scheme
+ represents backend protocol and the rest of the URI backend-specific configuration. By default, it
+ is a ``lmdb`` backend in working directory, i.e. ``lmdb://``.
+
+ Example output:
+
+ .. code-block:: lua
+
+ [lmdb://] => true
+
+.. function:: cache.count()
+
+ :return: Number of entries in the cache. Meaning of the number is an implementation detail and is subject of change.
+
+.. function:: cache.close()
+
+ :return: ``true`` if cache was closed
+
+ Close the cache.
+
+ .. note:: This may or may not clear the cache, depending on the cache backend.
+
+.. function:: cache.stats()
+
+ .. warning:: Cache statistics are being reworked. Do not rely on current behavior.
+
+ Return table of statistics, note that this tracks all operations over cache, not just which
+ queries were answered from cache or not.
+
+ Example:
+
+ .. code-block:: lua
+
+ print('Insertions:', cache.stats().insert)
+
+.. function:: cache.max_ttl([ttl])
+
+ :param number ttl: maximum cache TTL in seconds (default: 6 days)
+
+ .. KR_CACHE_DEFAULT_TTL_MAX ^^
+
+ :return: current maximum TTL
+
+ Get or set maximum cache TTL.
+
+ .. note:: The `ttl` value must be in range `(min_ttl, 4294967295)`.
+
+ .. warning:: This settings applies only to currently open cache, it will not persist if the cache is closed or reopened.
+
+ .. code-block:: lua
+
+ -- Get maximum TTL
+ cache.max_ttl()
+ 518400
+ -- Set maximum TTL
+ cache.max_ttl(172800)
+ 172800
+
+.. function:: cache.min_ttl([ttl])
+
+ :param number ttl: minimum cache TTL in seconds (default: 5 seconds)
+
+ .. KR_CACHE_DEFAULT_TTL_MIN ^^
+
+ :return: current maximum TTL
+
+ Get or set minimum cache TTL. Any entry inserted into cache with TTL lower than minimal will be overridden to minimum TTL. Forcing TTL higher than specified violates DNS standards, use with care.
+
+ .. note:: The `ttl` value must be in range `<0, max_ttl)`.
+
+ .. warning:: This settings applies only to currently open cache, it will not persist if the cache is closed or reopened.
+
+ .. code-block:: lua
+
+ -- Get minimum TTL
+ cache.min_ttl()
+ 0
+ -- Set minimum TTL
+ cache.min_ttl(5)
+ 5
+
+.. function:: cache.ns_tout([timeout])
+
+ :param number timeout: NS retry interval in milliseconds (default: :c:macro:`KR_NS_TIMEOUT_RETRY_INTERVAL`)
+ :return: current timeout
+
+ Get or set time interval for which a nameserver address will be ignored after determining that it doesn't return (useful) answers.
+ The intention is to avoid waiting if there's little hope; instead, kresd can immediately SERVFAIL or immediately use stale records (with :ref:`serve_stale <mod-serve_stale>` module).
+
+ .. warning:: This settings applies only to the current kresd process.
+
+.. function:: cache.get([domain])
+
+ This function is not implemented at this moment.
+ We plan to re-introduce it soon, probably with a slightly different API.
+
+.. function:: cache.clear([name], [exact_name], [rr_type], [chunk_size], [callback], [prev_state])
+
+ Purge cache records matching specified criteria. There are two specifics:
+
+ * To reliably remove **negative** cache entries you need to clear subtree with the whole zone. E.g. to clear negative cache entries for (formerly non-existing) record `www.example.com. A` you need to flush whole subtree starting at zone apex, e.g. `example.com.` [#]_.
+ * This operation is asynchronous and might not be yet finished when call to ``cache.clear()`` function returns. Return value indicates if clearing continues asynchronously or not.
+
+ :param string name: subtree to purge; if the name isn't provided, whole cache is purged
+ (and any other parameters are disregarded).
+ :param bool exact_name: if set to ``true``, only records with *the same* name are removed;
+ default: false.
+ :param kres.type rr_type: you may additionally specify the type to remove,
+ but that is only supported with ``exact_name == true``; default: nil.
+ :param integer chunk_size: the number of records to remove in one round; default: 100.
+ The purpose is not to block the resolver for long.
+ The default ``callback`` repeats the command after one millisecond
+ until all matching data are cleared.
+ :param function callback: a custom code to handle result of the underlying C call.
+ Its parameters are copies of those passed to `cache.clear()` with one additional
+ parameter ``rettable`` containing table with return value from current call.
+ ``count`` field contains a return code from :func:`kr_cache_remove_subtree()`.
+ :param table prev_state: return value from previous run (can be used by callback)
+
+ :rtype: table
+ :return: ``count`` key is always present. Other keys are optional and their presence indicate special conditions.
+
+ * **count** *(integer)* - number of items removed from cache by this call (can be 0 if no entry matched criteria)
+ * **not_apex** - cleared subtree is not cached as zone apex; proofs of non-existence were probably not removed
+ * **subtree** *(string)* - hint where zone apex lies (this is estimation from cache content and might not be accurate)
+ * **chunk_limit** - more than ``chunk_size`` items needs to be cleared, clearing will continue asynchronously
+
+
+ Examples:
+
+ .. code-block:: lua
+
+ -- Clear whole cache
+ > cache.clear()
+ [count] => 76
+
+ -- Clear records at and below 'com.'
+ > cache.clear('com.')
+ [chunk_limit] => chunk size limit reached; the default callback will continue asynchronously
+ [not_apex] => to clear proofs of non-existence call cache.clear('com.')
+ [count] => 100
+ [round] => 1
+ [subtree] => com.
+ > worker.sleep(0.1)
+ [cache] asynchonous cache.clear('com', false) finished
+
+ -- Clear only 'www.example.com.'
+ > cache.clear('www.example.com.', true)
+ [round] => 1
+ [count] => 1
+ [not_apex] => to clear proofs of non-existence call cache.clear('example.com.')
+ [subtree] => example.com.
+
+.. [#] This is a consequence of DNSSEC negative cache which relies on proofs of non-existence on various owner nodes. It is impossible to efficiently flush part of DNS zones signed with NSEC3.
+
+Timers and events
+^^^^^^^^^^^^^^^^^
+
+The timer represents exactly the thing described in the examples - it allows you to execute closures
+after specified time, or event recurrent events. Time is always described in milliseconds,
+but there are convenient variables that you can use - ``sec, minute, hour``.
+For example, ``5 * hour`` represents five hours, or 5*60*60*100 milliseconds.
+
+.. function:: event.after(time, function)
+
+ :return: event id
+
+ Execute function after the specified time has passed.
+ The first parameter of the callback is the event itself.
+
+ Example:
+
+ .. code-block:: lua
+
+ event.after(1 * minute, function() print('Hi!') end)
+
+.. function:: event.recurrent(interval, function)
+
+ :return: event id
+
+ Similar to :func:`event.after()`, periodically execute function after ``interval`` passes.
+
+ Example:
+
+ .. code-block:: lua
+
+ msg_count = 0
+ event.recurrent(5 * sec, function(e)
+ msg_count = msg_count + 1
+ print('Hi #'..msg_count)
+ end)
+
+.. function:: event.reschedule(event_id, timeout)
+
+ Reschedule a running event, it has no effect on canceled events.
+ New events may reuse the event_id, so the behaviour is undefined if the function
+ is called after another event is started.
+
+ Example:
+
+ .. code-block:: lua
+
+ local interval = 1 * minute
+ event.after(1 * minute, function (ev)
+ print('Good morning!')
+ -- Halven the interval for each iteration
+ interval = interval / 2
+ event.reschedule(ev, interval)
+ end)
+
+.. function:: event.cancel(event_id)
+
+ Cancel running event, it has no effect on already canceled events.
+ New events may reuse the event_id, so the behaviour is undefined if the function
+ is called after another event is started.
+
+ Example:
+
+ .. code-block:: lua
+
+ e = event.after(1 * minute, function() print('Hi!') end)
+ event.cancel(e)
+
+Watch for file descriptor activity. This allows embedding other event loops or simply
+firing events when a pipe endpoint becomes active. In another words, asynchronous
+notifications for daemon.
+
+.. function:: event.socket(fd, cb)
+
+ :param number fd: file descriptor to watch
+ :param cb: closure or callback to execute when fd becomes active
+ :return: event id
+
+ Execute function when there is activity on the file descriptor and calls a closure
+ with event id as the first parameter, status as second and number of events as third.
+
+ Example:
+
+ .. code-block:: lua
+
+ e = event.socket(0, function(e, status, nevents)
+ print('activity detected')
+ end)
+ e.cancel(e)
+
+Asynchronous function execution
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The `event` package provides a very basic mean for non-blocking execution - it allows running code when activity on a file descriptor is detected, and when a certain amount of time passes. It doesn't however provide an easy to use abstraction for non-blocking I/O. This is instead exposed through the `worker` package (if `cqueues` Lua package is installed in the system).
+
+.. function:: worker.coroutine(function)
+
+ Start a new coroutine with given function (closure). The function can do I/O or run timers without blocking the main thread. See cqueues_ for documentation of possible operations and synchronization primitives. The main limitation is that you can't wait for a finish of a coroutine from processing layers, because it's not currently possible to suspend and resume execution of processing layers.
+
+ Example:
+
+ .. code-block:: lua
+
+ worker.coroutine(function ()
+ for i = 0, 10 do
+ print('executing', i)
+ worker.sleep(1)
+ end
+ end)
+
+.. function:: worker.sleep(seconds)
+
+ Pause execution of current function (asynchronously if running inside a worker coroutine).
+
+When daemon is running in forked mode, each process acts independently. This is good because it reduces software complexity and allows for runtime scaling, but not ideal because of additional operational burden.
+For example, when you want to add a new policy, you'd need to add it to either put it in the configuration, or execute command on each process independently. The daemon simplifies this by promoting process group leader which is able to execute commands synchronously over forks.
+
+ Example:
+
+ .. code-block:: lua
+
+ worker.sleep(1)
+
+.. function:: map(expr)
+
+ Run expression synchronously over all forks, results are returned as a table ordered as forks. Expression can be any valid expression in Lua.
+
+
+ Example:
+
+ .. code-block:: lua
+
+ -- Current instance only
+ hostname()
+ localhost
+ -- Mapped to forks
+ map 'hostname()'
+ [1] => localhost
+ [2] => localhost
+ -- Get worker ID from each fork
+ map 'worker.id'
+ [1] => 0
+ [2] => 1
+ -- Get cache stats from each fork
+ map 'cache.stats()'
+ [1] => {
+ [hit] => 0
+ [delete] => 0
+ [miss] => 0
+ [insert] => 0
+ }
+ [2] => {
+ [hit] => 0
+ [delete] => 0
+ [miss] => 0
+ [insert] => 0
+ }
+
+Scripting worker
+^^^^^^^^^^^^^^^^
+
+Worker is a service over event loop that tracks and schedules outstanding queries,
+you can see the statistics or schedule new queries. It also contains information about
+specified worker count and process rank.
+
+.. envvar:: worker.count
+
+ Return current total worker count (e.g. `1` for single-process)
+
+.. envvar:: worker.id
+
+ Return current worker ID (starting from `0` up to `worker.count - 1`)
+
+
+.. envvar:: worker.pid
+
+ Current worker process PID (number).
+
+
+.. function:: worker.stats()
+
+ Return table of statistics.
+
+ * ``udp`` - number of outbound queries over UDP
+ * ``tcp`` - number of outbound queries over TCP
+ * ``ipv6`` - number of outbound queries over IPv6
+ * ``ipv4`` - number of outbound queries over IPv4
+ * ``timeout`` - number of timeouted outbound queries
+ * ``concurrent`` - number of concurrent queries at the moment
+ * ``queries`` - number of inbound queries
+ * ``dropped`` - number of dropped inbound queries
+
+ Example:
+
+ .. code-block:: lua
+
+ print(worker.stats().concurrent)
+
+.. _enabling-dnssec:
+
+Enabling DNSSEC
+===============
+
+The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates and :rfc:`7646` negative trust anchors.
+To enable it, you need to provide trusted root keys. Bootstrapping of the keys is automated, and kresd fetches root trust anchors set `over a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_ from IANA. From there, it can perform :rfc:`5011` automatic updates for you.
+
+.. note:: Automatic bootstrap requires luasocket_ and luasec_ installed.
+
+.. code-block:: none
+
+ $ kresd -k root-new.keys # File for root keys
+ [ ta ] keyfile 'root-new.keys': doesn't exist, bootstrapping
+ [ ta ] Root trust anchors bootstrapped over https with pinned certificate.
+ You SHOULD verify them manually against original source:
+ https://www.iana.org/dnssec/files
+ [ ta ] Current root trust anchors are:
+ . 0 IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
+ . 0 IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
+ [ ta ] next refresh for . in 24 hours
+
+Alternatively, you can set it in configuration file with ``trust_anchors.file = 'root.keys'``. If the file doesn't exist, it will be automatically populated with root keys validated using root anchors retrieved over HTTPS.
+
+This is equivalent to `using unbound-anchor <https://www.unbound.net/documentation/howto_anchor.html>`_:
+
+.. code-block:: bash
+
+ $ unbound-anchor -a "root.keys" || echo "warning: check the key at this point"
+ $ echo "auto-trust-anchor-file: \"root.keys\"" >> unbound.conf
+ $ unbound -c unbound.conf
+
+.. warning:: Bootstrapping of the root trust anchors is automatic, you are however **encouraged to check** the key over **secure channel**, as specified in `DNSSEC Trust Anchor Publication for the Root Zone <https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs>`_. This is a critical step where the whole infrastructure may be compromised, you will be warned in the server log.
+
+Configuration is described in :ref:`dnssec-config`.
+
+Manually providing root anchors
+-------------------------------
+
+The root anchors bootstrap may fail for various reasons, in this case you need to provide IANA or alternative root anchors. The format of the keyfile is the same as for Unbound or BIND and contains DS/DNSKEY records.
+
+1. Check the current TA published on `IANA website <https://data.iana.org/root-anchors/root-anchors.xml>`_
+2. Fetch current keys (DNSKEY), verify digests
+3. Deploy them
+
+.. code-block:: bash
+
+ $ kdig DNSKEY . @k.root-servers.net +noall +answer | grep "DNSKEY[[:space:]]257" > root.keys
+ $ ldns-key2ds -n root.keys # Only print to stdout
+ ... verify that digest matches TA published by IANA ...
+ $ kresd -k root.keys
+
+You've just enabled DNSSEC!
+
+.. note:: Bootstrapping and automatic update need write access to keyfile directory. If you want to manage root anchors manually you should use ``trust_anchors.add_file('root.keys', true)``.
+
+CLI interface
+=============
+
+The daemon features a CLI interface, type ``help()`` to see the list of available commands.
+
+.. code-block:: bash
+
+ $ kresd /var/cache/knot-resolver
+ [system] started in interactive mode, type 'help()'
+ > cache.count()
+ 53
+
+.. role:: lua(code)
+ :language: lua
+
+Verbose output
+--------------
+
+If the verbose logging is compiled in, i.e. not turned off by ``-DNOVERBOSELOG``, you can turn on verbose tracing of server operation with the ``-v`` option.
+You can also toggle it on runtime with ``verbose(true|false)`` command.
+
+.. code-block:: bash
+
+ $ kresd -v
+
+To run the daemon by hand, such as under ``nohup``, use ``-f 1`` to start a single fork. For example:
+
+.. code-block:: bash
+
+ $ nohup ./daemon/kresd -a 127.0.0.1 -f 1 -v &
+
+
+Control sockets
+===============
+
+Unless ran manually, knot-resolver is typically started in non-interactive mode.
+The mode gets triggered by using the ``-f`` command-line parameter or by passing sockets from systemd.
+You can attach to the the consoles for each process; by default they are in ``rundir/tty/$PID``.
+
+.. note:: When running kresd with systemd, you can find the location of the socket(s) using ``systemctl status kresd-control@*.socket``. Typically, these are in ``/run/knot-resolver/control@*``.
+
+.. code-block:: bash
+
+ $ nc -U rundir/tty/3008 # or socat - UNIX-CONNECT:rundir/tty/3008
+ > cache.count()
+ 53
+
+The *direct output* of the CLI command is captured and sent over the socket, while also printed to the daemon standard outputs (for accountability). This gives you an immediate response on the outcome of your command.
+Error or debug logs aren't captured, but you can find them in the daemon standard outputs.
+
+This is also a way to enumerate and test running instances, the list of files in ``tty`` corresponds to the list
+of running processes, and you can test the process for liveliness by connecting to the UNIX socket.
+
+
+Utilizing multiple CPUs
+=======================
+
+The server can run in multiple independent processes, all sharing the same socket and cache. These processes can be started or stopped during runtime based on the load.
+
+**Using systemd**
+
+To run multiple daemons using systemd, use a different numeric identifier for
+the instance, for example:
+
+.. code-block:: bash
+
+ $ systemctl start kresd@1.service
+ $ systemctl start kresd@2.service
+ $ systemctl start kresd@3.service
+ $ systemctl start kresd@4.service
+
+With the use of brace expansion, the equivalent command looks like:
+
+.. code-block:: bash
+
+ $ systemctl start kresd@{1..4}.service
+
+For more details, see ``kresd.systemd(7)``.
+
+**Daemon only**
+
+.. code-block:: bash
+
+ $ kresd -f 4 rundir > kresd.log &
+ $ kresd -f 2 rundir > kresd_2.log & # Extra instances
+ $ pstree $$ -g
+ bash(3533)─┬─kresd(19212)─┬─kresd(19212)
+ │ ├─kresd(19212)
+ │ └─kresd(19212)
+ ├─kresd(19399)───kresd(19399)
+ └─pstree(19411)
+ $ kill 19399 # Kill group 2, former will continue to run
+ bash(3533)─┬─kresd(19212)─┬─kresd(19212)
+ │ ├─kresd(19212)
+ │ └─kresd(19212)
+ └─pstree(19460)
+
+.. _daemon-reuseport:
+
+.. note:: On recent Linux supporting ``SO_REUSEPORT`` (since 3.9, backported to RHEL 2.6.32) it is also able to bind to the same endpoint and distribute the load between the forked processes. If your OS doesn't support it, use only one daemon process.
+
+
+Using CLI tools
+===============
+
+* ``kresd-host.lua`` - a drop-in replacement for *host(1)* utility
+
+Queries the DNS for information.
+The hostname is looked up for IP4, IP6 and mail.
+
+Example:
+
+.. code-block:: bash
+
+ $ kresd-host.lua -f root.key -v nic.cz
+ nic.cz. has address 217.31.205.50 (secure)
+ nic.cz. has IPv6 address 2001:1488:0:3::2 (secure)
+ nic.cz. mail is handled by 10 mail.nic.cz. (secure)
+ nic.cz. mail is handled by 20 mx.nic.cz. (secure)
+ nic.cz. mail is handled by 30 bh.nic.cz. (secure)
+
+* ``kresd-query.lua`` - run the daemon in zero-configuration mode, perform a query and execute given callback.
+
+This is useful for executing one-shot queries and hooking into the processing of the result,
+for example to check if a domain is managed by a certain registrar or if it's signed.
+
+Example:
+
+.. code-block:: bash
+
+ $ kresd-query.lua www.sub.nic.cz 'assert(kres.dname2str(req:resolved().zone_cut.name) == "nic.cz.")' && echo "yes"
+ yes
+ $ kresd-query.lua -C 'trust_anchors.config("root.keys")' nic.cz 'assert(req:resolved().flags.DNSSEC_WANT)'
+ $ echo $?
+ 0
+
+.. _`JSON-encoded`: http://json.org/example
+.. _`Learn Lua in 15 minutes`: http://tylerneylon.com/a/learn-lua/
+.. _`PowerDNS Recursor`: https://doc.powerdns.com/md/recursor/scripting/
+.. _LuaRocks: https://rocks.moonscript.org/
+.. _libuv: https://github.com/libuv/libuv
+.. _Lua: https://www.lua.org/about.html
+.. _LuaJIT: http://luajit.org/luajit.html
+.. _luasec: https://luarocks.org/modules/brunoos/luasec
+.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
+.. _cqueues: https://25thandclement.com/~william/projects/cqueues.html
+.. _`real process managers`: http://blog.crocodoc.com/post/48703468992/process-managers-the-good-the-bad-and-the-ugly
+.. _`socket activation`: http://0pointer.de/blog/projects/socket-activation.html
+.. _`dnsproxy module`: https://www.knot-dns.cz/docs/2.7/html/modules.html#dnsproxy-tiny-dns-proxy
diff --git a/daemon/bindings.c b/daemon/bindings.c
new file mode 100644
index 0000000..3a7dae1
--- /dev/null
+++ b/daemon/bindings.c
@@ -0,0 +1,1849 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <uv.h>
+#include <contrib/cleanup.h>
+#include <libknot/descriptor.h>
+
+#include "lib/cache/api.h"
+#include "lib/cache/cdb_api.h"
+#include "lib/utils.h"
+#include "daemon/bindings.h"
+#include "daemon/worker.h"
+#include "daemon/tls.h"
+#include "daemon/zimport.h"
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+/** @internal Annotate for static checkers. */
+KR_NORETURN int lua_error (lua_State *L);
+
+/** @internal Prefix error with file:line */
+static int format_error(lua_State* L, const char *err)
+{
+ lua_Debug d;
+ lua_getstack(L, 1, &d);
+ /* error message prefix */
+ lua_getinfo(L, "Sln", &d);
+ if (strncmp(d.short_src, "[", 1) != 0) {
+ lua_pushstring(L, d.short_src);
+ lua_pushstring(L, ":");
+ lua_pushnumber(L, d.currentline);
+ lua_pushstring(L, ": error: ");
+ lua_concat(L, 4);
+ } else {
+ lua_pushstring(L, "error: ");
+ }
+ /* error message */
+ lua_pushstring(L, err);
+ lua_concat(L, 2);
+ return 1;
+}
+
+static inline struct worker_ctx *wrk_luaget(lua_State *L) {
+ lua_getglobal(L, "__worker");
+ struct worker_ctx *worker = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+ return worker;
+}
+
+/** List loaded modules */
+static int mod_list(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ lua_newtable(L);
+ for (unsigned i = 0; i < engine->modules.len; ++i) {
+ struct kr_module *module = engine->modules.at[i];
+ lua_pushstring(L, module->name);
+ lua_rawseti(L, -2, i + 1);
+ }
+ return 1;
+}
+
+/** Load module. */
+static int mod_load(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n != 1 || !lua_isstring(L, 1)) {
+ format_error(L, "expected 'load(string name)'");
+ lua_error(L);
+ }
+ /* Parse precedence declaration */
+ char *declaration = strdup(lua_tostring(L, 1));
+ if (!declaration) {
+ return kr_error(ENOMEM);
+ }
+ const char *name = strtok(declaration, " ");
+ const char *precedence = strtok(NULL, " ");
+ const char *ref = strtok(NULL, " ");
+ /* Load engine module */
+ struct engine *engine = engine_luaget(L);
+ int ret = engine_register(engine, name, precedence, ref);
+ free(declaration);
+ if (ret != 0) {
+ if (ret == kr_error(EIDRM)) {
+ format_error(L, "referenced module not found");
+ } else {
+ format_error(L, kr_strerror(ret));
+ }
+ lua_error(L);
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+/** Unload module. */
+static int mod_unload(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n != 1 || !lua_isstring(L, 1)) {
+ format_error(L, "expected 'unload(string name)'");
+ lua_error(L);
+ }
+ /* Unload engine module */
+ struct engine *engine = engine_luaget(L);
+ int ret = engine_unregister(engine, lua_tostring(L, 1));
+ if (ret != 0) {
+ format_error(L, kr_strerror(ret));
+ lua_error(L);
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+int lib_modules(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "list", mod_list },
+ { "load", mod_load },
+ { "unload", mod_unload },
+ { NULL, NULL }
+ };
+
+ register_lib(L, "modules", lib);
+ return 1;
+}
+
+/** Append 'addr = {port = int, udp = bool, tcp = bool}' */
+static int net_list_add(const char *key, void *val, void *ext)
+{
+ lua_State *L = (lua_State *)ext;
+ endpoint_array_t *ep_array = val;
+ lua_newtable(L);
+ for (size_t i = ep_array->len; i--;) {
+ struct endpoint *ep = ep_array->at[i];
+ lua_pushinteger(L, ep->port);
+ lua_setfield(L, -2, "port");
+ lua_pushboolean(L, ep->flags & NET_UDP);
+ lua_setfield(L, -2, "udp");
+ lua_pushboolean(L, ep->flags & NET_TCP);
+ lua_setfield(L, -2, "tcp");
+ lua_pushboolean(L, ep->flags & NET_TLS);
+ lua_setfield(L, -2, "tls");
+ }
+ lua_setfield(L, -2, key);
+ return kr_ok();
+}
+
+/** List active endpoints. */
+static int net_list(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ lua_newtable(L);
+ map_walk(&engine->net.endpoints, net_list_add, L);
+ return 1;
+}
+
+/** Listen on an address list represented by the top of lua stack. */
+static int net_listen_addrs(lua_State *L, int port, int flags)
+{
+ /* Case: table with 'addr' field; only follow that field directly. */
+ lua_getfield(L, -1, "addr");
+ if (!lua_isnil(L, -1)) {
+ lua_replace(L, -2);
+ } else {
+ lua_pop(L, 1);
+ }
+
+ /* Case: string, representing a single address. */
+ const char *str = lua_tostring(L, -1);
+ if (str != NULL) {
+ struct engine *engine = engine_luaget(L);
+ int ret = network_listen(&engine->net, str, port, flags);
+ if (ret != 0) {
+ kr_log_info("[system] bind to '%s@%d' %s\n",
+ str, port, kr_strerror(ret));
+ }
+ return ret == 0;
+ }
+
+ /* Last case: table where all entries are added recursively. */
+ if (!lua_istable(L, -1)) {
+ format_error(L, "bad type for address");
+ lua_error(L);
+ return 0;
+ }
+ lua_pushnil(L);
+ while (lua_next(L, -2)) {
+ if (net_listen_addrs(L, port, flags) == 0)
+ return 0;
+ lua_pop(L, 1);
+ }
+ return 1;
+}
+
+static bool table_get_flag(lua_State *L, int index, const char *key, bool def)
+{
+ bool result = def;
+ lua_getfield(L, index, key);
+ if (lua_isboolean(L, -1)) {
+ result = lua_toboolean(L, -1);
+ }
+ lua_pop(L, 1);
+ return result;
+}
+
+/** Listen on endpoint. */
+static int net_listen(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 1 || n > 3) {
+ format_error(L, "expected one to three arguments; usage:\n"
+ "net.listen(addressses, [port = " xstr(KR_DNS_PORT) ", flags = {tls = (port == " xstr(KR_DNS_TLS_PORT) ")}])\n");
+ lua_error(L);
+ }
+
+ int port = KR_DNS_PORT;
+ if (n > 1 && lua_isnumber(L, 2)) {
+ port = lua_tointeger(L, 2);
+ }
+
+ bool tls = (port == KR_DNS_TLS_PORT);
+ if (n > 2 && lua_istable(L, 3)) {
+ tls = table_get_flag(L, 3, "tls", tls);
+ }
+ int flags = tls ? (NET_TCP|NET_TLS) : (NET_TCP|NET_UDP);
+
+ /* Now focus on the first argument. */
+ lua_pop(L, n - 1);
+ int res = net_listen_addrs(L, port, flags);
+ lua_pushboolean(L, res);
+ return res;
+}
+
+/** Close endpoint. */
+static int net_close(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 2) {
+ format_error(L, "expected 'close(string addr, number port)'");
+ lua_error(L);
+ }
+
+ /* Open resolution context cache */
+ struct engine *engine = engine_luaget(L);
+ int ret = network_close(&engine->net, lua_tostring(L, 1), lua_tointeger(L, 2));
+ lua_pushboolean(L, ret == 0);
+ return 1;
+}
+
+/** List available interfaces. */
+static int net_interfaces(lua_State *L)
+{
+ /* Retrieve interface list */
+ int count = 0;
+ char buf[INET6_ADDRSTRLEN]; /* https://tools.ietf.org/html/rfc4291 */
+ uv_interface_address_t *info = NULL;
+ uv_interface_addresses(&info, &count);
+ lua_newtable(L);
+ for (int i = 0; i < count; ++i) {
+ uv_interface_address_t iface = info[i];
+ lua_getfield(L, -1, iface.name);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ lua_newtable(L);
+ }
+
+ /* Address */
+ lua_getfield(L, -1, "addr");
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ lua_newtable(L);
+ }
+ if (iface.address.address4.sin_family == AF_INET) {
+ uv_ip4_name(&iface.address.address4, buf, sizeof(buf));
+ } else if (iface.address.address4.sin_family == AF_INET6) {
+ uv_ip6_name(&iface.address.address6, buf, sizeof(buf));
+ } else {
+ buf[0] = '\0';
+ }
+ lua_pushstring(L, buf);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ lua_setfield(L, -2, "addr");
+
+ /* Hardware address. */
+ char *p = buf;
+ memset(buf, 0, sizeof(buf));
+ for (unsigned k = 0; k < sizeof(iface.phys_addr); ++k) {
+ sprintf(p, "%s%.2x", k > 0 ? ":" : "", iface.phys_addr[k] & 0xff);
+ p += 3;
+ }
+ lua_pushstring(L, buf);
+ lua_setfield(L, -2, "mac");
+
+ /* Push table */
+ lua_setfield(L, -2, iface.name);
+ }
+ uv_free_interface_addresses(info, count);
+
+ return 1;
+}
+
+/** Set UDP maximum payload size. */
+static int net_bufsize(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ knot_rrset_t *opt_rr = engine->resolver.opt_rr;
+ if (!lua_isnumber(L, 1)) {
+ lua_pushnumber(L, knot_edns_get_payload(opt_rr));
+ return 1;
+ }
+ int bufsize = lua_tointeger(L, 1);
+ if (bufsize < 512 || bufsize > UINT16_MAX) {
+ format_error(L, "bufsize must be within <512, " xstr(UINT16_MAX) ">");
+ lua_error(L);
+ }
+ knot_edns_set_payload(opt_rr, (uint16_t) bufsize);
+ return 0;
+}
+
+/** Set TCP pipelining size. */
+static int net_pipeline(lua_State *L)
+{
+ struct worker_ctx *worker = wrk_luaget(L);
+ if (!worker) {
+ return 0;
+ }
+ if (!lua_isnumber(L, 1)) {
+ lua_pushnumber(L, worker->tcp_pipeline_max);
+ return 1;
+ }
+ int len = lua_tointeger(L, 1);
+ if (len < 0 || len > UINT16_MAX) {
+ format_error(L, "tcp_pipeline must be within <0, " xstr(UINT16_MAX) ">");
+ lua_error(L);
+ }
+ worker->tcp_pipeline_max = len;
+ lua_pushnumber(L, len);
+ return 1;
+}
+
+static int net_tls(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (!engine) {
+ return 0;
+ }
+ struct network *net = &engine->net;
+ if (!net) {
+ return 0;
+ }
+
+ /* Only return current credentials. */
+ if (lua_gettop(L) == 0) {
+ /* No credentials configured yet. */
+ if (!net->tls_credentials) {
+ return 0;
+ }
+ lua_newtable(L);
+ lua_pushstring(L, net->tls_credentials->tls_cert);
+ lua_setfield(L, -2, "cert_file");
+ lua_pushstring(L, net->tls_credentials->tls_key);
+ lua_setfield(L, -2, "key_file");
+ return 1;
+ }
+
+ if ((lua_gettop(L) != 2) || !lua_isstring(L, 1) || !lua_isstring(L, 2)) {
+ lua_pushstring(L, "net.tls takes two parameters: (\"cert_file\", \"key_file\")");
+ lua_error(L);
+ }
+
+ int r = tls_certificate_set(net, lua_tostring(L, 1), lua_tostring(L, 2));
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int print_tls_param(const char *key, void *val, void *data)
+{
+ if (!val) {
+ return 0;
+ }
+
+ struct tls_client_paramlist_entry *entry = (struct tls_client_paramlist_entry *)val;
+
+ lua_State *L = (lua_State *)data;
+
+ lua_createtable(L, 0, 3);
+
+ lua_createtable(L, entry->pins.len, 0);
+ for (size_t i = 0; i < entry->pins.len; ++i) {
+ lua_pushnumber(L, i + 1);
+ lua_pushstring(L, entry->pins.at[i]);
+ lua_settable(L, -3);
+ }
+ lua_setfield(L, -2, "pins");
+
+ lua_createtable(L, entry->ca_files.len, 0);
+ for (size_t i = 0; i < entry->ca_files.len; ++i) {
+ lua_pushnumber(L, i + 1);
+ lua_pushstring(L, entry->ca_files.at[i]);
+ lua_settable(L, -3);
+ }
+ lua_setfield(L, -2, "ca_files");
+
+ lua_createtable(L, entry->hostnames.len, 0);
+ for (size_t i = 0; i < entry->hostnames.len; ++i) {
+ lua_pushnumber(L, i + 1);
+ lua_pushstring(L, entry->hostnames.at[i]);
+ lua_settable(L, -3);
+ }
+ lua_setfield(L, -2, "hostnames");
+
+ lua_setfield(L, -2, key);
+
+ return 0;
+}
+
+static int print_tls_client_params(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (!engine) {
+ return 0;
+ }
+ struct network *net = &engine->net;
+ if (!net) {
+ return 0;
+ }
+ if (net->tls_client_params.root == 0 ) {
+ return 0;
+ }
+ lua_newtable(L);
+ map_walk(&net->tls_client_params, print_tls_param, (void *)L);
+ return 1;
+}
+
+
+static int net_tls_client(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (!engine) {
+ return 0;
+ }
+ struct network *net = &engine->net;
+ if (!net) {
+ return 0;
+ }
+
+ /* Only return current credentials. */
+ if (lua_gettop(L) == 0) {
+ return print_tls_client_params(L);
+ }
+
+ const char *full_addr = NULL;
+ bool pin_exists = false;
+ bool hostname_exists = false;
+ if ((lua_gettop(L) == 1) && lua_isstring(L, 1)) {
+ full_addr = lua_tostring(L, 1);
+ } else if ((lua_gettop(L) == 2) && lua_isstring(L, 1) && lua_istable(L, 2)) {
+ full_addr = lua_tostring(L, 1);
+ pin_exists = true;
+ } else if ((lua_gettop(L) == 3) && lua_isstring(L, 1) && lua_istable(L, 2)) {
+ full_addr = lua_tostring(L, 1);
+ hostname_exists = true;
+ } else if ((lua_gettop(L) == 4) && lua_isstring(L, 1) &&
+ lua_istable(L, 2) && lua_istable(L, 3)) {
+ full_addr = lua_tostring(L, 1);
+ pin_exists = true;
+ hostname_exists = true;
+ } else {
+ format_error(L, "net.tls_client takes one parameter (\"address\"), two parameters (\"address\",\"pin\"), three parameters (\"address\", \"ca_file\", \"hostname\") or four ones: (\"address\", \"pin\", \"ca_file\", \"hostname\")");
+ lua_error(L);
+ }
+
+ char addr[INET6_ADDRSTRLEN];
+ uint16_t port = 0;
+ if (kr_straddr_split(full_addr, addr, sizeof(addr), &port) != kr_ok()) {
+ format_error(L, "invalid IP address");
+ lua_error(L);
+ }
+
+ if (port == 0) {
+ port = 853;
+ }
+
+ if (!pin_exists && !hostname_exists) {
+ int r = tls_client_params_set(&net->tls_client_params,
+ addr, port, NULL,
+ TLS_CLIENT_PARAM_NONE);
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+
+ lua_pushboolean(L, true);
+ return 1;
+ }
+
+ if (pin_exists) {
+ /* iterate over table with pins
+ * http://www.lua.org/manual/5.1/manual.html#lua_next */
+ lua_pushnil(L); /* first key */
+ while (lua_next(L, 2)) { /* pin table is in stack at index 2 */
+ /* pin now at index -1, key at index -2*/
+ const char *pin = lua_tostring(L, -1);
+ int r = tls_client_params_set(&net->tls_client_params,
+ addr, port, pin,
+ TLS_CLIENT_PARAM_PIN);
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+ lua_pop(L, 1);
+ }
+ }
+
+ int ca_table_index = 2;
+ int hostname_table_index = 3;
+ if (hostname_exists) {
+ if (pin_exists) {
+ ca_table_index = 3;
+ hostname_table_index = 4;
+ }
+ } else {
+ lua_pushboolean(L, true);
+ return 1;
+ }
+
+ /* iterate over hostnames,
+ * it must be done before iterating over ca filenames */
+ lua_pushnil(L);
+ while (lua_next(L, hostname_table_index)) {
+ const char *hostname = lua_tostring(L, -1);
+ int r = tls_client_params_set(&net->tls_client_params,
+ addr, port, hostname,
+ TLS_CLIENT_PARAM_HOSTNAME);
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+ /* removes 'value'; keeps 'key' for next iteration */
+ lua_pop(L, 1);
+ }
+
+ /* iterate over ca filenames */
+ lua_pushnil(L);
+ size_t num_of_ca_files = 0;
+ while (lua_next(L, ca_table_index)) {
+ const char *ca_file = lua_tostring(L, -1);
+ int r = tls_client_params_set(&net->tls_client_params,
+ addr, port, ca_file,
+ TLS_CLIENT_PARAM_CA);
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+ num_of_ca_files += 1;
+ /* removes 'value'; keeps 'key' for next iteration */
+ lua_pop(L, 1);
+ }
+
+ if (num_of_ca_files == 0) {
+ /* No ca files were explicitly configured, so use system CA */
+ int r = tls_client_params_set(&net->tls_client_params,
+ addr, port, NULL,
+ TLS_CLIENT_PARAM_CA);
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+ }
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_tls_client_clear(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (!engine) {
+ return 0;
+ }
+
+ struct network *net = &engine->net;
+ if (!net) {
+ return 0;
+ }
+
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ format_error(L, "net.tls_client_clear() requires one parameter (\"address\")");
+ lua_error(L);
+ }
+
+ const char *full_addr = lua_tostring(L, 1);
+
+ char addr[INET6_ADDRSTRLEN];
+ uint16_t port = 0;
+ if (kr_straddr_split(full_addr, addr, sizeof(addr), &port) != kr_ok()) {
+ format_error(L, "invalid IP address");
+ lua_error(L);
+ }
+
+ if (port == 0) {
+ port = 853;
+ }
+
+ int r = tls_client_params_clear(&net->tls_client_params, addr, port);
+ if (r != 0) {
+ lua_pushstring(L, kr_strerror(r));
+ lua_error(L);
+ }
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_tls_padding(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+
+ /* Only return current padding. */
+ if (lua_gettop(L) == 0) {
+ if (engine->resolver.tls_padding < 0) {
+ lua_pushboolean(L, true);
+ return 1;
+ } else if (engine->resolver.tls_padding == 0) {
+ lua_pushboolean(L, false);
+ return 1;
+ }
+ lua_pushinteger(L, engine->resolver.tls_padding);
+ return 1;
+ }
+
+ if ((lua_gettop(L) != 1)) {
+ lua_pushstring(L, "net.tls_padding takes one parameter: (\"padding\")");
+ lua_error(L);
+ }
+ if (lua_isboolean(L, 1)) {
+ bool x = lua_toboolean(L, 1);
+ if (x) {
+ engine->resolver.tls_padding = -1;
+ } else {
+ engine->resolver.tls_padding = 0;
+ }
+ } else if (lua_isnumber(L, 1)) {
+ int padding = lua_tointeger(L, 1);
+ if ((padding < 0) || (padding > MAX_TLS_PADDING)) {
+ lua_pushstring(L, "net.tls_padding parameter has to be true, false, or a number between <0, " xstr(MAX_TLS_PADDING) ">");
+ lua_error(L);
+ }
+ engine->resolver.tls_padding = padding;
+ } else {
+ lua_pushstring(L, "net.tls_padding parameter has to be true, false, or a number between <0, " xstr(MAX_TLS_PADDING) ">");
+ lua_error(L);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+/** Shorter salt can't contain much entropy. */
+#define net_tls_sticket_MIN_SECRET_LEN 32
+
+static int net_tls_sticket_secret_string(lua_State *L)
+{
+ struct network *net = &engine_luaget(L)->net;
+
+ size_t secret_len;
+ const char *secret;
+
+ if (lua_gettop(L) == 0) {
+ /* Zero-length secret, implying random key. */
+ secret_len = 0;
+ secret = NULL;
+ } else {
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ lua_pushstring(L,
+ "net.tls_sticket_secret takes one parameter: (\"secret string\")");
+ lua_error(L);
+ }
+ secret = lua_tolstring(L, 1, &secret_len);
+ if (secret_len < net_tls_sticket_MIN_SECRET_LEN || !secret) {
+ lua_pushstring(L, "net.tls_sticket_secret - the secret is shorter than "
+ xstr(net_tls_sticket_MIN_SECRET_LEN) " bytes");
+ lua_error(L);
+ }
+ }
+
+ tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx);
+ net->tls_session_ticket_ctx =
+ tls_session_ticket_ctx_create(net->loop, secret, secret_len);
+ if (net->tls_session_ticket_ctx == NULL) {
+ lua_pushstring(L,
+ "net.tls_sticket_secret_string - can't create session ticket context");
+ lua_error(L);
+ }
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_tls_sticket_secret_file(lua_State *L)
+{
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ lua_pushstring(L,
+ "net.tls_sticket_secret_file takes one parameter: (\"file name\")");
+ lua_error(L);
+ }
+
+ const char *file_name = lua_tostring(L, 1);
+ if (strlen(file_name) == 0) {
+ lua_pushstring(L, "net.tls_sticket_secret_file - empty file name");
+ lua_error(L);
+ }
+
+ FILE *fp = fopen(file_name, "r");
+ if (fp == NULL) {
+ lua_pushfstring(L, "net.tls_sticket_secret_file - can't open file '%s': %s",
+ file_name, strerror(errno));
+ lua_error(L);
+ }
+
+ char secret_buf[TLS_SESSION_TICKET_SECRET_MAX_LEN];
+ const size_t secret_len = fread(secret_buf, 1, sizeof(secret_buf), fp);
+ int err = ferror(fp);
+ if (err) {
+ lua_pushfstring(L,
+ "net.tls_sticket_secret_file - error reading from file '%s': %s",
+ file_name, strerror(err));
+ lua_error(L);
+ }
+ if (secret_len < net_tls_sticket_MIN_SECRET_LEN) {
+ lua_pushfstring(L,
+ "net.tls_sticket_secret_file - file '%s' is shorter than "
+ xstr(net_tls_sticket_MIN_SECRET_LEN) " bytes",
+ file_name);
+ lua_error(L);
+ }
+ fclose(fp);
+
+ struct network *net = &engine_luaget(L)->net;
+
+ tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx);
+ net->tls_session_ticket_ctx =
+ tls_session_ticket_ctx_create(net->loop, secret_buf, secret_len);
+ if (net->tls_session_ticket_ctx == NULL) {
+ lua_pushstring(L,
+ "net.tls_sticket_secret_file - can't create session ticket context");
+ lua_error(L);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_outgoing(lua_State *L, int family)
+{
+ struct worker_ctx *worker = wrk_luaget(L);
+ union inaddr *addr;
+ if (family == AF_INET)
+ addr = (union inaddr*)&worker->out_addr4;
+ else
+ addr = (union inaddr*)&worker->out_addr6;
+
+ if (lua_gettop(L) == 0) { /* Return the current value. */
+ if (addr->ip.sa_family == AF_UNSPEC) {
+ lua_pushnil(L);
+ return 1;
+ }
+ if (addr->ip.sa_family != family) {
+ assert(false);
+ lua_error(L);
+ }
+ char addr_buf[INET6_ADDRSTRLEN];
+ int err;
+ if (family == AF_INET)
+ err = uv_ip4_name(&addr->ip4, addr_buf, sizeof(addr_buf));
+ else
+ err = uv_ip6_name(&addr->ip6, addr_buf, sizeof(addr_buf));
+ if (err)
+ lua_error(L);
+ lua_pushstring(L, addr_buf);
+ return 1;
+ }
+
+ if ((lua_gettop(L) != 1) || (!lua_isstring(L, 1) && !lua_isnil(L, 1))) {
+ format_error(L, "net.outgoing_vX takes one address string parameter or nil");
+ lua_error(L);
+ }
+
+ if (lua_isnil(L, 1)) {
+ addr->ip.sa_family = AF_UNSPEC;
+ return 1;
+ }
+
+ const char *addr_str = lua_tostring(L, 1);
+ int err;
+ if (family == AF_INET)
+ err = uv_ip4_addr(addr_str, 0, &addr->ip4);
+ else
+ err = uv_ip6_addr(addr_str, 0, &addr->ip6);
+ if (err) {
+ format_error(L, "net.outgoing_vX: failed to parse the address");
+ lua_error(L);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_outgoing_v4(lua_State *L) { return net_outgoing(L, AF_INET); }
+static int net_outgoing_v6(lua_State *L) { return net_outgoing(L, AF_INET6); }
+
+static int net_update_timeout(lua_State *L, uint64_t *timeout, const char *name)
+{
+ /* Only return current idle timeout. */
+ if (lua_gettop(L) == 0) {
+ lua_pushnumber(L, *timeout);
+ return 1;
+ }
+
+ if ((lua_gettop(L) != 1)) {
+ lua_pushstring(L, name);
+ lua_pushstring(L, " takes one parameter: (\"idle timeout\")");
+ lua_error(L);
+ }
+
+ if (lua_isnumber(L, 1)) {
+ int idle_timeout = lua_tointeger(L, 1);
+ if (idle_timeout <= 0) {
+ lua_pushstring(L, name);
+ lua_pushstring(L, " parameter has to be positive number");
+ lua_error(L);
+ }
+ *timeout = idle_timeout;
+ } else {
+ lua_pushstring(L, name);
+ lua_pushstring(L, " parameter has to be positive number");
+ lua_error(L);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_tcp_in_idle(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct network *net = &engine->net;
+
+ return net_update_timeout(L, &net->tcp.in_idle_timeout, "net.tcp_in_idle");
+}
+
+static int net_tls_handshake_timeout(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct network *net = &engine->net;
+
+ return net_update_timeout(L, &net->tcp.tls_handshake_timeout, "net.tls_handshake_timeout");
+}
+
+static int net_bpf_set(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct network *net = &engine->net;
+
+ if (lua_gettop(L) != 1 || !lua_isnumber(L, 1)) {
+ format_error(L, "net.bpf_set(fd) takes one parameter: the open file descriptor of a loaded BPF program");
+ lua_error(L);
+ return 0;
+ }
+
+#if __linux__
+
+ int progfd = lua_tointeger(L, 1);
+ if (progfd == 0) {
+ /* conversion error despite that fact
+ * that lua_isnumber(L, 1) has returned true.
+ * Real or stdin? */
+ lua_error(L);
+ return 0;
+ }
+ lua_pop(L, 1);
+
+ if (network_set_bpf(net, progfd) == 0) {
+ char errmsg[256] = { 0 };
+ snprintf(errmsg, sizeof(errmsg), "failed to attach BPF program to some networks: %s", strerror(errno));
+ format_error(L, errmsg);
+ lua_error(L);
+ return 0;
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+
+#endif
+
+ format_error(L, "BPF is not supported on this operating system");
+ lua_error(L);
+ return 0;
+}
+
+static int net_bpf_clear(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct network *net = &engine->net;
+
+ if (lua_gettop(L) != 0) {
+ format_error(L, "net.bpf_clear() does not take any parameters");
+ lua_error(L);
+ return 0;
+ }
+
+#if __linux__
+
+ network_clear_bpf(net);
+
+ lua_pushboolean(L, 1);
+ return 1;
+
+#endif
+
+ format_error(L, "BPF is not supported on this operating system");
+ lua_error(L);
+ return 0;
+}
+
+int lib_net(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "list", net_list },
+ { "listen", net_listen },
+ { "close", net_close },
+ { "interfaces", net_interfaces },
+ { "bufsize", net_bufsize },
+ { "tcp_pipeline", net_pipeline },
+ { "tls", net_tls },
+ { "tls_server", net_tls },
+ { "tls_client", net_tls_client },
+ { "tls_client_clear", net_tls_client_clear },
+ { "tls_padding", net_tls_padding },
+ { "tls_sticket_secret", net_tls_sticket_secret_string },
+ { "tls_sticket_secret_file", net_tls_sticket_secret_file },
+ { "outgoing_v4", net_outgoing_v4 },
+ { "outgoing_v6", net_outgoing_v6 },
+ { "tcp_in_idle", net_tcp_in_idle },
+ { "tls_handshake_timeout", net_tls_handshake_timeout },
+ { "bpf_set", net_bpf_set },
+ { "bpf_clear", net_bpf_clear },
+ { NULL, NULL }
+ };
+ register_lib(L, "net", lib);
+ return 1;
+}
+
+
+
+/** @internal return cache, or throw lua error if not open */
+struct kr_cache * cache_assert_open(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct kr_cache *cache = &engine->resolver.cache;
+ assert(cache);
+ if (!cache || !kr_cache_is_open(cache)) {
+ format_error(L, "no cache is open yet, use cache.open() or cache.size, etc.");
+ lua_error(L);
+ }
+ return cache;
+}
+
+/** Return available cached backends. */
+static int cache_backends(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+
+ lua_newtable(L);
+ for (unsigned i = 0; i < engine->backends.len; ++i) {
+ const struct kr_cdb_api *api = engine->backends.at[i];
+ lua_pushboolean(L, api == engine->resolver.cache.api);
+ lua_setfield(L, -2, api->name);
+ }
+ return 1;
+}
+
+/** Return number of cached records. */
+static int cache_count(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+
+ int count = cache->api->count(cache->db);
+ if (count >= 0) {
+ /* First key is a version counter, omit it if nonempty. */
+ lua_pushinteger(L, count ? count - 1 : 0);
+ return 1;
+ }
+ return 0;
+}
+
+/** Return time of last checkpoint, or re-set it if passed `true`. */
+static int cache_checkpoint(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+
+ if (lua_gettop(L) == 0) { /* Return the current value. */
+ lua_newtable(L);
+ lua_pushnumber(L, cache->checkpoint_monotime);
+ lua_setfield(L, -2, "monotime");
+ lua_newtable(L);
+ lua_pushnumber(L, cache->checkpoint_walltime.tv_sec);
+ lua_setfield(L, -2, "sec");
+ lua_pushnumber(L, cache->checkpoint_walltime.tv_usec);
+ lua_setfield(L, -2, "usec");
+ lua_setfield(L, -2, "walltime");
+ return 1;
+ }
+
+ if (lua_gettop(L) != 1 || !lua_isboolean(L, 1) || !lua_toboolean(L, 1)) {
+ format_error(L, "cache.checkpoint() takes no parameters or a true value");
+ lua_error(L);
+ }
+ kr_cache_make_checkpoint(cache);
+ return 1;
+}
+
+/** Return cache statistics. */
+static int cache_stats(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+ lua_newtable(L);
+ lua_pushnumber(L, cache->stats.hit);
+ lua_setfield(L, -2, "hit");
+ lua_pushnumber(L, cache->stats.miss);
+ lua_setfield(L, -2, "miss");
+ lua_pushnumber(L, cache->stats.insert);
+ lua_setfield(L, -2, "insert");
+ lua_pushnumber(L, cache->stats.delete);
+ lua_setfield(L, -2, "delete");
+ return 1;
+}
+
+static const struct kr_cdb_api *cache_select(struct engine *engine, const char **conf)
+{
+ /* Return default backend */
+ if (*conf == NULL || !strstr(*conf, "://")) {
+ return engine->backends.at[0];
+ }
+
+ /* Find storage backend from config prefix */
+ for (unsigned i = 0; i < engine->backends.len; ++i) {
+ const struct kr_cdb_api *api = engine->backends.at[i];
+ if (strncmp(*conf, api->name, strlen(api->name)) == 0) {
+ *conf += strlen(api->name) + strlen("://");
+ return api;
+ }
+ }
+
+ return NULL;
+}
+
+static int cache_max_ttl(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+
+ int n = lua_gettop(L);
+ if (n > 0) {
+ if (!lua_isnumber(L, 1)) {
+ format_error(L, "expected 'max_ttl(number ttl)'");
+ lua_error(L);
+ }
+ uint32_t min = cache->ttl_min;
+ int64_t ttl = lua_tonumber(L, 1);
+ if (ttl < 0 || ttl < min || ttl > UINT32_MAX) {
+ format_error(L, "max_ttl must be larger than minimum TTL, and in range <1, " xstr(UINT32_MAX) ">'");
+ lua_error(L);
+ }
+ cache->ttl_max = ttl;
+ }
+ lua_pushinteger(L, cache->ttl_max);
+ return 1;
+}
+
+
+static int cache_min_ttl(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+
+ int n = lua_gettop(L);
+ if (n > 0) {
+ if (!lua_isnumber(L, 1)) {
+ format_error(L, "expected 'min_ttl(number ttl)'");
+ lua_error(L);
+ }
+ uint32_t max = cache->ttl_max;
+ int64_t ttl = lua_tonumber(L, 1);
+ if (ttl < 0 || ttl > max || ttl > UINT32_MAX) {
+ format_error(L, "min_ttl must be smaller than maximum TTL, and in range <0, " xstr(UINT32_MAX) ">'");
+ lua_error(L);
+ }
+ cache->ttl_min = ttl;
+ }
+ lua_pushinteger(L, cache->ttl_min);
+ return 1;
+}
+
+/** Open cache */
+static int cache_open(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isnumber(L, 1)) {
+ format_error(L, "expected 'open(number max_size, string config = \"\")'");
+ lua_error(L);
+ }
+
+ /* Select cache storage backend */
+ struct engine *engine = engine_luaget(L);
+
+ lua_Number csize_lua = lua_tonumber(L, 1);
+ if (!(csize_lua >= 8192 && csize_lua < SIZE_MAX)) { /* min. is basically arbitrary */
+ format_error(L, "invalid cache size specified, it must be in range <8192, " xstr(SIZE_MAX) ">");
+ lua_error(L);
+ }
+ size_t cache_size = csize_lua;
+
+ const char *conf = n > 1 ? lua_tostring(L, 2) : NULL;
+ const char *uri = conf;
+ const struct kr_cdb_api *api = cache_select(engine, &conf);
+ if (!api) {
+ format_error(L, "unsupported cache backend");
+ lua_error(L);
+ }
+
+ /* Close if already open */
+ kr_cache_close(&engine->resolver.cache);
+
+ /* Reopen cache */
+ struct kr_cdb_opts opts = {
+ (conf && strlen(conf)) ? conf : ".",
+ cache_size
+ };
+ int ret = kr_cache_open(&engine->resolver.cache, api, &opts, engine->pool);
+ if (ret != 0) {
+ char cwd[PATH_MAX];
+ if(getcwd(cwd, sizeof(cwd)) == NULL) {
+ const char errprefix[] = "<invalid working directory>";
+ strncpy(cwd, errprefix, sizeof(cwd));
+ }
+ return luaL_error(L, "can't open cache path '%s'; working directory '%s'", opts.path, cwd);
+ }
+
+ /* Store current configuration */
+ lua_getglobal(L, "cache");
+ lua_pushstring(L, "current_size");
+ lua_pushnumber(L, cache_size);
+ lua_rawset(L, -3);
+ lua_pushstring(L, "current_storage");
+ lua_pushstring(L, uri);
+ lua_rawset(L, -3);
+ lua_pop(L, 1);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int cache_close(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct kr_cache *cache = &engine->resolver.cache;
+ if (!kr_cache_is_open(cache)) {
+ return 0;
+ }
+
+ kr_cache_close(cache);
+ lua_getglobal(L, "cache");
+ lua_pushstring(L, "current_size");
+ lua_pushnumber(L, 0);
+ lua_rawset(L, -3);
+ lua_pop(L, 1);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+#if 0
+/** @internal Prefix walk. */
+static int cache_prefixed(struct kr_cache *cache, const char *prefix, bool exact_name,
+ knot_db_val_t keyval[][2], int maxcount)
+{
+ /* Convert to domain name */
+ uint8_t buf[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(buf, prefix, sizeof(buf))) {
+ return kr_error(EINVAL);
+ }
+ /* Start prefix search */
+ return kr_cache_match(cache, buf, exact_name, keyval, maxcount);
+}
+#endif
+
+/** Prune expired/invalid records. */
+static int cache_prune(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+ /* Check parameters */
+ int prune_max = UINT16_MAX;
+ int n = lua_gettop(L);
+ if (n >= 1 && lua_isnumber(L, 1)) {
+ prune_max = lua_tointeger(L, 1);
+ }
+
+ /* Check if API supports pruning. */
+ int ret = kr_error(ENOSYS);
+ if (cache->api->prune) {
+ ret = cache->api->prune(cache->db, prune_max);
+ }
+ /* Commit and format result. */
+ if (ret < 0) {
+ format_error(L, kr_strerror(ret));
+ lua_error(L);
+ }
+ lua_pushinteger(L, ret);
+ return 1;
+}
+
+/** Clear everything. */
+static int cache_clear_everything(lua_State *L)
+{
+ struct kr_cache *cache = cache_assert_open(L);
+
+ /* Clear records and packets. */
+ int ret = kr_cache_clear(cache);
+ if (ret < 0) {
+ format_error(L, kr_strerror(ret));
+ lua_error(L);
+ }
+
+ /* Clear reputation tables */
+ struct engine *engine = engine_luaget(L);
+ lru_reset(engine->resolver.cache_rtt);
+ lru_reset(engine->resolver.cache_rep);
+ lru_reset(engine->resolver.cache_cookie);
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+#if 0
+/** @internal Dump cache key into table on Lua stack. */
+static void cache_dump(lua_State *L, knot_db_val_t keyval[])
+{
+ knot_dname_t dname[KNOT_DNAME_MAXLEN];
+ char name[KNOT_DNAME_TXT_MAXLEN];
+ uint16_t type;
+
+ int ret = kr_unpack_cache_key(keyval[0], dname, &type);
+ if (ret < 0) {
+ return;
+ }
+
+ ret = !knot_dname_to_str(name, dname, sizeof(name));
+ assert(!ret);
+ if (ret) return;
+
+ /* If name typemap doesn't exist yet, create it */
+ lua_getfield(L, -1, name);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ lua_newtable(L);
+ }
+ /* Append to typemap */
+ char type_buf[KR_RRTYPE_STR_MAXLEN] = { '\0' };
+ knot_rrtype_to_string(type, type_buf, sizeof(type_buf));
+ lua_pushboolean(L, true);
+ lua_setfield(L, -2, type_buf);
+ /* Set name typemap */
+ lua_setfield(L, -2, name);
+}
+
+/** Query cached records. TODO: fix caveats in ./README.rst documentation? */
+static int cache_get(lua_State *L)
+{
+ //struct kr_cache *cache = cache_assert_open(L); // to be fixed soon
+
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isstring(L, 1)) {
+ format_error(L, "expected 'cache.get(string key)'");
+ lua_error(L);
+ }
+
+ /* Retrieve set of keys */
+ const char *prefix = lua_tostring(L, 1);
+ knot_db_val_t keyval[100][2];
+ int ret = cache_prefixed(cache, prefix, false/*FIXME*/, keyval, 100);
+ if (ret < 0) {
+ format_error(L, kr_strerror(ret));
+ lua_error(L);
+ }
+ /* Format output */
+ lua_newtable(L);
+ for (int i = 0; i < ret; ++i) {
+ cache_dump(L, keyval[i]);
+ }
+ return 1;
+}
+#endif
+static int cache_get(lua_State *L)
+{
+ int ret = kr_error(ENOSYS);
+ format_error(L, kr_strerror(ret));
+ lua_error(L);
+ return ret;
+}
+
+/** Set time interval for cleaning rtt cache.
+ * Servers with score >= KR_NS_TIMEOUT will be cleaned after
+ * this interval ended up, so that they will be able to participate
+ * in NS elections again. */
+static int cache_ns_tout(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct kr_context *ctx = &engine->resolver;
+
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 1) {
+ lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval);
+ return 1;
+ }
+
+ if (!lua_isnumber(L, 1)) {
+ format_error(L, "expected 'cache.ns_tout(interval in ms)'");
+ lua_error(L);
+ }
+
+ lua_Integer interval_lua = lua_tointeger(L, 1);
+ if (!(interval_lua > 0 && interval_lua < UINT_MAX)) {
+ format_error(L, "invalid interval specified, it must be in range > 0, < " xstr(UINT_MAX));
+ lua_error(L);
+ }
+
+ ctx->cache_rtt_tout_retry_interval = interval_lua;
+ lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval);
+ return 1;
+}
+
+/** Zone import completion callback.
+ * Deallocates zone import context. */
+static void cache_zone_import_cb(int state, void *param)
+{
+ assert (param);
+ (void)state;
+ struct worker_ctx *worker = (struct worker_ctx *)param;
+ assert (worker->z_import);
+ zi_free(worker->z_import);
+ worker->z_import = NULL;
+}
+
+/** Import zone from file. */
+static int cache_zone_import(lua_State *L)
+{
+ int ret = -1;
+ char msg[128];
+
+ struct worker_ctx *worker = wrk_luaget(L);
+ if (!worker) {
+ strncpy(msg, "internal error, empty worker pointer", sizeof(msg));
+ goto finish;
+ }
+
+ if (worker->z_import && zi_import_started(worker->z_import)) {
+ strncpy(msg, "import already started", sizeof(msg));
+ goto finish;
+ }
+
+ (void)cache_assert_open(L); /* just check it in advance */
+
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isstring(L, 1)) {
+ strncpy(msg, "expected 'cache.zone_import(path to zone file)'", sizeof(msg));
+ goto finish;
+ }
+
+ /* Parse zone file */
+ const char *zone_file = lua_tostring(L, 1);
+
+ const char *default_origin = NULL; /* TODO */
+ uint16_t default_rclass = 1;
+ uint32_t default_ttl = 0;
+
+ if (worker->z_import == NULL) {
+ worker->z_import = zi_allocate(worker, cache_zone_import_cb, worker);
+ if (worker->z_import == NULL) {
+ strncpy(msg, "can't allocate zone import context", sizeof(msg));
+ goto finish;
+ }
+ }
+
+ ret = zi_zone_import(worker->z_import, zone_file, default_origin,
+ default_rclass, default_ttl);
+
+ lua_newtable(L);
+ if (ret == 0) {
+ strncpy(msg, "zone file successfully parsed, import started", sizeof(msg));
+ } else if (ret == 1) {
+ strncpy(msg, "TA not found", sizeof(msg));
+ } else {
+ strncpy(msg, "error parsing zone file", sizeof(msg));
+ }
+
+finish:
+ msg[sizeof(msg) - 1] = 0;
+ lua_newtable(L);
+ lua_pushstring(L, msg);
+ lua_setfield(L, -2, "msg");
+ lua_pushnumber(L, ret);
+ lua_setfield(L, -2, "code");
+
+ return 1;
+}
+
+int lib_cache(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "backends", cache_backends },
+ { "count", cache_count },
+ { "stats", cache_stats },
+ { "checkpoint", cache_checkpoint },
+ { "open", cache_open },
+ { "close", cache_close },
+ { "prune", cache_prune },
+ { "clear_everything", cache_clear_everything },
+ { "get", cache_get },
+ { "max_ttl", cache_max_ttl },
+ { "min_ttl", cache_min_ttl },
+ { "ns_tout", cache_ns_tout },
+ { "zone_import", cache_zone_import },
+ { NULL, NULL }
+ };
+
+ register_lib(L, "cache", lib);
+ return 1;
+}
+
+static void event_free(uv_timer_t *timer)
+{
+ struct worker_ctx *worker = timer->loop->data;
+ lua_State *L = worker->engine->L;
+ int ref = (intptr_t) timer->data;
+ luaL_unref(L, LUA_REGISTRYINDEX, ref);
+ free(timer);
+}
+
+static int execute_callback(lua_State *L, int argc)
+{
+ int ret = engine_pcall(L, argc);
+ if (ret != 0) {
+ fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
+ }
+ /* Clear the stack, there may be event a/o enything returned */
+ lua_settop(L, 0);
+ return ret;
+}
+
+static void event_callback(uv_timer_t *timer)
+{
+ struct worker_ctx *worker = timer->loop->data;
+ lua_State *L = worker->engine->L;
+
+ /* Retrieve callback and execute */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) timer->data);
+ lua_rawgeti(L, -1, 1);
+ lua_pushinteger(L, (intptr_t) timer->data);
+ int ret = execute_callback(L, 1);
+ /* Free callback if not recurrent or an error */
+ if (ret != 0 || (uv_timer_get_repeat(timer) == 0 && uv_is_active((uv_handle_t *)timer) == 0)) {
+ if (!uv_is_closing((uv_handle_t *)timer)) {
+ uv_close((uv_handle_t *)timer, (uv_close_cb) event_free);
+ }
+ }
+}
+
+static void event_fdcallback(uv_poll_t* handle, int status, int events)
+{
+ struct worker_ctx *worker = handle->loop->data;
+ lua_State *L = worker->engine->L;
+
+ /* Retrieve callback and execute */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) handle->data);
+ lua_rawgeti(L, -1, 1);
+ lua_pushinteger(L, (intptr_t) handle->data);
+ lua_pushinteger(L, status);
+ lua_pushinteger(L, events);
+ int ret = execute_callback(L, 3);
+ /* Free callback if not recurrent or an error */
+ if (ret != 0) {
+ if (!uv_is_closing((uv_handle_t *)handle)) {
+ uv_close((uv_handle_t *)handle, (uv_close_cb) event_free);
+ }
+ }
+}
+
+static int event_sched(lua_State *L, unsigned timeout, unsigned repeat)
+{
+ uv_timer_t *timer = malloc(sizeof(*timer));
+ if (!timer) {
+ format_error(L, "out of memory");
+ lua_error(L);
+ }
+
+ /* Start timer with the reference */
+ uv_loop_t *loop = uv_default_loop();
+ uv_timer_init(loop, timer);
+ int ret = uv_timer_start(timer, event_callback, timeout, repeat);
+ if (ret != 0) {
+ free(timer);
+ format_error(L, "couldn't start the event");
+ lua_error(L);
+ }
+
+ /* Save callback and timer in registry */
+ lua_newtable(L);
+ lua_pushvalue(L, 2);
+ lua_rawseti(L, -2, 1);
+ lua_pushlightuserdata(L, timer);
+ lua_rawseti(L, -2, 2);
+ int ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ /* Save reference to the timer */
+ timer->data = (void *) (intptr_t)ref;
+ lua_pushinteger(L, ref);
+ return 1;
+}
+
+static int event_after(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) {
+ format_error(L, "expected 'after(number timeout, function)'");
+ lua_error(L);
+ }
+
+ return event_sched(L, lua_tonumber(L, 1), 0);
+}
+
+static int event_recurrent(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) {
+ format_error(L, "expected 'recurrent(number interval, function)'");
+ lua_error(L);
+ }
+ return event_sched(L, 0, lua_tonumber(L, 1));
+}
+
+static int event_cancel(lua_State *L)
+{
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isnumber(L, 1)) {
+ format_error(L, "expected 'cancel(number event)'");
+ lua_error(L);
+ }
+
+ /* Fetch event if it exists */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1));
+ if (!lua_istable(L, -1)) {
+ lua_pushboolean(L, false);
+ return 1;
+ }
+
+ /* Close the timer */
+ lua_rawgeti(L, -1, 2);
+ uv_handle_t *timer = lua_touserdata(L, -1);
+ if (!uv_is_closing(timer)) {
+ uv_close(timer, (uv_close_cb) event_free);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int event_reschedule(lua_State *L)
+{
+ int n = lua_gettop(L);
+ if (n < 2 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
+ format_error(L, "expected 'reschedule(number event, number timeout)'");
+ lua_error(L);
+ }
+
+ /* Fetch event if it exists */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1));
+ if (!lua_istable(L, -1)) {
+ lua_pushboolean(L, false);
+ return 1;
+ }
+
+ /* Reschedule the timer */
+ lua_rawgeti(L, -1, 2);
+ uv_handle_t *timer = lua_touserdata(L, -1);
+ if (!uv_is_closing(timer)) {
+ if (uv_is_active(timer)) {
+ uv_timer_stop((uv_timer_t *)timer);
+ }
+ int ret = uv_timer_start((uv_timer_t *)timer, event_callback, lua_tointeger(L, 2), 0);
+ if (ret != 0) {
+ event_cancel(L);
+ lua_pushboolean(L, false);
+ return 1;
+ }
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int event_fdwatch(lua_State *L)
+{
+ /* Check parameters */
+ int n = lua_gettop(L);
+ if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) {
+ format_error(L, "expected 'socket(number fd, function)'");
+ lua_error(L);
+ }
+
+ uv_poll_t *handle = malloc(sizeof(*handle));
+ if (!handle) {
+ format_error(L, "out of memory");
+ lua_error(L);
+ }
+
+ /* Start timer with the reference */
+ int sock = lua_tonumber(L, 1);
+ uv_loop_t *loop = uv_default_loop();
+#if defined(__APPLE__) || defined(__FreeBSD__)
+ /* libuv is buggy and fails to create poller for
+ * kqueue sockets as it can't be fcntl'd to non-blocking mode,
+ * so we pass it a copy of standard input and then
+ * switch it with real socket before starting the poller
+ */
+ int decoy_fd = dup(STDIN_FILENO);
+ int ret = uv_poll_init(loop, handle, decoy_fd);
+ if (ret == 0) {
+ handle->io_watcher.fd = sock;
+ }
+ close(decoy_fd);
+#else
+ int ret = uv_poll_init(loop, handle, sock);
+#endif
+ if (ret == 0) {
+ ret = uv_poll_start(handle, UV_READABLE, event_fdcallback);
+ }
+ if (ret != 0) {
+ free(handle);
+ format_error(L, "couldn't start event poller");
+ lua_error(L);
+ }
+
+ /* Save callback and timer in registry */
+ lua_newtable(L);
+ lua_pushvalue(L, 2);
+ lua_rawseti(L, -2, 1);
+ lua_pushlightuserdata(L, handle);
+ lua_rawseti(L, -2, 2);
+ int ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ /* Save reference to the timer */
+ handle->data = (void *) (intptr_t)ref;
+ lua_pushinteger(L, ref);
+ return 1;
+}
+
+int lib_event(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "after", event_after },
+ { "recurrent", event_recurrent },
+ { "cancel", event_cancel },
+ { "socket", event_fdwatch },
+ { "reschedule", event_reschedule },
+ { NULL, NULL }
+ };
+
+ register_lib(L, "event", lib);
+ return 1;
+}
+
+static int wrk_resolve(lua_State *L)
+{
+ struct worker_ctx *worker = wrk_luaget(L);
+ if (!worker) {
+ return 0;
+ }
+
+ uint8_t dname[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(dname, lua_tostring(L, 1), sizeof(dname))) {
+ lua_pushstring(L, "invalid qname");
+ lua_error(L);
+ };
+
+ /* Check class and type */
+ uint16_t rrtype = lua_tointeger(L, 2);
+ if (!lua_isnumber(L, 2)) {
+ lua_pushstring(L, "invalid RR type");
+ lua_error(L);
+ }
+
+ uint16_t rrclass = lua_tointeger(L, 3);
+ if (!lua_isnumber(L, 3)) { /* Default class is IN */
+ rrclass = KNOT_CLASS_IN;
+ }
+
+ /* Add query options */
+ const struct kr_qflags *options = lua_topointer(L, 4);
+ if (!options) { /* but we rely on the lua wrapper when dereferencing non-NULL */
+ lua_pushstring(L, "invalid options");
+ lua_error(L);
+ }
+
+ /* Create query packet */
+ knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_EDNS_MAX_UDP_PAYLOAD, NULL);
+ if (!pkt) {
+ lua_pushstring(L, kr_strerror(ENOMEM));
+ lua_error(L);
+ }
+ knot_pkt_put_question(pkt, dname, rrclass, rrtype);
+ knot_wire_set_rd(pkt->wire);
+ knot_wire_set_ad(pkt->wire);
+
+ /* Add OPT RR */
+ pkt->opt_rr = knot_rrset_copy(worker->engine->resolver.opt_rr, NULL);
+ if (!pkt->opt_rr) {
+ knot_pkt_free(pkt);
+ return kr_error(ENOMEM);
+ }
+ if (options->DNSSEC_WANT) {
+ knot_edns_set_do(pkt->opt_rr);
+ }
+
+ if (options->DNSSEC_CD) {
+ knot_wire_set_cd(pkt->wire);
+ }
+
+ /* Create task and start with a first question */
+ struct qr_task *task = worker_resolve_start(worker, pkt, *options);
+ if (!task) {
+ knot_rrset_free(pkt->opt_rr, NULL);
+ knot_pkt_free(pkt);
+ lua_pushstring(L, "couldn't create a resolution request");
+ lua_error(L);
+ }
+
+ /* Add initialisation callback */
+ if (lua_isfunction(L, 5)) {
+ lua_pushvalue(L, 5);
+ lua_pushlightuserdata(L, worker_task_request(task));
+ (void) execute_callback(L, 1);
+ }
+
+ /* Start execution */
+ int ret = worker_resolve_exec(task, pkt);
+ lua_pushboolean(L, ret == 0);
+ knot_rrset_free(pkt->opt_rr, NULL);
+ knot_pkt_free(pkt);
+ return 1;
+}
+
+static inline double getseconds(uv_timeval_t *tv)
+{
+ return (double)tv->tv_sec + 0.000001*((double)tv->tv_usec);
+}
+
+/** Return worker statistics. */
+static int wrk_stats(lua_State *L)
+{
+ struct worker_ctx *worker = wrk_luaget(L);
+ if (!worker) {
+ return 0;
+ }
+ lua_newtable(L);
+ lua_pushnumber(L, worker->stats.concurrent);
+ lua_setfield(L, -2, "concurrent");
+ lua_pushnumber(L, worker->stats.udp);
+ lua_setfield(L, -2, "udp");
+ lua_pushnumber(L, worker->stats.tcp);
+ lua_setfield(L, -2, "tcp");
+ lua_pushnumber(L, worker->stats.tls);
+ lua_setfield(L, -2, "tls");
+ lua_pushnumber(L, worker->stats.ipv6);
+ lua_setfield(L, -2, "ipv6");
+ lua_pushnumber(L, worker->stats.ipv4);
+ lua_setfield(L, -2, "ipv4");
+ lua_pushnumber(L, worker->stats.queries);
+ lua_setfield(L, -2, "queries");
+ lua_pushnumber(L, worker->stats.dropped);
+ lua_setfield(L, -2, "dropped");
+ lua_pushnumber(L, worker->stats.timeout);
+ lua_setfield(L, -2, "timeout");
+ /* Add subset of rusage that represents counters. */
+ uv_rusage_t rusage;
+ if (uv_getrusage(&rusage) == 0) {
+ lua_pushnumber(L, getseconds(&rusage.ru_utime));
+ lua_setfield(L, -2, "usertime");
+ lua_pushnumber(L, getseconds(&rusage.ru_stime));
+ lua_setfield(L, -2, "systime");
+ lua_pushnumber(L, rusage.ru_majflt);
+ lua_setfield(L, -2, "pagefaults");
+ lua_pushnumber(L, rusage.ru_nswap);
+ lua_setfield(L, -2, "swaps");
+ lua_pushnumber(L, rusage.ru_nvcsw + rusage.ru_nivcsw);
+ lua_setfield(L, -2, "csw");
+ }
+ /* Get RSS */
+ size_t rss = 0;
+ if (uv_resident_set_memory(&rss) == 0) {
+ lua_pushnumber(L, rss);
+ lua_setfield(L, -2, "rss");
+ }
+ return 1;
+}
+
+int lib_worker(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "resolve_unwrapped", wrk_resolve },
+ { "stats", wrk_stats },
+ { NULL, NULL }
+ };
+ register_lib(L, "worker", lib);
+ return 1;
+}
diff --git a/daemon/bindings.h b/daemon/bindings.h
new file mode 100644
index 0000000..5540778
--- /dev/null
+++ b/daemon/bindings.h
@@ -0,0 +1,88 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Bindings to engine services, see \a https://www.lua.org/manual/5.2/manual.html#luaL_newlib for the reference.
+ */
+#pragma once
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "daemon/engine.h"
+
+/** @internal Compatibility wrapper for Lua 5.0 - 5.2 */
+#if LUA_VERSION_NUM >= 502
+#define register_lib(L, name, lib) \
+ luaL_newlib((L), (lib))
+#else
+#define lua_rawlen(L, obj) \
+ lua_objlen((L), (obj))
+#define register_lib(L, name, lib) \
+ luaL_openlib((L), (name), (lib), 0)
+#endif
+
+#if !LUA_HAS_SETFUNCS
+/* Adapted from Lua 5.2.0 */
+static inline void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
+ luaL_checkstack(L, nup+1, "too many upvalues");
+ for (; l->name != NULL; l++) { /* fill the table with given functions */
+ int i;
+ lua_pushstring(L, l->name);
+ for (i = 0; i < nup; i++) /* copy upvalues to the top */
+ lua_pushvalue(L, -(nup+1));
+ lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
+ lua_settable(L, -(nup + 3));
+ }
+ lua_pop(L, nup); /* remove upvalues */
+}
+#endif
+
+/**
+ * Load 'modules' package.
+ * @param L scriptable
+ * @return number of packages to load
+ */
+int lib_modules(lua_State *L);
+
+/**
+ * Load 'net' package.
+ * @param L scriptable
+ * @return number of packages to load
+ */
+int lib_net(lua_State *L);
+
+/**
+ * Load 'cache' package.
+ * @param L scriptable
+ * @return number of packages to load
+ */
+int lib_cache(lua_State *L);
+
+/**
+ * Load 'event' package.
+ * @param L scriptable
+ * @return number of packages to load
+ */
+int lib_event(lua_State *L);
+
+/**
+ * Load worker API.
+ * @param L scriptable
+ * @return number of packages to load
+ */
+int lib_worker(lua_State *L);
diff --git a/daemon/cache.test/clear.test.lua b/daemon/cache.test/clear.test.lua
new file mode 100644
index 0000000..473103b
--- /dev/null
+++ b/daemon/cache.test/clear.test.lua
@@ -0,0 +1,211 @@
+-- unload modules which are not related to this test
+if ta_signal_query then
+ modules.unload('ta_signal_query')
+end
+if priming then
+ modules.unload('priming')
+end
+if detect_time_skew then
+ modules.unload('detect_time_skew')
+end
+
+-- test. domain is used by some tests, allow it
+policy.add(policy.suffix(policy.PASS, {todname('test.')}))
+
+cache.size = 2*MB
+-- verbose(true)
+
+-- 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
+assert(#modules.list() > 0)
+-- Self-check timers
+ev = event.recurrent(1 * sec, function () return 1 end)
+event.cancel(ev)
+ev = event.after(0, function () return 1 end)
+
+
+-- Import fake root zone; avoid interference with configured KEYFILE_DEFAULT.
+trust_anchors.keyfile_default = nil
+trust_anchors.add('. IN DS 48409 8 2 3D63A0C25BCE86621DE63636F11B35B908EFE8E9381E0E3E9DEFD89EA952C27D')
+
+local function check_answer(desc, qname, qtype, expected_rcode)
+ qtype_str = kres.tostring.type[qtype]
+ callback = function(pkt)
+ same(pkt:rcode(), expected_rcode,
+ desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str
+ .. ' with rcode ' .. kres.tostring.rcode[expected_rcode])
+
+ ok((pkt:ancount() > 0) == (pkt:rcode() == kres.rcode.NOERROR),
+ desc ..': checking number of answers for ' .. qname .. ' ' .. qtype_str)
+ end
+ resolve(qname, qtype, kres.class.IN, {}, callback)
+end
+
+-- do not attempt to contact outside world, operate only on cache
+net.ipv4 = false
+net.ipv6 = false
+-- do not listen, test is driven by config code
+env.KRESD_NO_LISTEN = true
+
+
+local function import_zone()
+ local import_res = cache.zone_import('testroot.zone')
+ assert(import_res.code == 0)
+ -- beware that import takes at least 100 ms
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+ -- sanity checks - cache must be filled in
+ ok(cache.count() > 0, 'cache is not empty after import')
+ check_answer('root apex is in cache',
+ '.', kres.type.NS, kres.rcode.NOERROR)
+ check_answer('deep subdomain is in cache',
+ 'a.b.subtree1.', kres.type.AAAA, kres.rcode.NOERROR)
+
+end
+
+local function test_exact_match_qtype()
+ nok(cache.clear('a.b.subtree1.', true, kres.type.A)['chunk_limit'],
+ 'single qname+qtype can be cleared at once')
+ check_answer('exact match on qname+qtype must flush RR from cache',
+ 'a.b.subtree1.', kres.type.A, kres.rcode.SERVFAIL)
+ check_answer('exact match on qname+qtype must not affect other RRs on the same node',
+ 'a.b.subtree1.', kres.type.AAAA, kres.rcode.NOERROR)
+ check_answer('exact match on qname must not affect parent',
+ 'b.subtree1.', kres.type.A, kres.rcode.NOERROR)
+end
+
+local function test_exact_match_qname()
+ res = cache.clear('a.b.SubTree1.')
+ is(res.count, 2, 'single qname can be cleared at once')
+ check_answer('exact match on qname must flush all RRs with the same owner from cache',
+ 'a.b.subtree1.', kres.type.AAAA, kres.rcode.SERVFAIL)
+ check_answer('exact match on qname must flush all RRs with the same owner from cache',
+ 'a.b.subtree1.', kres.type.A, kres.rcode.SERVFAIL)
+ check_answer('exact match on qname must flush all RRs with the same owner from cache',
+ 'a.b.subtree1.', kres.type.TXT, kres.rcode.SERVFAIL)
+ -- exact match for negative proofs is not implemented yet
+ --check_answer('exact match on qname must flush negative proofs for owner from cache',
+ -- 'a.b.subtree1.', kres.type.NULL, kres.rcode.SERVFAIL)
+ --check_answer('exact match on qname must not affect parent',
+ -- 'b.subtree1.', kres.type.A, kres.rcode.NOERROR)
+ -- same(cache.clear(), 0, 'full cache clear can be performed')
+ --check_answer('.', kres.type.NS, false)
+
+end
+
+local function test_subtree()
+ res = cache.clear('subtree1.')
+ nok(res.chunk_limit,
+ 'whole positive subtree must be flushed (does not include neg. proofs)')
+ ok(res.not_apex,
+ 'subtree clear below apex must be detected')
+ same(res.subtree, '.', 'detected apex must be returned')
+ check_answer('subtree variant must flush all RRs in subdomains from cache',
+ 'b.subtree1.', kres.type.A, kres.rcode.SERVFAIL)
+ check_answer('subtree variant must flush all RRs in subdomains from cache',
+ 'b.subtree1.', kres.type.TXT, kres.rcode.SERVFAIL)
+ check_answer('subtree variant must flush all RRs in subdomains from cache',
+ 'subtree1.', kres.type.TXT, kres.rcode.SERVFAIL)
+ check_answer('subtree variant must not affect parent',
+ '.', kres.type.NS, kres.rcode.NOERROR)
+ -- same(cache.clear(), 0, 'full cache clear can be performed')
+ --check_answer('.', kres.type.NS, false)
+
+end
+
+local function test_callback()
+ local test_name = '20r.subtree2.'
+ local test_exactname = true
+ local test_rrtype = nil
+ local test_chunksize = 1
+ local test_prev_state = { works = true }
+ local function check_callback(name, exact_name, rr_type, chunk_size, callback, prev_state, errors)
+ is(errors.count, 1, 'callback received correct # of removed records')
+ is(test_name, name, 'callback received subtree name')
+ is(test_exactname, exact_name, 'callback received exact_name')
+ is(test_rrtype, rr_type, 'callback received rr_type')
+ is(test_chunksize, chunk_size, 'callback received chunk_size')
+ is(check_callback, callback, 'callback received reference to itself')
+ is(type(errors), 'table', 'callback received table of errors')
+ same(test_prev_state, prev_state, 'callback received previous state')
+ return 666
+ end
+ same(cache.clear(test_name, test_exactname, test_rrtype, test_chunksize, check_callback, test_prev_state),
+ 666, 'first callback return value is passed to cache.clear() caller')
+ local cnt_before_wait = cache.count()
+ worker.sleep(0.2)
+ is(cnt_before_wait, cache.count(), 'custom callback can stop clearing')
+end
+
+local function test_subtree_limit() -- default limit = 100
+ res = cache.clear('subtree2.', false, nil)
+ ok(res.chunk_limit,
+ 'chunk_size limit must be respected')
+ is(res.count, 100,
+ 'chunk_size limit must match returned count')
+
+ -- callbacks are running in background so we can now wait
+ -- and later verify that everything was removed
+ -- 200 RRs, 100 was removed in first call
+ -- so the rest should be removed in single invocation of callback
+ -- hopefully the machine is not too slow ...
+ worker.sleep(0.1)
+ res = cache.clear('subtree2.', false, nil)
+ is(res.count, 0,
+ 'previous calls + callbacks must have removed everything')
+end
+
+local function test_apex()
+ check_answer('a negative proof is still present in cache',
+ 'aaaaa.b.subtree1.', kres.type.TXT, kres.rcode.NXDOMAIN)
+
+ local prev_count = cache.count()
+ ok(prev_count > 0, 'previous subtree clearing did not remove everything')
+ res = cache.clear('.', false, nil, 10000)
+ is(res.count, prev_count, 'clear on root removed everyting including proofs')
+ check_answer('exact match on qname must flush negative proofs for owner from cache',
+ 'a.b.subtree1.', kres.type.NULL, kres.rcode.SERVFAIL)
+end
+
+local function test_root()
+ check_answer('root apex is still in cache',
+ '.', kres.type.NS, kres.rcode.NOERROR)
+ res = cache.clear('.', true)
+ check_answer('root apex is in no longer cache',
+ '.', kres.type.NS, kres.rcode.SERVFAIL)
+ check_answer('some other item is still in cache',
+ '16r.subtree2.', kres.type.A, kres.rcode.NOERROR)
+
+ local prev_count = cache.count()
+ res = cache.clear('.')
+ is(res.count, prev_count, 'full clear reports correct number of entries')
+ is(cache.count(), 0, 'clearing root clears everything')
+end
+
+local function test_complete_flush()
+ local prev_count = cache.count()
+ res = cache.clear()
+ is(res.count, prev_count, 'full clear reports correct number of entries')
+ is(cache.count(), 0, 'cache is empty after full clear')
+end
+
+return {
+ import_zone,
+ test_exact_match_qtype,
+ test_exact_match_qname,
+ test_callback,
+ import_zone,
+ test_subtree,
+ test_subtree_limit,
+ test_apex,
+ import_zone,
+ test_root,
+ import_zone,
+ test_complete_flush,
+}
diff --git a/daemon/cache.test/testroot.zone b/daemon/cache.test/testroot.zone
new file mode 100644
index 0000000..51814d5
--- /dev/null
+++ b/daemon/cache.test/testroot.zone
@@ -0,0 +1,1256 @@
+; File written on Wed Aug 15 09:23:14 2018
+; dnssec_signzone version 9.13.0-dev
+. 86400 IN SOA rootns. you.test. 2017071101 1800 900 604800 86400
+. 86400 IN RRSIG SOA 8 0 86400 20460416024207 20180815062314 48409 . r3pIfvAMPJ8eHGU/OLKUCCRU2+u+1ah7fably80gtRVEgLeb207jQEAW MbNlTFJhUIomov3+ERdPAOZ9Kw0+k4d856sfMUUtgdX7BL9Zo0fIcpTu 7/ek7EELfKXb/vYfFlIP1lEOUvo7MB/YDo1zljPJ1Qh1BDsp5zyNaTgq O0PQg/nN1UmFXwjEVGmsn6uid6cJfxdO7UaluZ5c/alvOIx3tBAoDJ2j a4He6Rjlc63pEsKBYmAHz2Rdxq5d/+kqVFJUTyVVvMJ35apczpZ4S4gl BPJQZz15hB3OcpPt7GY1TwI83FjGXFaGiuTUqhvSIRoFimRPVOIGSkJX edKPBQ==
+; resign=20460416024207
+. 86400 IN NS rootns.
+. 86400 IN RRSIG NS 8 0 86400 20460416024207 20180815062314 48409 . lAwwTOgQuFowdXma1vVrHKz7qi4vaighh104/2vl6DlEpAFEEOz8y5Y3 9bjWfaNp+UDq0AyqnZ3ow7VN9/Hm8uUbicu2BrNSDhGQ/F66rvw8wZff eE5+w3Ihgm4/vK8zmxUnxRz5mwcbbuCyyTP13WQJ8N89wFgsgezkM2E8 s0qKw5ZyriPopd9X2uLFS1vapezFSSo6AN0khBdrUYWzgCRbE1zLHmKV AW1nWMcgV5zJNX1/9dxgC+yEM1nYhcN1Nhl7neO23pRk/vZ26SruzPdY s5j+fn7WyV0cQqAb7BJ7aTFYuD3kG1s3QUxjQolcCUjK7fCCJeYPINdK m2dLtw==
+; resign=20460416024207
+. 86400 IN NSEC rootns. NS SOA RRSIG NSEC DNSKEY
+. 86400 IN RRSIG NSEC 8 0 86400 20460416024207 20180815062314 48409 . aLdes4mtohdcKA7/kyOdDrCUA72c7DRuK86yxOl3p+5mDarjzBw2Q02b nXEgWoY2RdMJ+KlkxcU87Ojl/p3sIF9RwHlzvW09iXznPyLVTPwD/mnq /5onoMPW4dLbwVIIrFgjhuF/YfN+XCyKoKIJQgB2Qdoo4ppWqeUZXOgU byoPWpzuBupMmrwjownmJLO3bUFUHDaNkIXFi3KwdvhdYj28bP4Z9P4A 1RAKdmU4C24f0auTJDIVDFj4v0ENXzpvDKJyX/VOyLR5+EXwp4YtO1vl TjWueCrTREYDZzXYJgtYHvMDVNQ1uZ+6YCLE7wrgCTQYne9uNp6eAZH3 oFIPTw==
+; resign=20460416024207
+. 1814400 IN DNSKEY 257 3 8 AwEAAcliJP8Jh/RjL3c8eaUj8dzVdEksENKubqVA5FdrDJ2rC0O/bGG/ MVZt+WacE1o1mRVwTT/TrhhZUAzZ+qOcpB+IWxURsR4vVqVwakHMny7D 2aLXKoVXwTo/VhAQtHDw5G9bxGgwybPUtd5Vz6EIenUsmNYZ+Spde4l8 vpw7UISVL6q0C1mwHMN18P/1yfHmbkS19b6B1S9Y2aputccF1lso3yiF Ig7UNqqD4PNxSo4jByDnajQSP3qg/LSJSOnzBIumb8wc6svxgugy/pxr BFKgGGk4/JdJCKufdfU5jFX4fJ3HM37G/RccrtGhIf2Z1utoOyaILoa9 wT3O1WaYG/U=
+. 1814400 IN RRSIG DNSKEY 8 0 1814400 20460416024207 20180815062314 48409 . MiiRlBAgMnadkm6Y/IDLGiE9Y9xvGU0u9IYD1cAddUMpYAwGe5584lfj 1L/Pdg+OeJiGDfFSD7m3ppX18wIeo5eWHmdCO1/nEAdPl0u/XDB/avtj o3Q34szjOV/sA3s5a3Kc+DFrBiZiTA5BwMIHZX4vvITf2cEq2K4K39Iv Cg1blSathklWkpgYIPttpZIcIt5vxoe9e0aqar6FLOCWJ68noreCIenR XmzhMW+l7kOBoKOvkyv1lcDM0zWXI9r5lqAqJB0mWsCiR+VloxSbFii7 kjUDju8BqXu2G8YzM7y0vMytMafjfLsj7bHvQ1HefOht6Xzi1jRHblEh GGQ8UA==
+; resign=20460416024207
+100r.subtree2. 86400 IN A 192.0.2.1
+100r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . QMB4uw2u+kJ4Iic6G6fJ1B+LL5Jp0y/tPHwb9iW8mfAfZoaHuNxUFMdW nncNjZcjIXpeWiWcD3fzKr6DwJ20oWMpt9fxGthYyXp5DWhmeb6Pq6sS cWy5xhGEP6Cv1gBx4oZs0xmdmLsi/eK8x0E6DScwGeLu9v/lUGxnD7ug b4gl/9ABXhFVXW0qALF48dTpc5mMDsAIkPJevarbLDrg3CyOTrCcZtc1 v9DYSN2KGG7l3H7jAY9WNpwhTGXVNthSGHLvSUmzJ418ya8bU9L0Baoj +WBmdERcmFyx4KS+mwDcJxORwLPtEpRN1Kdg/X/z41cw/ViakoZ62ikq r2j8YA==
+; resign=20460416024207
+100r.subtree2. 86400 IN NSEC 101r.subtree2. A RRSIG NSEC
+100r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . EK+71G5XMyRg37UmfcnMxVl6RbkzUL5YLi+kL8LXYXrq5AEXCof2K26i EPVZlQqjaSSyCHDV1PGpUAbxmGu5JFJ17wksgJ5q+vkPMTJhU9gVQUGf 1Ymj8hOa+eYs1Of0/3O2mf5V5KOpQ/R/cONQelziCLQeJJvJ70AHJUx/ 3+guSkXehkZszxuVozy7AmguyRjAWtNOLmVR74HFaP5+PyosZhie7LZS rsGq0p+VzHc1Xl9BXLBg/WrBXEpcuEtvg1wc/gVkNAlC4NLr1zbWcmIw Hdf3/NFgfFkJ0gFRX7P3+zXCipUHjdpb/kxtFCopYlZpP5km0cOmrbq5 rPwwcw==
+; resign=20460416024207
+rootns. 86400 IN A 198.41.0.4
+rootns. 86400 IN RRSIG A 8 1 86400 20460416024207 20180815062314 48409 . wvg4r9R9qeCwgxxb+Hrit/Ag63lSAzB8SPyKAz61GHojJbES8sz1dvnN fpaFX6bd+8oYfLKK2m+xyNITXm7mzZU2lEg6eHih4E/PCQzEfi12VaQq Esyg46LKIYCMcazou0I3ot/BbXHokSlAnfyAA+2+7EKSFK6SZDVQwK5Q Y0w3ps+gevcrnSHSQuymyjkOUgAxGtGTEA/QvmfzS7f7Dc0vrTRHRmOU 0lJ6Epi8kajwMjtEkRWN0TUnwD2z1eNyaCaa+C9TTwIKGoQkZlMFoc/m 4hBCPDEA1/Z5qDVFnFdWNOE3CLon/P14ONTznSJr8lfqdPmRb4iXqIn3 yNRLaQ==
+; resign=20460416024207
+rootns. 86400 IN NSEC subtree1. A RRSIG NSEC
+rootns. 86400 IN RRSIG NSEC 8 1 86400 20460416024207 20180815062314 48409 . Kv4r9Utt5+TrzUuzXedmlJlJsjai7Ebs8Ldj2TfV7uktkOx1GTCTMIFs jmWBJDi/tm2CRP8VvNL6tSmxOQ2sNRXbyNmbgY+WujCuIA1hTiYC8cR5 p17K0MuR6LDu7UfcHVUAIiiucqQ+FHgnDN6DPyeGPMEqiRUG6KFtmQdz VcVHN9EeVwvru6l7M0NSg4onNGDUAd3Lzb4OVSdjcfCC5/TRxN/A04pm xIbag7nBTmx5T7/RRhywKycnbaPCmljmGtwd0+ikdNtIkQZa2/S82rOb 579vgx2cliVQszNmqei3PVRSHDoqTv26lHM27fTAkwdCGX2rBMDRLjQ3 Dk/etQ==
+; resign=20460416024207
+102r.subtree2. 86400 IN A 192.0.2.1
+102r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . AJoiUtB4aQsllEPMM4jYB8B5ZSJA3nAZnICi0lxmpJzhYUVnOKdj6B50 1U2TADwJ1SHs9tciZAuJvILJMwu1LapISmZ9692u7CR2gcTfifnY8iny +cZwEsFFbwdv/pHMIZhox+h2+H18ltSpa8Sad8vvnqtaePtUgnJdR3kp 2b5q77VrSxEwizLq6OkjCYiCPh7YrbfNfTesZwIbuKH80TSaJF9kTVar hh5yW23vEwq0tGyrYn4bYJR2OzZqukLHz+aSMIQ7BpQPHY0hMWPndb5p HLvaDHbmp7Mc/1bnhCVmqqECKouV/Y8omUUHOljyjZ8WlCol0pYWQm6L O/U4SQ==
+; resign=20460416024207
+102r.subtree2. 86400 IN NSEC 103r.subtree2. A RRSIG NSEC
+102r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . PE/1zyqvetDmu+d7yFhICfmNWl1AmSZ1K5tKtb04wz+I1aihwWkozCuC hLskRAIrhogNSFDjVqhM2F1HFYm3ACkr3vvWxsTAk4hnvIIw1TVj+iqU ZTpXNKnS7UarPxM8a5fctME2mgPSQnsAzJRlkInpT450Ls7qhTpqEREx wMAY7fQx+Y5zdg90rEINJBdKJgw7K8ES3bm1JwEIpkjw4I5NZhyAS026 2fW6x5UaTGWhV1Qa7YVwdXrEDxcsyb/FR/N0AofcP0JrQ8/UfPJNoO3U rtUigRA8tDapiQuSTFaYb1thtqMgGtrJqxjDRU82I75HBZnhhFilOGUD fcVq9g==
+; resign=20460416024207
+104r.subtree2. 86400 IN A 192.0.2.1
+104r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . lNhsi5Dvb6lFrzp8+A3j5DDY+sK8p+CbB9jRtxRoCh9TJsWs3V8tWfcJ qLN7PKgCchmLTfQryKsIERDBNgEvaFbygk9UO/YX4z0J5xVtXQ6lRz12 rcgR0EJ098ZoOtmHa25+YILXwwO9WqoBzx5VItYduOaRXqDJA6QcUUyx cnd14DkfJcD/MXKMkWS95SIIOg5KuOga0N6H7ATIxkrTCbqRfLwoj0Ne 52Mp4LSyVY9BN51s/jHmYOYiubLMtGYKVVffkmH1LtqUnY/kNK0YixT8 4zs1REuk6e3PAiyBOANvMqVm76FiBapaZgFnGJu/2S7foTC0yHfmLh5J NFTq8Q==
+; resign=20460416024207
+104r.subtree2. 86400 IN NSEC 105r.subtree2. A RRSIG NSEC
+104r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . BrGAn5NmHS74Ru+CcjBzjUamO6en8GaTg+fOuSaECpFNCx/rnFQziS/a gZs44Mlc3IdXLEeo3GI21Uqf/mNeGqfl+TCnUfnevy+1Gl2093dtma4/ LNHMd9BxDmXe94WQVp4xJOLbICv4aaKIhGD7w4ck9cMk1r8p4ngvsvoV kn9uLl1lx14yddufdS8B/NGvzeqRlJNdyfZJg47pQfghgg+qLbmmzqHr Rt+ke0Vge4Nur3PsNs/daYb02OWqWA5YLbljRaKqatEn95ohkrfl/CMe rZ3b3gcR6dowj9+dIkGODCQhrzqtk3v1QAEkYq5LJDPPnhXYduNWqsz/ 5F/m/g==
+; resign=20460416024207
+a.b.subtree1. 86400 IN A 192.0.2.3
+a.b.subtree1. 86400 IN RRSIG A 8 3 86400 20460416024207 20180815062314 48409 . BTJSG6mn5eRo2GDyw8lT3S7POU6BBrB2w3ULx8+BY8aoEHzak8Ul/abD fqneEji+subHFGpMwQXFtzKfnwTTRHyHS2P7FZQO3l7b+T2oDRZn9B0d mrmGQRmv5XrTDlO0WRYlRsWSMalkbUEul6L9q3koCCtXd4GCxDh20tk7 o27pKtuO0ALptDLIMXoVdhRhTBd+qARVJrg2u/XyUzlwqfgZTsY6SkyN mEYvK4UCM1RkhqCqo3NBNmR7islmWVPM3Ax8uTssCf5T+MUN+gNT6Co+ zRXX71qGw6hpQFxWe1dA9PCna4ZoWPIfx6A666blMrciwbYTQL8xUEPS 5RmRtw==
+; resign=20460416024207
+a.b.subtree1. 86400 IN TXT "txt exists"
+a.b.subtree1. 86400 IN RRSIG TXT 8 3 86400 20460416024207 20180815062314 48409 . gN77jXi+ntj3SeXk2WprymZGcwPEXY/0w1svtsP2RXijvXwOlWlpmsSK +SnZFtkSRKwJXEY1nSDW3UORaDzzZrraMxCtjtt2Vi77Uqi97PdaRmk+ +IRcJBNQ3PywGRdqw1MZuPlyKbmGe8B4PE96d6ya4fXtvL8tuMU4L7uP h2Wn4sB1eSFpH5rGrytPMyv6W4T7JubL6+R1YdmQBAARJXnIqN8WlOYM JiZtQl7kdnPOxxhHZCzlFOP1Ci934u9jh93ynJPbF9dJG75f74+sWw2k RHZUu7SkphoPsVR7nhulAHcPB805yxWPtmMC2Rsq+RGbLpfgFENPsnxv VZa5Wg==
+; resign=20460416024207
+a.b.subtree1. 86400 IN AAAA 2001:db8::
+a.b.subtree1. 86400 IN RRSIG AAAA 8 3 86400 20460416024207 20180815062314 48409 . cLipbUelm96/75NoXOJR3atJKr9fjYaz4lu4CQBREmQ7eUwkGfDGW7jj ar60VA2OSVT1Q3SxsvKjZ9OjSDLtPbhVeIabBKhDQWULy3c0rBgblMfc xIa++QItLiuxyfj3EiwGfwkIlwuM0jg4sByisvWBeUwsIv9+nHJYZERf p96DhsZXC4OTba5Tai2sJ8CmYINWQBu8GRb9wrtrEBGg2LyZifXRirTB RoTuXPOSScURU4VmzyJrVLuNipsd9V6Dnq6J4Yuc21ws9ZrB7nF1TYqr zmW1x86umJHM1Evw6NWrHX4rMgiTiwJR0ju4uc7FDZ8irdtmcPGr0C9F 3hycmg==
+; resign=20460416024207
+a.b.subtree1. 86400 IN NSEC 100r.subtree2. A TXT AAAA RRSIG NSEC
+a.b.subtree1. 86400 IN RRSIG NSEC 8 3 86400 20460416024207 20180815062314 48409 . ipRQluXZcAsYQqTMnV6VDykJym1bh4VujIFvWnM+oQxZEpsreqwl3hFM INUapADed66F8p7goYlC9oKdbOfqLAJSBvPyL2MJIPEifnTeFI5SiQe7 u2NxsNn1glotVBHGL37EOf55xvbO9N/y8fnIjtOD4g8kJBthaeyOCsc+ KbcOcBce9y9f/meoibQc9plyQSeGhXYtc/9jliNmnJehG+y4WUZpmeOc sy0NB3VDHMwNigxUJl4+ezSGlR7TLReeGXX7BGBSdtDigz8uDpi5vmXk Ebmxjr1gSVh++5cZV2l6IhA6l6/ZNWh8EnYWnydHZR/FdMZ/xnJP9fdK bN5Kcg==
+; resign=20460416024207
+105r.subtree2. 86400 IN A 192.0.2.1
+105r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FBlsr7zNihecaykcv61PL0Huh8SECrXoZ/unuWxmT+ZqDKjmfFGxoG/v MgmmJ9r/sfZ29e9mMnfBtXj/xJLW1dbHYwFZSt4ZeGZ+W/ohgCKVW0Fb XJTZacoWJtXhgObs6gjoab5IPqle3dgqUsP6xNvzonReL1IW7+45QRh2 nWU+4j5OTL/eO1mNXU841PtvmmotwDamR6aSoNr3X99Hwd3/3yJtRIIW JYVAr5G49m+YickDVH2gvJ+JZkswzGfbQgXqThUiSMBfHw23LQk7A1ZH aStSqsRcJt/w1pTl5cEDpnq2yFelQdmCE1DqnUIOkSTRdemk4ObnXFOW FArXZw==
+; resign=20460416024207
+105r.subtree2. 86400 IN NSEC 106r.subtree2. A RRSIG NSEC
+105r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . uSMqXmIDnP/m7EW1gMGI7N8Wp6x142d8xTtJTFJ4mOPbyrKBRDDdzEEQ iiistwQThYUZkM6gWpiE0yfTvUI1pP4Aa6zASUExJoO0P+CEmHTmRJ9c 2sCqottCOirXivxfC2PoxXlIZ1nQketizjzMXErjKogU4/HaaaxiDLXh P56bAeFkIqGKujbdRhVMArugKvbbkjVjE1iXznG/bzKqOXldi7JNQbxx cc6+fZTs4Ahg2H+FtQLH+WFbD+JBLQzvc2WnjqvNq5MGTSKxcuGDA80O CLEFs/7zhEBSZYhBGoVXqywvDR4j4Lox02g5wUpWlXK10pnU0ppr6Asx bM4wLA==
+; resign=20460416024207
+106r.subtree2. 86400 IN A 192.0.2.1
+106r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . kIpw+6J1f7ByzuP9ynrP4bmQTZaXCF5CjRgDLWoIcU+mnqidsWwjZRHb 1q8OtXcUUEfwTNc6mqvUnWHXQHARTIjZ/xtQeAwfpq8ZGjmplXykmlDX Fl3p4cleG3QM+uhHnZRncbVSEXnOA8H9PFHpDSi9bBVLmqEHOot0evxA 4wfUkGQLGpa2MxzAXymWO6A1RdntuL+gBbRYgGlR0RnZycMMLtRNv+ou QhFEuPw/apF+fqmV82dX+XL8kEWPflDhOBbZqMO53sKFj5MG+gXBiPid G5VFgyFqsc4atV/PMk5RvQ+jRE8PgC2lWmdmnOL9D/RT0O4FjrXbq+4/ LEuBWg==
+; resign=20460416024207
+106r.subtree2. 86400 IN NSEC 107r.subtree2. A RRSIG NSEC
+106r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . OnRo2W1aBYVqzLQsgUF2zisHiaGnlbp1DIC3bcReDq3hKe1EqPtqffho 3UcvnzVOIp31qTjr1ntoWhNt8mOJMDzn2/UUSPQkAj8Laj1lOtSnLzm/ b8c1eFb45LTsZHic4DZoEwlhtwV3EmwKJWXMkDQZdV5x+g2RGGZ2W30t tCZllImuJbHLyCB7Mwy0ipGrVQ9iO2uCITURICTiDGKwyIox0Dr/0la6 7ZtknDiqG27PC4me5AASkGj3DaLo8ZmlBudYDG4sh/B+tryvdrhizgcK XoIrLqNjB18d9+CgKKVmRLZr6yY9aw7XSlP8euPJmQZv20CyCxhG5S+e dpCdAw==
+; resign=20460416024207
+107r.subtree2. 86400 IN A 192.0.2.1
+107r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . UyerZwNc/ZaXNSvYbxxB4CpWkGVntXXrSwtyqIeBY7LUSW1738rmg1Bj V66avt0M2HrlhDJioLvHCjI0xwKxaoLEzDcC3g6sqvvDW/42KKzMVAwy 3ARu0Fm4HNlbjuaqsC1h0WqvIjJBMHc+pK5etjSRJ1WqAq4YK7fh+DE+ Bb5NMXyqWTFe0aW2nia8v2oZQ5QlRICXBQ8pvvyiOUmiv+CuNHYOTJ5N kcvekf05R6QVjTAR7cIUn3/87V8psWcHuuV+qgTJ3eaQ1EWvuO+B6rOH eYLsvHfCi0EoE2+uNtloBTL1H8URJ8E5+XMMSoGEXFdq9A4M0JvhMfcZ EuCshw==
+; resign=20460416024207
+107r.subtree2. 86400 IN NSEC 108r.subtree2. A RRSIG NSEC
+107r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . B0lyCGkGFKCwmYXdbTUyanhQP1qlQltuiF8fcMyPVtlUcGdML69XWZ5s PqBiuEZ13avpCL8HTQckuTcR274hxGBsP0vcMGIwHJ8O5BqhN1Rp+0SQ aC+MFTsH4Q3DenHv5hkaS3FmdbEN0hQU3PSTvjTc6X1HSz311Qcc0EVc +dcnxa0F6nCwpuNU/cuVQ1Vo7iA/4iXiGDFnek4udqGAbxK3MuAYNqe4 YtQlnA2hOTrJ7nDe+n2Id1jPcRf0SRT08YM2hUrkMjiFi+eW3kD3jSIR 6O/Rmz8QpruQBsf3xPfv5zFeam6el6rca12lcSoFFWXq0WakDF3Lkqgr LF9WSg==
+; resign=20460416024207
+108r.subtree2. 86400 IN A 192.0.2.1
+108r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Odwq4YxK4F2S2jUo8vMOFVoHOCsC2CdHRhdR2Z6WiGpk3CY4qD4uIAzy JW6vpL6fKgHavZHfg/YA1vyxvJ96lMx4RiHkoQhoeugyQ4nllh8Hrp4S IJEyjNq0OqtO9QjLBSMHvrEIwVVfN/sQHainyoLquY7bMQdJvm99fN5l W+pmxMlLVG0n6M2UA/o006q7AXorgUsVMot5lyZ6TCq/YFZyAkIYZdJC zaVS+WYcUOMRQqfOmWhameS4VjOSfBjPjdO7tIgZ/0N3YsSm2mwaDFYD rua6BIoriiXUurzPZIfaI2eo7JGPB+NM3cOZDDf9NIFY8xt5SDBUz5Mz ye/veg==
+; resign=20460416024207
+108r.subtree2. 86400 IN NSEC 109r.subtree2. A RRSIG NSEC
+108r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . mHx6mvjFhd/o6RvTuRmobVp6p8PC21Byq/Bah8RJnZ89E3uzgnw75PK1 Kv3UfGw33xK53ujjq2A3R5ik6ozJQTIF7CRQ3g8va3yKrCttyff+dckT qBtXpaDkZlm1JkgH/H1AQ6z+4l9GlLOIcrbfwv6ypAgxRME3lcOpHM/o xyIuIdHh1GntYzeyNWhsB7SOzpqAePNh0RVLyPslPGhm7BFGTv2MJbBp BayfaWNCgn6VISbj/9K33GCZMbbh0Usj7K3HIKHoVtyoNP/izDnNCXDJ 8cNXN1SrRl/9jIOw2Z7mIt0dzxvC4Ts3SNQBdgHd4JuZV+aasHt49+MS 0JdlTw==
+; resign=20460416024207
+109r.subtree2. 86400 IN A 192.0.2.1
+109r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . eI2rg6VV++ck7Ei4g/K4XH7CEKliDx4+a9xyYPDHpGZJ1FhKjI5wcJNf li4IQXP84I15EKHSyiA4CVn0oZ6GTGuJtEZWkOXndPzVTmRW0vyk1tm0 oVaYcBNfVvYd45qLYMJLTwWD0cooq/qHoFTofqKsqgW8Jxe+ziZnhqDH otRi/B6OZMmqG9ZPkAKWmC6eHyBSzHzBRZ4U5fSExkdzeqcMA9+sNkNO c1koeZKUFpbzIFNyFu0erZWaZuxgBlpkyCX5TvCNu5gxKHOMy9GsKymJ OCpg73ltfjAh057odKJd+5cZBt2sqgHTipPdK8OySn4l4H4ZiWt69SmG uoVlZw==
+; resign=20460416024207
+109r.subtree2. 86400 IN NSEC 10r.subtree2. A RRSIG NSEC
+109r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Y3Dz809IO18mcCEk73sQJdCJteOpR+bH77aRQfSD9pXz6xwCmlcdnqe3 zvLJeBdRrN+wbn8TKleUIoPLQZcX+/K8eVrcrHtn+Myit3ojby3WBkqK GwMEy95fxFA+yB8EWqsVPyEFgcYLiawf5Y+d4CRp1YBF5C1iA2SYDEfT lzDQ24IeI2hKsUJSxBGzhzn334ghrwySQ/8XMqVBa0Az4VCcJL9Or/ZN a7+lw9v4afZtbMlaYNQ8V65u2oLaRXPfiY4gC8j0MiUxgdpmbFmB6x9P URY/jLmhclShe2VzRiHyir2Whcf2lRjs/o8Sp2HOqCC2K3XeG4B/6+X9 oTAYYQ==
+; resign=20460416024207
+10r.subtree2. 86400 IN A 192.0.2.1
+10r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . iAfz6tNv3K8iEbFfVH2M+Oj+tWLA0JK/yBWB4mERkTZaJW4Z5I5vBCYo 1TW5wj9b7QcDvMYGtgHwQ4UVZ2QZB4liQnazhqyV7OYr48yif5q6INFy NP6l9ZVh1axfw8dUmorOA9cEZQgI7PJkRckspp4uLg6NmQaaY9Z5NvvQ cRW+2zFITLKrZbtwHGYqHIpB+kdakBvPXemZfWBrMQ3VcOO//cXX/O3Z B4w1AsX3odnHQbLMGjs3tJc/cs5ClFoZxtOS9rNPJHvMs5YFyZz8O2cF jfIQwZcmIA+iuEkNP3N4zLJkzqRBnHXSWmSAIagvCrdfK7c0MBZa4jpr kVZ+9g==
+; resign=20460416024207
+10r.subtree2. 86400 IN NSEC 110r.subtree2. A RRSIG NSEC
+10r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . QEFhA6SbI156Bv/lxec5Og9S7gwCeTKzlToFV7UThfHykz3FA6GjemA6 3tsgTi0OzEWtEzK03a0ATPiwuleqcO4iY1m5jwh0gReKT0iDiCdVEMHL WQzL5eKXOu69G7qPxust4p51PlDqH51HhqTHWSiBIoIn5Keus7xdFxMQ IwGiyBIrAyIUZtO48FvZJLI3hNn3ChZJjVRZUmiK28Z1vkn3vYPbPalg VmMWgb+vGiHJPj/RuAfeMZ00keids2ICoV5YgYokzyO3hhr9kfF/sYQj Osnp9b3B4EC3Qm9bzTzt9CBcIAuvqQUb1+vrGssNDuAtVX68WBH/iL6C dPUpMg==
+; resign=20460416024207
+110r.subtree2. 86400 IN A 192.0.2.1
+110r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Ou9f5KbvjA77eB+iAQ3OPRHwcUQrNfmR+H0RF6zInwO6fQbSP9q2jWMU 0l8Zt7GZN3rgoUpchhZBBbgGckw5qpLa+IiRBGphxeXmfeLOFMeUOTy+ Ge2yRz6Z+LhTWKUVk/vgEBxklx/OEmsM7/y+Bkiyt74+s17KLM70uKb7 AA/URH5lYCS638z7AZgAbxqblAlWqqz0WH4IB2yINUZxG3C9tZS+xByN Rc0+jL1owXuJkrNMjONcMRrPAfjTKYlZFIFoGs0BwahwTRNWlPSw/HsN X8FCtpkvL1w+3geku7eMUwokfHvJvvKBFE7c4WkL8dCxaBCbHXYDquNy AWrVSA==
+; resign=20460416024207
+110r.subtree2. 86400 IN NSEC 111r.subtree2. A RRSIG NSEC
+110r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . wtaj215QzpzCt3LTtSZaXxUo8wJmWo8tcjhsB5/f6VkZnxGJDZnqCAbt r4xsIMT/RUfwMX28v5qNonI2QHNoBmgOpsdiV6ZWuwpgLYrBpRkOpLDM XO3XH/5kdbQJXaRHKBRdHoHo7J1suISafFwxBgm6I+cTEIeJA5aZ6JOq r06Jb8GcqzW0MgqxRVuHcgv1n4/ZqCmJlJW9ZzCoC96lbXCmWpAs0zFz UM0IYUdyl0w/VqRIHetQlB/hf7tof4deq/rPyatxcYWPkiYEK6qmDEzA hHRWB4oFYegcQOYYuhj/gWGN1bF9UEVsPSQhHrFJpsMSQkhxd6lV/t5J oXBFNQ==
+; resign=20460416024207
+111r.subtree2. 86400 IN A 192.0.2.1
+111r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Kvj8eksZMrSLThU8yas0K/Ep+k0LEZygkg9Nw/cvTpK3gKlDV0wXGtMM xEa6y+VnVmELbZg5205/haxo+diQ1+NDpNYD6GY/zgBQkIUT3MeHGdMG L4WHwZhqg6vHVz6XtQvNOkwq5IIvwZkOTnMmUwx1H3kZOO7HyazxmkY4 0ak9ggXRDBTvydarg27CX1UvAh3AI67/Jsy3LchaD0TUPtKIEK66MkkK ja8HXxAY2qiI4pc97ZvZOOjAh2Kj29AS58ben+pdY6B7d2Ea+04jeyCQ SbFP1YQGGzpI2/m39y+pcBJQIDa9aTtcL9/W7sn+pa8NTettHcpGfGzT Hww0Fw==
+; resign=20460416024207
+111r.subtree2. 86400 IN NSEC 112r.subtree2. A RRSIG NSEC
+111r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . w9XbWXOIjG9K+uiCTpq04S5a0ZXPK5eAQ9wZ9HWhBlNwLD9k0P+4Uami R3NoHd/ns/SqCdhQpbrR1SYnzhT/hezRzC/ahtipvsc20OCoqn5Cpmcx Kmh30VW6mxUVtV9KNumoM80FQq3jIu/q3r9I8oDG23TAXOPDc9K97Qxf 0Kt2RQgh0wYYCXfL/rlv9zUn/H73Ldj4N0gioOFwvU0SXbJVUfPG8UiO HV+BtY382maAOGMR9gRLHVX0TIKD07QIKcnkUugLRhLlgyJ1Knzetrbk O119Ti2FBOqVhvjEcm8v28bvo9XKuBTR2Guk9AzsXJQGC0juE6JJqiH2 XtXlLg==
+; resign=20460416024207
+112r.subtree2. 86400 IN A 192.0.2.1
+112r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FvJNCOO1H1K0KHlXwdwyaFSQNPdUG0D76yZkyQMhOm7wxjC84dq6X5yi g1vNXp/vvovpRgLeJPuQqg+7O2MekQUTwNQSbzehtOnY2JoPXbGmllio +hqqk2gpqcj7bnIZpN9PkjcqM/EOL+TpmizPi1LRcVl9EHfsfQdIRUBj cMdfveDjOuyV8iWtNLwPUG0bP/gqoOPwkEJIoas7yMbQTiSjAZtV701Y 0gAoM6Kc9tINYlmydqvallSow3K9DE0otRVwaR0IFF0o7uSjWo76sYsf L6UyqK1/WDMkh6gS//IT4ZvwfcXbKi/WrFS4/AurH3XyTY2D1rvxZi/B K2UbGw==
+; resign=20460416024207
+112r.subtree2. 86400 IN NSEC 113r.subtree2. A RRSIG NSEC
+112r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . UDQGBNS8u+0VJLat16g21VS795ucpkMD/GucIPErSyF5zTiSke8jThPA TllPS+Ynx6hcljlUnye1ARb5pqcp1gV9DN2L6AFgQfSJ/gCtUe8Aanwd ajuQtENFgMPy94nkjEYteLvUJNP0x9mmnt0Y+Keyaehwz7ppI+1eJUfs qZvZMD+i/o2lKEb4KRo52ju8MGcMXOWmgcgDqwPf5EOH3tG89fVOE90J gMS+CQbB0f97fDGWea7lAou+V9HmIqxtQkUHguxwcxYot2rx3GKw+ACr dNY4hm04vseoULJm8MVoy4cEh19DGWmxOgCMW59B8EQd1+h81FDfszbx k2NdqQ==
+; resign=20460416024207
+113r.subtree2. 86400 IN A 192.0.2.1
+113r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . lMLx5WcSLY887gnXvV7zwS5GzmPas9qF7q6GI+9KOdJFcQnbRsmMZlXP 24p6Fz3qcXpH6aMKg7y5HuGxwcWfIMY5WBbdl7rJ8r4nu7UzqOXVQDIC z9Q0EVL7ZYUynzUCGlg1Twb+IUbNcCktySwbIXyt60yX87D0JX0xa0UG P4vlUz602aymQi2J2OCt9jDMjGoopvdy6D2dpr2rygeajpgPrn2XKbqO sdG4GLEnjwSFRWVgnajRM6D7ARaVp8HNElOwdQz4x97AoqtlmrE9XB1b CV8m45n17X4xXAlS8ibPPrs63Q8WLBAXPGiq4xjAubqCkWRJOaKsud7O Ovl6fg==
+; resign=20460416024207
+113r.subtree2. 86400 IN NSEC 114r.subtree2. A RRSIG NSEC
+113r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . XSQUXBwDasTlUvwGR7qmtItAd8IN2t5PEkp5WwyoakU2UerAOAlvWew5 jajLTb4P5g6hyqqeub+undk84ZgG5hrGK8qK8rf8EhjkPaQl/svow3eh x1tM5sL+CpKnG2CgEKmHdVEjSS5sJVOTE7qUJ1HG2+HYxaMkuNdANNuQ cAuLz/y0YPZ41c1ii10cH80+K/zP8NipEz8EJvDzU+r0NNghiFJI1leX wUeUNQ90svZjZ5XujNk2Q9lvoTI0xV0gIIdpTooTC3pDmAp7JbxXLAnV /IRHHgfX5/6SDpqtQUcRcIIAsDgEut4lktzYHmarfHVHZqeuNIardgt+ f9N09w==
+; resign=20460416024207
+114r.subtree2. 86400 IN A 192.0.2.1
+114r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . kmW55IQ6ZIl8D0E+Jh/ANLu3YzE0MgZPy51DVXHSjQBbNRZc9pGAUjA7 5yAZyPlySl3WOZaMDmVSquqBt6TG3mNo3nyweUKGupcNU/AaAZlKfR4T aloJ5oB60sDPPoMZtyet9dnrncNmfUJSiWackRcNPbgzySDmxbc55XWu 3du14PY6EaG6cgxmebFmOneffmKH26HyyA22Bum1qogLQVUGzidYgxQB 3M2GgGty/mHWp+dZpRoqh9gijN7w2dKbdW2fJgaxzrBTyL7bBYOaUFL8 pAs5bQtNE9kP3Ks3yQrVbn89hV+AC6RUNOcG7qp/YsiAFPHRi/RH0rwv cuWInw==
+; resign=20460416024207
+114r.subtree2. 86400 IN NSEC 115r.subtree2. A RRSIG NSEC
+114r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . qYqJCfeflaJz7SH9/hax1OVxNmdD4IlPeqyj6RcZDbZcoLea1YwCILCJ yKJiQhWJA2KbF/wgVrJPSZeNNFNgtsh24Rm/errWQVaGZs/eJOQATbxG FZhVHxlx2BguyWZkLfbrFdjkI4vONi5/LDF8ooYF1hC4go0EH3+MUWmr wfKJUFHZZ4iahun+RiYvLFtg2zlQEzAInLgMzngDOaVZ46XrFitcIo6O Wy4W6Hks96RdGAdOlmbsMmCmwhf5JFPDEu3xKgVC40IuU2Qd6oQSOtks hoGmOu0tW5by7tQXRhBB9g5t6DhMUplvwXxYF7noSCotVBAm0Gt7q8S7 YZpKeg==
+; resign=20460416024207
+115r.subtree2. 86400 IN A 192.0.2.1
+115r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ozrkRqfwxLgosoEbVRsyZuuxUU4XmxY2CKPQMeqwEJmOfNxVyDmJgG4y wGTO+hRQTrwRFSem5918Up1MO4Vt3vLr+i/n5aXbl4rq2jvyQidKpQCV nXxF7BWOhyHO/v8ns/5hr57ciDbz5kPjfM/JI1W03XnhdHd7opEEiu6J zlb9i653+dod9pd3gvuy9gzlhVh05uSOgpVJxjIyvRrOW/DqYg1iOsHR Ew+1q7bZlFGDaUL/3bmFxP6G2C7WxSuB4a8Wmu/KC44yb1LwFe/bhARS J9NnKpTX3SBP5A0pK4e957YqtjXRCMA5gXjmQuadgzIx5wFF7WRm+Ga/ HtojnQ==
+; resign=20460416024207
+115r.subtree2. 86400 IN NSEC 116r.subtree2. A RRSIG NSEC
+115r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . MvMYm8VkU6wGeUYsGTXft4vmyuz7KK/0cQcw8il3uobixnhEwtwdr+6y 5BGDV6MLNaBKWptnfUn7s5gN1t75H+tlxOrQSn34i6LacDaWZQEdHHxb A7089FsPBL3SgWeKf1Dn80YllFlpdH4xWDI9dHQgO3CuUi2lEvZ61NjT 9J7pBZvqQbb8yTzxUwXgSKRfmgjYEIRsdk8Pg8AwVmeNxmbH1oQSo2RL gpTpNKigdnA569l+2D1THUSZsMxnIk2G5IBFFDoPzpnp8UOfPl6O4j/g nExRVrGcSsZRLTwHavYrmsbMGPKFYlub0VbM/jfPjXe36fW4sMbeoyvQ eUu7uA==
+; resign=20460416024207
+117r.subtree2. 86400 IN A 192.0.2.1
+117r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FrC5ZQG8KQIopup7Ndzgh1LJCVYIUutqsK6+EeQDTBPZWEJYksRfjUMy iuaI3AzGKQEE+gBMwqXbPSYFDqj01ac6IXq3juiQXGpi5iGTgYTiict0 ggAt7UbKswuguW9w5WxL/+tHoqxtoVzojwT5L+NjgPtHKxdWjYcIoZDy Nm2YsJW+KNcEA2smFetqwdWc1bvlioE+FXAZInPrJ7dI8/5aKWiCCVqu jpOGiaTlAHGyl1Kn6psrsrptyaZbr4CseqDh3opLuTrLClA624lfbcr1 gnKvMDFFpkEIRtHDRt9h4/wJlcS8skVEUzrZna6K61U/H3ItL8rFPtnL 18L2aw==
+; resign=20460416024207
+117r.subtree2. 86400 IN NSEC 118r.subtree2. A RRSIG NSEC
+117r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . X4NPBF0M2VXoAw7t5OeWCIKig/KITQdBW6BxgFsidlrNacGLRYSXOlmT uFfslTO6h192MVUCjP7Nw5qXHII8vQ/joZUSO8diArhNI4rWZh5gO7De l9qpuy+Su2GzcCJ6/GltrpZDFSJo0PlCEcIiBQqZ9Tys6nHwwc7GYaGj l/biIrFJJDCv5UzYTob5Rb2zbHw3mHq0qh2FvmiYMZHo+vfCT48GRGjk Am5j8tQERyBGJ3HPOuXTif4wnQVYSHNrkMlCAfcsHQ/7FwPSyJMNQxMu EOlM+kjhmDYpiajVpcvZZ05oqw6iTwJM69N+zzuqvgcA2gQkhhg+VyP8 az9bfw==
+; resign=20460416024207
+118r.subtree2. 86400 IN A 192.0.2.1
+118r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . hIP4D5QN9rCph/6IgxToHbNJAssjLJuMq1zUZswcxRxUTHvShbPxNRon vCJwZscL0FbvYErydUK93+qrD7yU8Gohk7h0CDbDmRzgLbiZBWm/vkHu 1rYV+4ICCwAhlMlK/KyR1yZ5gvy0NKnd3pwapJD0EGVQ9V7AxAG1IHk6 N/8lQXbRkf/WGLCA5d3+nkBhoKZV6P6WTtI4OJt+XJjzJD9V9pFp3GUf VxHV17wKueFszQRblAn8vjYJwDihQGlyTcrJerZ62zm3yHRSW0m98T6N yHvNKNKvVHOoQQYtvm27/GC17GcQKjG8eQMJ3qGfzht1OEGtTSeaFLa2 lj2h6Q==
+; resign=20460416024207
+118r.subtree2. 86400 IN NSEC 119r.subtree2. A RRSIG NSEC
+118r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ZubXMLN67Fh758fSx4K3nSLMgGsxOl6kBkJG+7aZImIXyw0W4GGSI63s VIG1DT1VvSShWiO04M7FES4J/1/OhCSANMnivntKq2nZtbEEkiBp9kXf WJ9L1cvEsD8C0ca/rYN6rjP2XUxMj1iO/8QfJSyF1iGoAofuVdlLeHoE DTFCd+MxHdslbGawtAdycxjTFF3aWDwnTbP7ID2v7wiJqySwsBYcqefd iTQh+XdrHI1bObZNIOiQB+C+wKCOLmwzhhqGuvOzGbmCq+ELaQMs1wse VEGqV7DTVDu++rMo/QZhTVsiBWy67YPP4UMtOm191i9aU7MWMIqgIt+i 2e96Mg==
+; resign=20460416024207
+119r.subtree2. 86400 IN A 192.0.2.1
+119r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . IU9wuoC0CDxaBfN1HGIAZh3pC6ld8UPeHGkVrUhe+PJbPw2ELLvYue+1 kw5ObQ1/sijjWZoD/N0xOt621jCSZfRYNDXutYY1QxffTAYwR2K4/8gl m2qFiq+GHyLTFhFej7m2ZW2wXSjmLtcDi/AEOg0ED3llb/IOzgXjn5ja eMhb3HA9likWIv6aK1lOq+7dwDApJNRmo+7haRLeBecV5HOP/0Y2EGrn POHfogNJyVPTGazoinlEmeOSRQ70u6GD1foOk7j3P21RjT2MM4G6NCYu JKwI3YjWhtnJt1W07bATknoYoBGgT7Q281waUZ30nDuM4nLaOCG/Ry7a BUbrqA==
+; resign=20460416024207
+119r.subtree2. 86400 IN NSEC 11r.subtree2. A RRSIG NSEC
+119r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . rcdQ1ziRoRpysd8OMNH0Fn4nmDBUcPHSpe5gltLYYZblPRFU7oQrsvmi OWhl4OrIiyXvzkIWmUwt4V+THLI1UJfoAQQB7L3MK6m+81jTgLQYXK/E tgLaZynLBZN12M+24PV4PKrNqNH5qfEXolxQBcbNsYrOwdapEUalg1Nu Ge0MECjpNbtVYAwZNAtuzR+MIVmDASohxKX0aL2IYERvf3oi/m+bUv8Z VImvX6QdTw2ROFiuh5Q/r13ZbrdYG6gclt/xv+OzErbvnZH9m4dpFjXk H6/3A24Z04GBMaS32y1u7TO6Vdr3OsMiMweEXXKG1WFq7a4re663DhE4 Z/VGlw==
+; resign=20460416024207
+11r.subtree2. 86400 IN A 192.0.2.1
+11r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . eFgY0mGVLLbLvDn9QvJ6Nnxp+zL1sggZhbFlIisWv75nY/5YI3TZGq4O gRwD/1TpUr1/VI99rUuKYTb30C/pFmOo79tUxuvREp5NlWzuv7sE6tWe rxmtIpSH1vQMbZkP28it4C1JW3vC4MrqhoIQnHeIDkutet9rCh/1xoa8 22c7Uhj2MsuAesVhQRJxRj/fi8moNVOS8f0d5dgqnJ2MikshkyoziJgo udYxlnQMurbqFYi4isFGujwZnOdgQNJ/ccf81JRCRKLoCGj3uTgCNI8Z MxcweQsWD/mwk83VmiyQpT2PCSwKHfSoppNi9cuRC868OfKOTOc3DllE uHHShw==
+; resign=20460416024207
+11r.subtree2. 86400 IN NSEC 120r.subtree2. A RRSIG NSEC
+11r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . OaQS/m+fnOUyNf7JUfUFaGBQJ30+qX+g/jZc5mN/Zm5RSmyobvpZepbw dWttsrQnaEA4glDvgde+au1hf5I4zXOeav+sFZa3JE6JqEUNpkrhTjEo bvYoS2myywt6987D3IZpmDysdCWs/kQZyS4hR+TG5zVagFa7ojXUuRtz 2y1Z925/7EPLWi3W5gKwlyQLRoA3M/OaGOFBDOEY/qlGT15HaVo/gB8X K4dvzpsIWZfgS517U2eHQWEoPs7+xnU1ifii2HsEc5XuAjdodWT5gCuR 4l2iS9JzKt9snfrps0oSI3feDLDrXpJywFZQWMwu6cHh5YrcmnuNc2KN dkvtKQ==
+; resign=20460416024207
+120r.subtree2. 86400 IN A 192.0.2.1
+120r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . sh22Z4R/Ui5KsGXDxWZmwaxBarP5topizqVI68KtI0WqWu/jerkmPiI/ SQ9AKpoERv/sALeoS7uO4eJBWAW7zgPh9XiuD2NtDG3rKW2zWcw2XKvu GHWduKuy/aB6G6eUUFwXsY62sXQTV5rLHhEQ9U39x9+M4FY10RLjMGkm qIKJXyc3IV4T4GsO9BDtnK5nfd0llyOYrHyV2prGrJy2e20u00XK7jMD KxAt4ET1vlcdgvCmNXLkuRVJgA3oIAHj2l0R+H4Z46NZFsAEtNS6eejy i4SAAEXHeX9p4ZQYVwAADMOrJI9xl/iYy+Arsn/ZGsfPa9Y5g2hgQ6Ic z4DyGw==
+; resign=20460416024207
+120r.subtree2. 86400 IN NSEC 121r.subtree2. A RRSIG NSEC
+120r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . HfhZ5uCo72oXqj4Gr6jsTLPR9ZnXoYvLwmDPCBb5+ILw1jWzhaJdSPvg 2Q+3C9DGvQhj1UykKzjYuoLU0Oglfn22RB9zcJ3SkuBRORO/PDzociwu TbSFzdMYQW/F7bBaImHQ/hBrZ+g515pYUF048KgO1SySr0Gcu/d3JXnU A7wTdOi8b7LqSHjHBBLxjKfR9QCE/Bqk5XHO89AiS2AsALGSBB4JPNfc nZxtDzX7KAqNf5ttPdrp4j7Mo752nj56wf/q4LWmOZOE4MMDHdLaOFiT jo/Syo192MH3jiTp7gd1w//MXwd6UQ5I/4pQ8X0Lmt9fxyqyN8Vrue5d jBIhFg==
+; resign=20460416024207
+116r.subtree2. 86400 IN A 192.0.2.1
+116r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . O0oUTgppI/Cs1Yy28L0iThbZ+kKNbD3k5m8Y+VFb76tJLMAGe8zxMgCE Qta2kIAGWfTX3GW9/POApn7R2N1JYl19TGWGHw8YU1HJnn25VwRs9kNf B6rujBxFY8erychMRuxX55I3KR6JGyKlYdXNKbECaPS2nG+ctIa1oGfD HxYbivqoTsJIUnB0LapVJYfdR30/UOABIq+S2VSncdnXCUzMdf6zBwOM ohXLwZtuBtQg+ZResG0ELYu5VLFlCzJ/z7D8KlV0S1wRkyvR8t5+lEs2 LD2OKlC+ap6n1aK3AhIAmNeVXTooCR+9rG7XqJ05kuo3j3S/Z7G32oi4 tLNJOA==
+; resign=20460416024207
+116r.subtree2. 86400 IN NSEC 117r.subtree2. A RRSIG NSEC
+116r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . CbWdqSUtXErQ00EU6Xr13vgGvgD5iZBsj2cwNYuPfMmsCkNMJJAgVMDg cjtHMD0qbDy+ETew1BRFjHc7SOFSuaHT8oq0rGkHxfXu9vRhZUfD//Ea qKCUDjoUPpPWeLoo1z6Dr/iJs5ZZxs2vfo5Bog8nxsPUdzAFfvU5jS9S PylFAYVpjpBhNfXvMVcPLnIhFSskuvc2O29HuI9QPCkrGiTx0S5h7jP/ O9noYtKNT7NOY6jf0FRSjIsqyraoMa+H4/kbPZR/bDpDG3/XdIBEgagp a8ezUQ9PYsS/HoB5+lZBZz+UROezw9eZ+OmMjBAUb1kWjEkvPcHXnVRF 8TzpfA==
+; resign=20460416024207
+122r.subtree2. 86400 IN A 192.0.2.1
+122r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . NB5NXBp0OPis8JwfJq6jZL8A5c4OrObjj5UhXHmfu3QnRNG77B8FWwe8 v8pm9bRUgpbEGeQNLf6CFx5LbRNSQBoLl32Sp4ydaNtVNikU8Ob/B7ek rvX4NTl5kh/rEbbX1BINXmKW7WKAE4Qjbl9ydjrJ1FmsVZHnnnpSG+Tq 40ABYF9hKp4uH2ngc9wAuAFMXCpwODcm3ZvDgMGxGRESEbZp0ydJnoY+ SpRNnlY25oKkBDP4KNOU8Z5mRlEtjch0l9tDgUXsuB/OTRGf2wGwGbSJ iC22Mafk1BPRih8Dtybp1wwHdLEf3Uh6G9maHspDGHL0SoCoC20pDH0p 4VPfXw==
+; resign=20460416024207
+122r.subtree2. 86400 IN NSEC 123r.subtree2. A RRSIG NSEC
+122r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Q+RYhLVqKtiZG2gfNA5JJWs6UQHRD67thOVMP8kfXptrzCJ3zLDo4Ffw IFlkPM6BtBUI51fqnEL2Qjb5nXA1bX2p6IWOrINpPkSrhLXB4p66rNyw HyZXum6tKt6/4NMpyRPx4V0sU/2Z098s28bHo8AA7V4goC0m3NUr+JUn P47LYmK/cQ8xO0QFSTlfWOE2TC4kCY5vQju+OdfgRXPGFp2IMNUZs4jG 3snRf+xDjEGz4D2LyEHBu6YfEaaNvZGIR19uwGe8ATcINKD6hhHDZj4Q pIYzkvi3OocsZcu+dKhRE0Uq48S4mwq//YAfIBRRoiEXeYhqG6/KSIba DFR7+w==
+; resign=20460416024207
+123r.subtree2. 86400 IN A 192.0.2.1
+123r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . GndMmZXvK5RaihISXg8hCnuN+mkkP+YBlvZYTkB1L75ThSdhOzry+DcW Q2YiB5y2vfQceJ/7SiCCpYyOdhtlmm4kBBzEZNHkq5SHzFydYJ+RxU84 rbG6Zoa+QHNiL3W2EqsvtRdZSYmO6I3xTtpmfn0zGiWsXRpgRv+u+6Dr yhQ16FUHTKAYrh2/PReH/Mf13QnIPPMaguJ4HGM3YfOikrDPIEwx5Lmm JgAsHpSNGvgLgLrc8rew9C1DymzsXY7aa8rAMXXBOtvp5bqPOblxSQ9d 3JUXDRZPQYr0DXXNg9j5+FLX/JcPoCiFLr7OKVO/XxgIRZMYPI9pbZbX ZXdPog==
+; resign=20460416024207
+123r.subtree2. 86400 IN NSEC 124r.subtree2. A RRSIG NSEC
+123r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ixCP9RE3d7Ha9aOp5SYR4JZGtUe5cm9w1NWDZWhjXuvMLFnbKzICYgbK +eEAK3HFzpcNraSUUQnZ9zrUa5tZJedqRE53JCht05gDWqOwV33qbg5q Owp9xfXhJRG7bQxxwHV8AwMBDsPe6+Ann8jDUzXKlGZUEMTRdnemd9bI JavE4R22mmUZalElWTUtOki9VdLlg+8WM+38/rE4IG+q4LP/rv8+8lrR AdUTPwHH09e4sRe24zowxIQUmdOkSMP+pXH6H6huIXwnQHjMInO557JQ sxu1jLgJa9sngEpV66d8LJ4R4HVI8WpXNoCr+YX6vf6Uru570idlTnNS QkUzkQ==
+; resign=20460416024207
+124r.subtree2. 86400 IN A 192.0.2.1
+124r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . XolYJ3fJxqmokb/kRF8S8moO3lo7bV8qA24kqaB/B2jXGlvuPYAhIYNJ sifNdJIW1Vet9LmRnb4H3LmojrtiWT4NkFfSoiZVISpCQw0yUXt/Etm7 7zS7HyFp1Hn8Oedhr1wQOeqHl0sRKGoTiZXXHcR3+b9LaDyivVt1T+On JCP7OV3uctr/FXqB8LFNsCNLyNTk99fz26qQTgGGXu2EiYrApUfoG2pu ERE/sMDZO6pd4Y8I58mu7wVihEFiSqQGa3uS+VJwnCgQWArvcAF6vcbv 7T4EIhFZ7b/8JQhRw/R6UlDP5JxQhhZezTw/ERMUMJv8KhL02VlsCFGr bvTDFQ==
+; resign=20460416024207
+124r.subtree2. 86400 IN NSEC 125r.subtree2. A RRSIG NSEC
+124r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . tO74Eo4FdyD/rOtjVjrfVxEe85sHbjlA0sKbvD8iLBs4J5lN61JtnZcy LR8K+/QXx8mTlVdIPT+AYzDUhzhdCtZp+4gNH7sM6WhfglOK1IHr4Hqn Bij6d4HoDvzqpgcczekMiPHl1CCztvzzdt/GujwtHOVmyTTQJTAL0lta vxEbQ5tsVkHjM8zVioEm8tH6H2CSZJSGtBCyHVhp40Bzi+D2hwAtwNGk Iql31PYVaeJj7KXM2oKYuSQHFmkvshwUk3EYstIVEc0ykBKHc922T9Qu I7a6//m3u+Y4k3VyoKlYNSVfNiih1V6pBT4VqUHpa0EYfmsJVR29iByd KuwJ8g==
+; resign=20460416024207
+subtree1. 86400 IN A 192.0.2.1
+subtree1. 86400 IN RRSIG A 8 1 86400 20460416024207 20180815062314 48409 . SPHvBQOqg5nWHvEkJvt5jcV3/glfVA/b6wI2gkPNgcXkoMAzBfzCa95a fG3PCJS/9sAQAgr6W7k5FiQq/PaCt1jMZBS3z+2zWsNdGOAGdKz//Sdy y8YHxI20Uroqntvm0+Cu1p4HMRZd7QyBOxJJKA82mCLkwmRrSmwP/tVf qFE8HU+kkloSbidPwmp8nmYXYjByANHQCRBY6xkV6sMK71X+/eYzBlFT uxPS4aLHVoOTS5cwdxI7LXHxlW22J7Oe98vom2Bf6kWHd1MGLYvdp1fP D3bRMU5HCdktc+4Tl+nRo/RivUhYdcQQXOZWEx1CY4fCWssR683IlcOM nLwV+g==
+; resign=20460416024207
+subtree1. 86400 IN TXT "txt exists"
+subtree1. 86400 IN RRSIG TXT 8 1 86400 20460416024207 20180815062314 48409 . Eq+g8WC5BonimGjapGkBlLxjpXeRI20dDx6l3e3iyL5WEzZF2jP5ERps IFD9q4h4SPizF8iu/5Rwkfvm4IxJ5aT+8Mrarsk0c1HdtNJOo0AHT2k2 KllWe8T7e4QD+nZ0nx6Qwml+AaPj7PdcAKtEKLXCJxnubQrPjLfn/cYf +lTep5kgOUfEcHK/8kxor5Z6OfOCnE3jBrfsiDXPHnoO6JBbonNTnRft VWRTnyV+kSnq3UmwU6diuhiPJpezZJhYcn3ycwTgr+1WY5na/hOYOBTD gDrf/slbSnGyzkEoedln14DROa+EsNUyNKMfkXzBgx52w4rop2hR9UOm 3Nfuiw==
+; resign=20460416024207
+subtree1. 86400 IN NSEC b.subtree1. A TXT RRSIG NSEC
+subtree1. 86400 IN RRSIG NSEC 8 1 86400 20460416024207 20180815062314 48409 . KONcjoRpLJQHvahE4t0Q5UlkxkQv3DT62MbAbR8xIDigv9x7K8LqrpxT 3vC6aoHLn2ERJAQAG6sWj2vizmHdo0ND5flI1GbFAJQAzELlxHnbl7aS siQRIdwxpAnkCV48jjhcPkNpOWDHmUl8U+OVChTOWaRgChTMsDxWZvOC zbO4xzvHECjfw6iZGESSCLnQz7/FVQEG9B78nigXkx3QutFlvHKjAr7G tbse6i6CfQQWD3HUxlXOpYR5ZyUNXNwEQQXvKvv8o3B9b1dXhVgNpP3s Qulk3xxuKcbeZlvfOr2vawfY97VQ43mJZ8DVs/IXYDj1xQIDvUaZ1eFm mAi9Pg==
+; resign=20460416024207
+b.subtree1. 86400 IN A 192.0.2.2
+b.subtree1. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . iZZnfKUQIy6LbOBnjzbnKgpbAuolfWnhyiVKRDC+u6iNMsX/g/YQ6eDp sfaORTwV9yvVwYAXfHk1T+cFcguF/uM+jsVeXKWJTiMb8UDpNGzM0h4L 3ZAZv7v5FyMf/JNpg87JwyWwO3HU0BHfiCS5S5X6yCdEdbKyXrUcUgi3 X2DA6jm2GhPFsycNQndc6uwEgTZOKE7cxfVd/rlYhC1gYMafyu6g+NZY WmFQ5IHGFSwGJiweVQoIsQfB410WH4OpqtQUhwTbnh4qCnHqwnUZDaYA bjL3jJbOlieakQ6XW6h8ehr2+on7fozcdAuUee8OKutiDn4eBhZiRC5o aIdkFw==
+; resign=20460416024207
+b.subtree1. 86400 IN TXT "txt exists"
+b.subtree1. 86400 IN RRSIG TXT 8 2 86400 20460416024207 20180815062314 48409 . XdoE/QvnjQ8FEwSKG9CuqQ2TdxGdcMjWLGOfIgOoHOEPoGZD+TAfITw6 NYICtiujV4/9XRTQ8h63nVwhUjVdsGO5D8JLPqca/7fOdLyQ42hNnAmf t2KawvzqC1Y9Z7yMrcW+QC4rT1GEEkQMu59F35tJAsWODMZAZXDL28z/ HVXcxTDU+bcP95OYEGhnknFqYjzdk11zWT3PaA2RPt0GlDh2WATCtEc7 nr2ZhsbYyyzlY1glQhXPpjnpBcZXFP01wMhKcNH/vcXZF0AIi6N6nIXJ TO6u/fsG2RflsdpiBym98ASuZXA1nGZzMQ6nO4IuWafaLRUQ1feBAJLH lst32w==
+; resign=20460416024207
+b.subtree1. 86400 IN NSEC a.b.subtree1. A TXT RRSIG NSEC
+b.subtree1. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . axjtB8r8jja6iKQfdd4Qt6aaobdM8kpXLYAeJg+R8BXv7D+2Q5ZeCikc gaYaomzhSTqA7cdyv3ov0W6T8yZZ3rkPLkN6rwVNHf7Lu/cbF7q8UuaP O7pyXJDxp953/T+i+hJ6Lq7HV+Be6K0Vgq0+w0xe5BoTNVcBRQFu3QN1 Nu2m8ZsVhouM0aD9QDs/PqrCPktLL57xLVMoU3SaVhbQdV8VYZy+t646 zkDMQU7ZgvnFxFA+qyvVM2n8TRYImr4xzrIcOncz/td+HCni/EbCS2by KpE/xHQWthuSbiiUMXC6Go0yN5WKvOO3Ef4c6Jejw04XCg7XxlMmIDTa /KcznQ==
+; resign=20460416024207
+126r.subtree2. 86400 IN A 192.0.2.1
+126r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Y+oDiMv6emPFsvhyVOFd5w5OdHxWAcR7b8P6fHZEKWrgLspEBlkJJbCT Jg/0D0JgM2tSVw2G8qS8MdMEOrgrWukfy2H4gQn2GXd9oX+Q/SaYCxVZ AnnbX5duHLYKRUJ7aQ5S0BLUdqsQNzfjluNmAJAmrmqB/4qq3LqiOt4w rPe2NgsCDVx83d7lHEeLc9WY2iRHN6XEBVo0JuCH+n0qMItmnXAW2yRF 53tw9E3xzV7Ql8mZ8HfYkRxvs7LUnIBnWm4WTPxNFgW6sAuaovEHrCj+ S9edUSrKtFexYoj5NdYccEz2mVEnUgVkHbBGXBdplx3eiOAuH06S3+pn LAk2WQ==
+; resign=20460416024207
+126r.subtree2. 86400 IN NSEC 127r.subtree2. A RRSIG NSEC
+126r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . B4/GyQfnLIrgknDrWgMrRO++hKv8z1pFd5SovWqOXgfu3/pQ9F06vfL/ oo/w7OyQes22whJqZ5YHt7vj/ZcRQdzFyVUcIpAxCXV5vgFHhQtX4ctw XhW/HZFNeVEeJGjzVhOaSI92mACBdzMuywuqmFtRW6Flf7YyUZMchZOv KtR+SlKtXXjxtc4bIABPO7pf0jFBup42X6pv8KORhDSEq1gptuX7MrCE ZAT/6n2yk+2ilK+Gz5dKgxkwqKLg297jB1G3br/ja0dM5KPxXQxH6joH GmfgRn0YKOzKV97PklDjO36+eRSOXw+voQmSnAS0eOXCb3zvqevYdAg6 bXv+ug==
+; resign=20460416024207
+127r.subtree2. 86400 IN A 192.0.2.1
+127r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . bZQ3ozEF0PcebMQWveJURSLH/8S8Abntn84pBAuG1kCzElCGrNPXQg+m l5Oh0dM0cMoZUYOgpECLR3mKE0MOvZfgGEgRFHHJEb4M0PipuLn1GfWD jGaAKQ3pMAOptg85aRleMgcHKWaAmTxV2C9TftEpul5o1T37Pumybe8t F+LS+5KlSBxOmNpQzXDz6Ladd8FIzqeUpA/ARQK/adt/+L3fIbuQ40M/ RGIRBrjQ4kz2YJuVqcvhNcdQitJNhXdRILNYjGKvuj4ZHFp6KXWoRTzL x6S+PYH7shrIkTqVeCziDy3LVtAaKgto59Kbw8cq0m5Yldyl+z4WQIq0 t1guyQ==
+; resign=20460416024207
+127r.subtree2. 86400 IN NSEC 128r.subtree2. A RRSIG NSEC
+127r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . uKm1r0A0BuyTRsYGjGoDDGcNld1VXSck076HxHJ7mQNusuKQZ5lfNtRE b2/FjlXukHQyXEkfsQc5BJvUjL5WnnPUSDBTnPercSCf6lkB9PPWOeJT EuOHGjCZC+wCYSsuz3JIaEhHwsA8JE3Ufj4Ld1S5FLyt+Y/smxpKg+Ca Nj2Um+sX5H6OkgXJPHnQ1s7rF+fTuYbkz/iiRU0CnDc6sfyJJjoDF2/9 ahCnNTqdxUjSK00Mg1flt3+sJCJ6tHjYdYgyANUKc65ZusPUKdGPJM17 +oxSrzE5+biVZllfYRnDYopeayciU5CuyUNl7Swr6x0EI9TA5t/KVD4m F/wEtg==
+; resign=20460416024207
+128r.subtree2. 86400 IN A 192.0.2.1
+128r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . b7G4uESmTYUIboJyhjzJUIGJyzfwOMgtQRdhk3+hGC6p0lE9QIAHoTta bUk5VGHvhg1FTE/RaNelXK3z2WmsaHtZEtEM6IE3j/9w2d1OCX/K6esG N0dLxynR7w8ZWTTC+5HZTgNURHD25Z0gMWBkiFv28Qwr1zGPhURUG5JA s8dZhsSpA21I3bkNaz5AJDMFxTyUTtdXG5jc9SRFX3jgPUCzsyNjf5W4 H2c+4opkhGcydcUPcsc6tbocUGzXhQU83Im/8Updz0f7GY7uzdaDZoeW Fz6AveZ4EbTmzWRXSKm/EEJqEOyy0xxKAY+CHocz5J3QZfbNjbEN27aC xhzsIg==
+; resign=20460416024207
+128r.subtree2. 86400 IN NSEC 129r.subtree2. A RRSIG NSEC
+128r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . eJikYm6Eb+bJpCxqIgiOA66w+mJNtd0MeA+oXNgl5JZVHuBLx88g6UhL 6nxC1RFY86sujHTdWnXR8KeHUFASsyYVzY/KQbEeOik7oTLcXZp75s6X /22WIxq2ahvRAXm/PCB7UEiZb6HJNhdU0vTafcUznYahO7FbE0Nw9fDD 0MPgOdRl1wtApItW1ZG6hUq/UYUKCTQHS8jJY5cidOg359mQB+Ik/L2M HKpk3j8bTxbHE2NVBIoScDQT5xqd8rsQAbBrtAVBBvIVcqgrd1fabr0s HKWT3+21ucs+DLdsFiidV48rRuGAJIrYPsteDLOgtZRchZxoUGOpHWPJ YVGs9w==
+; resign=20460416024207
+129r.subtree2. 86400 IN A 192.0.2.1
+129r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . xvPIWkuZ/zQGallRWH2I0V4qyV2cN0a/A9j/IGhnrflC58PKTsjkDu8x 6qemDbBM9Xo2nkdI3G7YZZjB+fZ9SSr5SuSMtCvmoUQOWjx36x8ZBa3S Z97oOOptOJPQLWYK957QuwgEtS1/SiRMZPrXFoMWpPIBINTcHPrXTYNg 1jgoIxPsXjinZTuO7VXPyRjlhhK0Cf27yomKku3zBkrwDqBH8FpKajD7 zq0uFztSY1MkpVgvfmT+6mxuVPiuIB39h/A7k6/sV+5G6Yw0qaL/hzER 6TYIs9cFw2C9qgLHeLcPGEnFhCwgEuk3wCYtXJtzCBqq4nXRm6OKTypP E9kBVw==
+; resign=20460416024207
+129r.subtree2. 86400 IN NSEC 12r.subtree2. A RRSIG NSEC
+129r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . g8Ym8RVSbxNVY8D4c4iNmHRgm58PPUE0JBaapvJTrAtt9Zj4ho/QYgnH iJGvNzIdEySGYD86aGvWlWgXKdsubnRCihSd88CuRDK2CXuYE2Jw3sGN SFRBkY9hOH4BZ0Bms95ZZCmjCxzI+6jMfkwuKGhI7K23vIg3oVpE/yHV 5HKVs5WJr6K1gMQOF8XrwlsJbawHYNsOOMuG5Y3NZWUUW+3L0zwxMMK2 d03f1AJpS1K6lqfw08FvhYgAhFoVVx8unB18UqEW+L8x0a716o27kACG 4fqdWR8TKhSG+/i/HVMFulBaWqGKwi1g4UnM825aEQtVtoOUnMYHOvmQ u86UcA==
+; resign=20460416024207
+12r.subtree2. 86400 IN A 192.0.2.1
+12r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . RQTtb1xIPxf3rvjpYYw0q9RcPiRPlx+uXg/1O0SNrgL8sxb+QZuBjEjz mLgOioX+g1zheFDbftKswUTb3F1vyKukW/Y62jiClb85lezH8EjTcHAQ n47ZSUnNGA7jFRMkrOc9i2hRo3H8DkMTFa/d5RFA4ZvFe3/jj4zWJ3YI aXWW7qUgDutPnrJtcPixOAD//KK4uGRxKwgeo1j1E6wkjyht3A09klN/ hZtT7qN3xruTu0mF3WmX/9HEOQhU4wswVHKHTGOMwh9h11qQsEu6UBgZ PDbAIZytU4m5ZStKg2QqjruNlS0lU3g6VBpOQ0RlZpzssPkxyRdQTCCq c3FEBw==
+; resign=20460416024207
+12r.subtree2. 86400 IN NSEC 130r.subtree2. A RRSIG NSEC
+12r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . KZEpHmob+s8a7FK1vLvmKWB3SWcmUP+tWmMP2QxeeLjHY+hZKU01F4tV FlXAUqLpmjuM2hQPfhPFqhw+Dpgvh4VUHmwO867t2OE82TNGTb63PDAm Ri0zsb+IObCIG5n+pd0mhV1gn77KSA72Qf++5SD1TqQ4b3zu554nzsig 3uqkpXbzYGe65GY6yVOW5nRsdo6qOMfcZIRFyPCSb5xUg7u/OaoOTV0C BaYx1o0dpbz5/pfriBPHPHkL6SXnOdzI70nj5m0ZfRQpKMgLOP+UW9bf +pPD9NHjD+UEs5LBQgWjXwymgLy+GvoCWURlWaHfDvgHT9fmeh2PlTUP 2ExVyA==
+; resign=20460416024207
+130r.subtree2. 86400 IN A 192.0.2.1
+130r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ZYhlWkQY1P62dVBDxos+9jRishojmYY/0wNvx4Zj2bHJIpuxAe6xvIrt YWlj58GQYlfB85sLp8K4/zwd+fahEjy1aZrmmfJJKVPLnQrUcYQBt9i1 RbTjR1smcSVoiIXu4FdDanbJc7sIuUpQOp3C27PImyfZa5qUMCnAxcLL SGhdvFLbii/ALAC5OKVxEqjAYtlJ7NuYZlxa/ZhruHnDRYBhh2QXSatx M19Mi9QboBMEd79U5ym2nK6HX6AcaFdimpK5Q6F4BWskUY+Qls5Y0PQH 5TPG2LiY998Pi5Pol+nnsfKMLJ+yX6bELy6AHeRHm92m19iu5Cbzg9SY wfRUTg==
+; resign=20460416024207
+130r.subtree2. 86400 IN NSEC 131r.subtree2. A RRSIG NSEC
+130r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . J+/kpBOiixw9TPfTRZXv8lkVS75TRukn57NKGPmp85uA6uGTsmAr+uMG gtAAqkxmkz4IeF++sjjaKxfK3afX+DrA+9ROUXqLeRBaut/abMocM5IK hJlMSweF6JLDSVkqFZ2lu/1yvTRTnIkL0XACrNfvbYfCDFAD8Rn3vXBB MkpsbfQ4mJVruag+OgP3lOyKDZPjMbjPR3l+rD5ywerfqfda744sWtRW A6xGpluhgUcJJzPvHGxrdpaDxKyJjWCN+JcP4yjfNrXX1YYJ8XZjErHr 5UHUMRZqEOxxNt8N7JdzHMieCRVrcQlB/oFMWZIoeiVVDe206seaV5cX 6hur1A==
+; resign=20460416024207
+131r.subtree2. 86400 IN A 192.0.2.1
+131r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . yDl5kzqQVeOBQANC2Hsb6WmN2W+Qul8r1L8LzquzWwYsjzYQOMeEKl7S ftHZSmLeVmEmARugRJxABJdwjzqfbOwHs0wAzKMrjp6W5naY3PH2Hehl vsi9nx7SHb2Q1bBMH4+OLfHfhG+E3tN5pJ/djdN2ksng+O0gSTAkYa1+ ym31BmIrtEBLUwLUUUvq6JWeU3Tw04JKeI0YXDL+Ve4Qjjwgrl/WhiId VGXRKgpGHy+GcB4pI/lJBpkQUWhjmd5IFkGbEImb1LDmAxsEmpLxTSEr dOlY57azlHqOfSVdSZVSiaYV/iCEvHW2a0+6hia3cFkcXBPzi16GiMEh CdbeNA==
+; resign=20460416024207
+131r.subtree2. 86400 IN NSEC 132r.subtree2. A RRSIG NSEC
+131r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . h9PV1OZUSaDHg6esVRA1903+gvl1LjuL9uqnmwfBwno/AeyGlG3H/UvD mGP0ktjftNcNdxLXOsKQ/bgoFT6771AtwpTqdBwcO0BPZwabh2tgUhLY UoMY/LufRyLs/3niI0rhBLURVvkG34aUJ9SIYa2vvxPZuaZevR8LrSrY etbkXWKiiCMeFb8y26xFjo/nYsbOoVs4RZr+24q8zSfEXEzeCPqe2uj4 NZtKOuSD14zP1qiWczlK3xOOFA9lU3MhOpyxXBv5mgVq72Q5CluugN48 9Jkp1bwZj3FPBRDdBJWkvJQhc3X1cdE1YQ1uLcvHyizdH0Cn3YqsRKh3 RlkIMw==
+; resign=20460416024207
+132r.subtree2. 86400 IN A 192.0.2.1
+132r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . bSSIZRLnCDmGki3LVPFKkSG7FMHn7NUEbIObjHsKYRoBDMu6Enjrd+1z Qrt3mGkQg9vFVsFrcVTKPn+7Sq8Bjuur9knI9XX33tOszxWUFBk+pdAl tVtFLGQ9arB+JB8sISnEZZzD3/LID3XnUwURuemjLjYdzKZ8YZYvUoq9 DQJMZdScCBs3NghCJRcJ+Z+faYaQDepnsznmXfFklVE2ZMFjkejJpJPb qCO2bsPppOI9scQ894SefP72sehSVER/Fq1C6eUZ59sTB68Dd65yS1aJ Ft6WTTqp3pwysKpTpNXpxHCLdarQP28T9aBp8tHUyQu59S0Ir32tosXi iafxpA==
+; resign=20460416024207
+132r.subtree2. 86400 IN NSEC 133r.subtree2. A RRSIG NSEC
+132r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . qY8oCvoTEvFhDw/i0KCNZJvrlj2d39i637X69WOZ9pAHcsgs/BWSoxcR Fg+wjMZ5T/rqVuBmlOZywtwt8W1RZGpqmBzZq6PgkE+QJ3HuI3L8nxgS gkmeNMLqDDOH/Yq64u6F/hsV4GMlqrLdPTSVApkCjAQCeYtbv/vX+LIS X8hKaUjzbjRjKRfV18VwXgHdN8k7CE1TG6c4MN61rDuG50/Bzj4FquSG XhKsznV0qd2MuE/O/Rk0Iezo6QFrqukb9remAz78HfseDp7UxZ3gvCOr 5ZgN4LnXqEW9NbMznyEFqZQi9C2UzcVOHX2WXVWuvphhhcMPor0QfEuy NC6/JQ==
+; resign=20460416024207
+133r.subtree2. 86400 IN A 192.0.2.1
+133r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . k8zlvNtimuoghUWTrivzYIHg2y5nj3zErrgjIza67br+wy6k3SfHCLUm eDBFoMptjFyasoN9lYYDaKFziT/xOK4YaQKlfZrj3RP1AyKHcVBlIeea ZgF5LFHZiX1RtaMs0mnIkRk2B4DbxuljX8fADLsJsmSozR++Fr+nv2wt xXHbtodiyT2lQUJ4A/rx/TMJjHxBmNPGeXOA42KoAV+fbQx5fcYtySsz WBy9KMNylpWl85zqrXAi93shwDVJc7AsOozXNPNQAtw9ivRiVKzJp/Ya AQanbB+t0GbuwJUsL+Iz1wdze1BdLzo9yi5DUqAb2FEhyRfcgHywpdXA 4NNT0g==
+; resign=20460416024207
+133r.subtree2. 86400 IN NSEC 134r.subtree2. A RRSIG NSEC
+133r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . r8Eu/Q4l9RjfZ7R9vBJ9h048xdZS+P7Bs5hrY/++5bmE6+nCBkGXiK6f M+iBCzMOAIjgCkNReix7a78+MpRoQkUJ+UiMB3KIuOfsRWUqAEQwm6Ss 3XuGvDLPG01iBPL3nY38XfORJT3e9TRXml0WsmoFOWLcJGmgaOYC9m+z 8RERNtyA4S6YhUGyVdTGZWcIDF9uQXzHaEV3rnYLJ4keayYr1Wz/8y+g TKHPqO4vpaj/ta6umfaFlsBh+ij+73mdnwPLORbNfjoelqTvac9ysSIE MittrRdOtebOa1bSnuMixp/M9JKDcVon7ng9YQ96koKyQAulMOZVukoH So9Eag==
+; resign=20460416024207
+134r.subtree2. 86400 IN A 192.0.2.1
+134r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . s7kYOZs5grqw/3bmKBJn53eCs5IfIwXHS2LAjRcZEt2NpfrFZstYLOR5 dGJaRAFBHgh0n+7d/Tk/iEhvHo7eA6AJflqhGAYaoe8jcGA+bheI/lXH nFA/2Ct7pE4LPF4kgxrg8GdOnAq/lA7mG4BU7PDxNHFCbBjJRrZAtbZt rPszgHcRty0i2FOmL3LsUlRJYJZaNLxw297zXEouQ4S9URMsqiFL4QBD ALDOUtEA5t30+0DfSIxbxgIv5ELZ1y8FN0DW7o0AXOlmVj7vcWQ+65/F w1UsIbAYmoMxd3GovKTjAl2VT4uZ8pSWn21YZSm+mxwEhj/KlXi3vyZP 5F4kJw==
+; resign=20460416024207
+134r.subtree2. 86400 IN NSEC 135r.subtree2. A RRSIG NSEC
+134r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . nn23Ag8vjOKtaGMdeng/lUWZgtPNjQh52KoiIKOHNjcwRPcg/ABw4766 x/DYK5A1VzCAHRiXHxjAQDCdESbrZDURPiVWJUsAVKYHegkuET8ehmSp v7o+l7YhJzLqnwvbKHNMnXq78j98FGt7PTroughRV3FY0OJswKuxM0eW QnUAWn/126Z4m38hw8Ec2PtTCxS5Q4g4RefL7QEN+7wrZBMaYKMYL+wW 78RLqWMhGomI94QUNG4bhLSZhl5M3amVnljugdNOT4WA5Lvy15fQ5yfM Y1Dhj1l6vR0WN1S97mCPb0vEJYnNkW2XQZ+edR46FzV5dlIzhZLBKx06 18aLmg==
+; resign=20460416024207
+135r.subtree2. 86400 IN A 192.0.2.1
+135r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . mEUP8TKOZC+6X3NmaRFiczENU9+KKIalrmRD3fgkAIvogcDUiNkwo/Ii +PiHdJ5fJubznQyo/dbMep44pPWe7ONAv3+dW50ibI63UNRzcJ8T8d5W FsZQ7FeNNS5QPQe4TMacttc5sj+09IMgCADDa+TP1hgEMfFOVcICacbS /ZcZ1zol82sbJIf6qVW7oKSGP9WGs38gaNH/DP+i9qKGycMFUK5L4oa/ lKXO2xgiOaBsC6s1VpyNbAkkk3xFEfqR4XOYMdyKSUApqGFubuLdocn+ KiMg3FonJt2oH2rZPEeubyanbKK3+gHvPjHAzH+FpTl4SJoBNKg7oqon DTwgUw==
+; resign=20460416024207
+135r.subtree2. 86400 IN NSEC 136r.subtree2. A RRSIG NSEC
+135r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . FEoC0LiOaR55lIqMML0Hb+Q5vkqLdpJTAFK2zKbOntA0uSNPDyF1+iO6 8c+Si9E1QeteSUa15I6tnxE06g3uUEkGp0+uxHlruYKaK/6UdI6cJBBC UPxBqMqJydDcPKPJdWsmiOp3rRjPdwbEcLPtUqclLWr7FgDxpvor+3lV b8Cmogud5PmTN7IAMdo0hXljS/+GU9K2VERd/6z55Yk1jcQI5ZDSooa1 oY0HtssSg95QKn0VFw6PfZisDAgMsXTd1qGaomI48q4M1Nozp3XIHhaD YYGu49N26jkG+WfQXzC9ND0r0YpR8/FKhDo5VHryUJoDxG5F3CyN6f32 S7PSlQ==
+; resign=20460416024207
+136r.subtree2. 86400 IN A 192.0.2.1
+136r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . UVkngBEk4LZEtH7qI6nNxKJUoiWD7maltLy8h78nxEndS6mWqW7xq4GP E2w1I0PEaZV8Lzss2y2Hnl36uJKLgx3iqL/r1ycRBZgniEbfMxe3K56b i2qsViHKDBqKxmaOWsAEd0YoYJyiXYQhGZGbJFrDav6kCMrz7TMgCx5r t7Ng5XCsW4XfEqT2AbPTQdaccc/mYoJoNQypAnm0KYLvMtI3K1GJQlJ6 b9pL4ctktvW5+l1qdKUEdQDYgzx6kIb7YrQcB+/Hq6EA+oQMCWclwKos byloRSKgAIJcZNiHiIFHvilqgi+LfCGtvd/VU+M5if/57Ad5IdzIqcBL BmytXA==
+; resign=20460416024207
+136r.subtree2. 86400 IN NSEC 137r.subtree2. A RRSIG NSEC
+136r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . N/CSs1q/X5rVNErYAvM+nB4xBEeS6D/muzFOfp8wT0iooCVL5B4AlasB eu95iD+Yqvj/2UPpTODjDqCwq0GNJMkin0/PakrLPvGVjqaGUTibwa3/ 0uffENpVohWee1G/AmN90qfYMrHdd1LMFmbV1zAlr7/i6TpgGeYgLnyJ kFQN9PvWpHJzgRf6w5Kfy9rAkb+VolLOtaGysjota+FhL+vZdbZExTKf YeoajRvALe+7mdTSJl+fq0CvJmHIMHLZQ53OBrLaSH4ArT5L43Qb0td1 9GFIcRbnMPQxRDGacqTayUtDl707LhoN3Zycn6PCFvgbuBfJZgpnEdfT kwzu8w==
+; resign=20460416024207
+138r.subtree2. 86400 IN A 192.0.2.1
+138r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . dd5AaIiOxGjLALDsWlu35xdOGnzmaqfT7dfUfV7swQEJJCI72xUttN3O bU66tRDNlGyPO9NF5FhGpqtlYde+cmkDsq9VicpbcRFGPzd7/iax9xkG NmRcss1SSqoShfTAobS9bmQNY769p9a29RxogqyW0ENHCuMyNrmnfDCq 2Ps28Mi7h8HhZkaweBq2E+K5jfvvdMpV5S0wGnXRItgsSulMltW0rAys 27cHbxeZruRKWwiF6TvXIRnFKmcDdJaB0wyHmGcbCp8yPP3cs1ucyL8T Jog8I9OODr9bkiNqR8hYe6Yb0Gjos9Jz+8sdg2j4GPTC799E7DQhDmTu P/IwbA==
+; resign=20460416024207
+138r.subtree2. 86400 IN NSEC 139r.subtree2. A RRSIG NSEC
+138r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . sYHaUjamI6bt1N7W4vexUPdzHO5kDj2xuG0E/3ASwpqwkMFdkPNLTKh5 368WZ9Jc7inXp96GY5UroJz+BvRuJQGW48R0XL3IZ6mxX7KIeS9srvxM +tjyc+ub6wrTXtmQLcv61+m0T5CNMmQ7LvwMmxOgtMgGOmBtTZ2sw5S5 3mvkZmtXihMDBnEH1/lJDgu67LtD72ULQLqMofHSjazdNWkxIjRkUQDi UWqQ1RHMf37UK/PA9U/y6x/VXR0IPHNDcEme2gsc8gS3nwbZee+eDyij cIl4l6+4E7ypn5s+PSUaaulzJ5r4SlQvsljyeoHd56X4fDld2X4NSYdJ Vqmt2w==
+; resign=20460416024207
+139r.subtree2. 86400 IN A 192.0.2.1
+139r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . daQs19zKjNtGzUILqvBev3gNBsGYSGPix4XW3KjPjJZRChxJ35FtZJD3 3J7zFUyGmvSMdqeu3SGUZPJQsjPfudn90nBQoixB7P1miaCP29VxUmw5 jhM4AeefB1N16Lu+e+XPWqjCjPmfwiUHvWLq4LSPG8h3vQwMwJqnAMCZ 5503rUtno791CZGGXAxAdJoJrYFoEgaDcdqk++0ls+xJLWgJkK49NW8b si/7eerweQHe6ugYU7jcpy+ZDOabHlmHD4/pg0oL9Iw5H8Ar+CYUhUgN uLjDgQlaw7lWco9vFLr2Aea26J+jOhKSRqLovm8hOl9yFv+M42QbUaHC TmHzGg==
+; resign=20460416024207
+139r.subtree2. 86400 IN NSEC 13r.subtree2. A RRSIG NSEC
+139r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Dcf7I9Uh4Tbiwhz5s+8VlC1O32GhHbSxDqeiwDrrnxghi3luXUerBWg1 hdb0zNRWDxnKWSryWVf5z2ixnO1DVQqqkI9u822xc0Cg7MhdEKtIHO5o xNDSg538jIbcrpI3yOyyfPoS74n5Rxv4bpbltWTuZIzYm/lbN78t9PKM twTHlVykbw0teKLEIkkRMHrVziwqZ3OO5OYeWsxkaR5gaNgUnCAgg+gS tFAb6I4X6cB+yh1opulG+hEZga81CmgwS0iDBor+vD8kTFqeAnQsHU6C 7mmaqP/gNS+l9plTYs76yR2yCdsDhKulK4BWFTjrCfPVtacJs6mooSg8 hIwgVw==
+; resign=20460416024207
+101r.subtree2. 86400 IN A 192.0.2.1
+101r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . naTWHH2lrB8zSUEbz6CGNZty1Ugf9dIFewyFcZzC305e6XtCPW0JlUPZ 0jInSEJg8dVeVZmWB2/9IaKlCtTCdUw9PL2b6lBNKenQFbb5dtTgZHvL nscMdS5VhTiu82hUNnGOAgvvrPbL6sq48r4kYqon2aE4w6C1S7hiOjGZ Ba4j3lhwLIBVYm2dmn1h3rrr2n9ZJWMDQIjyApx6CrcawFLA9qfyCosC spm2qXEMa6ErnCNjwl/GXsGxW4Q74Kk/xxYEq/xQaudGsHdJfXZ5+7N0 CXcuzfVpXYDkRd+M53g/T/K0ZWhIm3r/wSBj8zkpsJ4k8wUDz0sMG97u iFZ6gg==
+; resign=20460416024207
+101r.subtree2. 86400 IN NSEC 102r.subtree2. A RRSIG NSEC
+101r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . V5zFg8xsuFrlbt67SRtfgtw0twyy+kSd9oLuyRikZ1gIzNwI6Arg9MmN pE29waWsmKEx/JLve2iV906CGdansOw/of71SZilPXH9L+q4w2zCJRkl 8fh3YjAv3B7fulp5UYc075f2vcxqHtqP11b+a7MS1ZaCGA7hfeMqzJez B8zPcxQ5qU35DUYHEfx6ixe91a5sf8t0kOZmliZ0LrXu7JF75x9mfI1Y y1+WWRh4MPBER/4y/CVvQF0Yf75xKdDVRu7nM7OqLzwiZZdFiGIEr6g6 2JCEYhZMdLJ+QIuaEpKF+gz8zfOqgZ4ziXen5o/Ob8OjmxAOI9/A/vGE Dm7sXg==
+; resign=20460416024207
+103r.subtree2. 86400 IN A 192.0.2.1
+103r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . pR8d/NmAtcsBFWTXaqsR5LMSiYHHYlFg7YrMef27Sms3rDDf4xVrFUpp jhBMaz7f7ZRmQ9s9QxMY+Kcgz3/cB6Ic3ku8avkYS1eRhWY0ouY6d+Wr AfK5dplkPq2rZG0q+2TwutSEYC8YbXsEBw0PUgrkJb+TX9PALgN88dXi fOvF4dLw+fP3rdltnWU8ha8EMcz95OtiuiMXD5LKJEs/55e/wdBh3L4U En1F+iBTlewF1a+/1SJ8Bf67HWH2vZUd0YHDNKoLaCfVDa0dFAXZjR/o suYLrC30Asnqvnn30tzoT6HRql9FX4y9a8npbAVGgHcFsnGbm7QGRlfr yPXE9g==
+; resign=20460416024207
+103r.subtree2. 86400 IN NSEC 104r.subtree2. A RRSIG NSEC
+103r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . li/pzRyR5qYV92PvcXGMraDHPpfS0aSwzjZb4b8TykvnDLj1ho0Sc3GP MQi6M7NZ1aqyxwDXlJFGBYOEOybSC/yH8tIqpOZG7cXQm6OOJ8rWVCHX sU0+h1Hxs0oban3jnbflJnqW7OCtOFBxLApqS1AI/Wbu9qStqCUGCyZ1 18j4sewPatQ4fQUcw+nXaiPNV45lKgR35fOfoq6CSKWjecboY9BvAn+w 7PXywU9g6mEfgDw4tIlB9LGtl7MSYagHJnQX8+NWidZyzLHCdhePDYYl 4PfspQ7TCZvsLcerU95ETtp1iLyOYgQNgbobY3i4Ner3yuf7gtrMxHcf 7GbW7g==
+; resign=20460416024207
+125r.subtree2. 86400 IN A 192.0.2.1
+125r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . h5fOzVZEq+smgW8etkQddwgA6/TmQ3CTPHkLng1xY9V0hJai/n58ORk7 nDgpuy1A09tvtRuMolQmQIDUmazniagyWYxvTg+VF/lQXmMASjLWchQH tlKAhmuu1JN1b+exsJn/vn6dZ5E1c13yd6c6GB/emtRyF5fbeHOedInT EbPJxBkhGDo3CQXHDN7Hx/4I2giTQz4jXduj3vt8LcN/JOX2uG2acPgE RRgP2UBb2sjVeOKcHkRQyh8YsUMIMgvfZZOYTJycEm9YlLZpafg/ICJX U6aiAYqp7IYKjU52bGs/vGG1kOMgG73YDDZOkjCV1TVQYOVBz70dYl5y CzXfTQ==
+; resign=20460416024207
+125r.subtree2. 86400 IN NSEC 126r.subtree2. A RRSIG NSEC
+125r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . xk3GwzP9UACMuUs/0waDDlPfeJ7dZyoRHCfVl9EH3FP2ff8H9ttXjZhK HA9uqRPKf9nQZ5SsSIH093JsdKOHTax1E1U0HaN8aUsZ6wffWXED1Sk3 nZ2jKrfFhOvsz0e8BbemHl+Sn4B60O+ie0IEvNX4qKypc4zbZjUXKq4C WDU7+LJilB4OoFOtic81ZRVA6rWVSLIxbehJ7mbaEA0risRAbF5mT6HW 4OPmOZ/NxDG+5JKPuYpos9lnSjNmeUFuBE4i8nfm79Eq9KTak+clNJSZ iXlCY3/OkGSx7Je+fMa+rXxIiULKEopAFVTPCb7TKw2UqVclAAAOwf8L v0Z72g==
+; resign=20460416024207
+140r.subtree2. 86400 IN A 192.0.2.1
+140r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . vPLnzmORrcydMv7m5Qs+8ppHJZSPRHjLMOYISRo7UAsEfCszaaVv4AVo H4hTLrq2H7YbklEI/WsCSCZI+cm1bD83siNoTRgTXorWj7srLKIZ1UAw 32ArfkG9RpWVAt7wmEYirWgIRFejrZSpHdIa9t7sFa75buxueNEhnGi2 gPDBeFJDlfLI+YqZsUmjJWGgM7C/JLhGVzcQCEHppjtLRmjwRVZ7Fwbq v8S+ESjoQCTw9DEYglqYPw8VrXbp5yFlMkq8O3sxC7S1+OP/bn6ipXkx P3L1gKaTfakSM+sc+FwyiNFKI4Cqxu8oIerJxBk+IiOhdi4+drf5PPPD KjH46Q==
+; resign=20460416024207
+140r.subtree2. 86400 IN NSEC 141r.subtree2. A RRSIG NSEC
+140r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . pxg9E6E+3OteP4DSEUmH3dJJIXp0kdLG20xEP35PH9TA3bLgLZs0Y4ny zFDFUiRoesKOmjVithOpbjbYu8Jm0jOK1MXWN/AItzqxpgC5it6ut3xH 9briU8VTGS4IW0XfLwl5jofHnZzeUCR++7vkK89BI8VG9nPzsjORKCzl MRJ1Skjq0KeNxTl/4k+uc1RhXSToL2EPW/7AgdqsOOQI/1+bZ+8ZmsdN xkq6kZTOsnLbvx4GM8IFKz+zfQVZWgptsbDf/RiprljqUOdPbeYl9cRt JElK6hvBcXvp6KFjodMk6YwzezbjyI+pmUy5BQFn317H36UGief0Rd7T t9lbxw==
+; resign=20460416024207
+141r.subtree2. 86400 IN A 192.0.2.1
+141r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . s0M9Sv/nxCRcj/+QW0pi0RbTpTdN19RejnoYY8KYiLJzzdThRyLmwGRg FOsEIS2IbA+zAYdiXejrGxqiGP8F6Djr9jULjVdrH9bfCnpqBv1HDB2n U+ma5apsAocchcyYJjkmP4QsDAoP0vUu+jhLh+fokPzG0lnAV0qOXZSD l8DJpzKtH0F+0vTEliF/21usW9DmOmvPKPFWjB2PvBq0lsoUjSc3v8/U oLiRuFlRnruUUgAMN5m1Oo847CJ5FV4S7b304PHHQicjlOxahhBbhBGJ nKv/wvCjnqyT2l5rkgZVHg1GIDDBt6riY9+oxoYiOkjIed8oRUySJqgg /VvARg==
+; resign=20460416024207
+141r.subtree2. 86400 IN NSEC 142r.subtree2. A RRSIG NSEC
+141r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . VCDQxwBmXbc3Xg0Dx7w/+Icjl9zLziJi68YVgJC9GqynePbBGv7Y8c9l JMETd133deacHm/KO/CTE385PKkg3hxV7icsZUi+9mYxuKMSebUcq0v5 fa+BoiwMGDHq1ypx+U8/T69dM0nB8WIVXXUY7h4EojABv+nceg2ctgdB E6Q8iFXZhQtRl7KkTYOSGYXwBgaiokx8xza90SgVInEk9RjsLrfEaQN8 yRuMD5QTOF6vx59rzd55ig88tgXO0lULDbUFwwTVfM/P/wro9t1EpN6Y lEIKzB2AG34M3d1Ff+oE1xmaKGfiM3OXaDJ9biWuHNKdmcqcVVN/QfQ8 kSBgyw==
+; resign=20460416024207
+121r.subtree2. 86400 IN A 192.0.2.1
+121r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . T49hjXWYhZI/lr9ElgAbuBilAsfDoautCeXeOEcD0lq95BqhW1IL7/Ma r7jxVviyQ8q0HjDVpbwlMTGDav/CG0Xsxh1I3fN2gRBEPauO7N6CQ6ym BEF+004dH1Zmk8NgjsggapWcitlzwmiXCxDIuVFSVTCiLQ73p/uFjBDC ML0qIy14B7RQLnMwGQN/7wFMqJTYZ8hnFnzJepzFw72ykQYqV9NhuYd9 iF74bU4q3KXb/ZxU517uRQ7Nz4k8ffuNOOankIFKjjjUu327rlycziR3 Jwy9TH9uShXbvEef2mhgisiAaNVFlYh7+i/AgDSBw22Fv0a95qyMlchs kTevBw==
+; resign=20460416024207
+121r.subtree2. 86400 IN NSEC 122r.subtree2. A RRSIG NSEC
+121r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . PB03mEoq3UU1YaNTWTv2WwJeV240u76occSIJ4ydGzmTQB5Y/K7GFlck O4vk444DRKXkq5JNLNGt26y481aXteR0g2FhWZIcR4G6ryNwio6XioO9 wB3FGZNe5w1D586Y5mSqNpAITV/kbTVB9HHM6BHNjrW58OUyT90Xyfkn uGU1EmwAIu1QXMwDpUbjqRa5hAbEhHYrLjYL7bvu8wQ1OiSQ0kbQK7B7 3/l4IcMywykDmPcXwV8gm+G3DOZ+Tr9j3vgmNs8w3K9EcRMENGo9zVEQ qlnt7w2e+P8W+avwdemx/2D1DiF48QYRnfFwvCWzVpB/Gl/JdsfDZzkC ry+bCg==
+; resign=20460416024207
+142r.subtree2. 86400 IN A 192.0.2.1
+142r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . M2+OjdCI8RU/X4ziIxBR/2aUAAXP1im94Zgp9e6clzNHs+3v5213RRsb Y3QTqGsB8O9+5B+yV1vGlit4N9Zu1Pksncx9Un5fa7pC0Cw7qmmLtFgf Uaue1NnHZPB+1ImCGJfnwHRksfA4SLaOkRKYcy4MlQDZ+kGWP9pT+PQZ onPrtYHluaoZgYSQMSItld25jomaWcjNtrAeU7SdzAWXcfwneX6sf6I/ igelmxGD+k1IfrJ2fAUcQuTR/JqDEQLWVaBLsB1D3WBC8BQif3MFY53Z 7/TizcH3ScG/qn6KMYjoyLW5k+02SA90Bfds7sbR0uD+sBHAu7Yp/L/D +J7Lww==
+; resign=20460416024207
+142r.subtree2. 86400 IN NSEC 143r.subtree2. A RRSIG NSEC
+142r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . dDa7tarIl6gdQtX/uOz66itjuyZtJ/GwxEXSUuxlqWvlI1PC8lYjlHsZ 0ss7xZRUfMv740xDDXY21r3n4inlm8OjTmMczfi/j3bbQmwENPoJKFeG 5JhQQOPuFS3PVjQnaiaVSyusmpJqCBuwPMOgmqKqoN4QWWKymrxYWWft SO0qEsb+K9UYIrhjvjmTUPUA9uerNZ8v6UW3lD+AkyzP3eH8pHP8KzUT EhH2lO4sVfX1aQpgGd6Gfeu4R12yzPqqH6epfS6N0DwGYGH6Je/b46jn bUf/tP9/3BAJw3xO+LKEg25sNJdQ4TzrdMY2gvyXYNkMTFxmO8bCeKGq I9ZoxQ==
+; resign=20460416024207
+144r.subtree2. 86400 IN A 192.0.2.1
+144r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . vouYlS0l2NOkfSjVK4qj62oRQprrUvs6Vh9oXGmlEqoN7qEyj06GAwz2 tQsR/Yuz1c1AKthS5mbbLYNVIRSvHcXwz+pPx6nrcnlcMqPLZxsq8XnZ GQfTiVOyIdxLDRqppaAflpz8NwxNcNiNrVkTc+mZnTZBhZlniebZxUPz UBexrWxUBTQShUu8G5q79PH1XK+4+ZBsT1zBXRTyekUHbIF/vkxvmE++ iBft5YCnNXyJG1aXOvVvFmpOXYMj6Q0i0Z2nghVvlBw7zU9Ms8nEwS2D GBNlux7k8bA8fyPb0x7Ktw611EZNoj9IUbnnYOcGzli9FdZRkQROrUU7 7gYw/Q==
+; resign=20460416024207
+144r.subtree2. 86400 IN NSEC 145r.subtree2. A RRSIG NSEC
+144r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . LXKaIEqlhXpgW3s2wCOozeOcuZZzd6EPP5ntrD/ZF0Ef28US1RsrOAm9 ffXeIrv7zZe3elJC3ZsMvrsGe7ue2CfdXRMHYp7X/DaZfo2BsDqxNn8j yUfvawyVfByQRzoBUa+sWx8YWZBPEwsYPj5cvg14lXi8KbMTWeT5Xtgp uacp/KfXAJo150QiCv6vJRJPuV4+OgfvG3MieJKqsykOrd1zpixwvvPd OKQ2WWcURPH8p5ozimacxdVdvxabCemQ33ksiDRWFlAHxMePkDLNP48w Nb1/Kf3Xfn6lQXAe7Sf63v1bjGPmHqNMLjiq9si9AOt6SN9/kPKFh+H0 OaGzkQ==
+; resign=20460416024207
+146r.subtree2. 86400 IN A 192.0.2.1
+146r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FIf5murAK7JG32Aiquf9v1kti6C8uTHzO/zYOI+CPI5XlwD44OtViTgC FyQUu9ZGdyg7FFYKtmLXDD+irw8Rz1g86C7bBrFPHN2KntKLK6q2K7qH stsryYKqGbSyktkUU/owA5C34zzJ5ubX1kTJ6NY7GbFv6mlOTBb2vfaI xyiguxFl1Ic1XCvlkCrKE2oSesBtjAWGEAoPFYf+1oZgZbk0OB7tNpXL MreaemYoWvICTTVSPggiWOcmWyfwjzHCrtycFxK39b0s9bafKxzlwv44 GRx2sl1KvBhB5JO/Gz+KVuDyMpb9nhG6X5oOSqoNqQ4B+M/3dA7lcuDQ 8xCjOg==
+; resign=20460416024207
+146r.subtree2. 86400 IN NSEC 147r.subtree2. A RRSIG NSEC
+146r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . SrHI4fdyyLPVIbhzkzz5um2wT4eQXAEWCq13XnZcUQsn64lH+IYDQgcU wXt5PlKQBpB315hyAd6V6O7ZKBy+NWI6l+SSSIrSX1LXsgE2OfBocmlw f9hJqsD0Pt0KDODjeQ00mJF3uWz6VSASAWPeStKy1IYe4FuYWfD8vLZ4 1ZR6T8jAgP+O0TYZaTv3C2fCNiRc6PWJn5swmJydl0VYkX4nFmEdztlO yUNkmyAXDf5yjG3rd2qoX2di7x8Kk6eg1ANlPYmWhd8NpPmbTngb3tv/ c1bYg7Vo/w8EPOUmaAy1CW1GoPwXSqdXXmAPq2rXh8a7KCG/tOrz8Euv t3DDGg==
+; resign=20460416024207
+147r.subtree2. 86400 IN A 192.0.2.1
+147r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . itMxfwAPvB99xBemNGQ4wTq6q6hshfxjC4amZmFtNdOLXOVZhkwMlhMq IG2Wg36SYpEBmNSfagYxFj3ZTz9GdULbRgb4I7sMVM06lCVfumNcllUR nrvwOc6vsms3RWxntFkW19hG2x2kE4We5P5ztUitMwmVsy4ChprDyqls VRGEmqdEFv6xgmOxCeYhBsptwt5Tr3yCIvjYb4wO7F9ACmO1RUH6j//W eqm3DDZS/QGq/DjxhepWyIgbP4/CLUUPH33+NGbCTMxtwnP7V5/afLWX DSKr7hskDKDz++VLxmchw3OMzBRAHoRkRSJ26i3i711WHKOGs/kcMhIY T9xAqA==
+; resign=20460416024207
+147r.subtree2. 86400 IN NSEC 148r.subtree2. A RRSIG NSEC
+147r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Qp5LV8mmTH/nad5/DRQ3Eja/JdZbBT7d7htQBL6DizIhdXw35fa4x+Lh eRRXtRh/HgmqRLKgyQvaLKqdwC7SeuCk8dezeJmvtFirrAcgXZlacOT+ b/3yS3xgStmfDlnpwxzuhuxoIADwyxonb0BDxZYKGjGZAiAihKVZJah+ ApVZkP0ikLvWfSRwUeMGtiC8HvpL4W4c45dNz1G0SubfktzPWaBDM+bD q151REswVl7b/TpqMNG/YjAgE09zCLoFtrt40XbDTrQLOCM70r2n46s5 JQBhoj23HUh2QidQdcwHdUvO+QZOom+pxhrBx5A876AGPr/on8r0Mi8H OYoLwg==
+; resign=20460416024207
+148r.subtree2. 86400 IN A 192.0.2.1
+148r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . f3UZIkgKRmT8OO71vS1xILRITsURjmYu7Ivf8fRv0EgTY6oM7DnpxP3b wizevoLNiopyD8+ZbpeaqcA/nvW5ue45oShQsCrhDXmzjz9JlK/4/o6p kBud56M3IDRFrEXIuaaY3WCDs7/RCs8USBMarUiB0F5QLSVz5HbPcFvE WPt8FKws95CkGXhEa5FiLW9PVpmEIcvMb82vcOMLg5WeMQtXH6aay3Ve 5gauH9e9xQD4zlW31VdyzTEoegmR4shKM+N1Jj/H/O3S3iioYbp42VWh XY7L4Dhq0O9K4h9RPk2YBsv9yXYl0ea6ONouQq6GBpvL4UtkCk5WxC/p f55mfQ==
+; resign=20460416024207
+148r.subtree2. 86400 IN NSEC 149r.subtree2. A RRSIG NSEC
+148r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . A/RNTWjoB4beOyWVfe5KIeF358CXU7QSDFSlq9NkQgM/hdOoWBoP7BfD 5i7shmE8HkH73YmlacF5dXEGXds/AoBFWxloxjwuF96+XPgeATgna+Ce fcpyfHJoSptoCUB3nlu8TE3GzUBFnHYNyRa2vOPTGAI+uaDrAbbw5vb3 ID0+ezMr2m3hHlFQ6MXfz+UwlEoURz2K0T1WlGdO+WDPXZys3yIRa+IG cYQE/iM6yPW1yorjPUCP48Bg2M49Q4L+H3HrJe+mGRxQnheZG1VENFZI D6P4PCeRTQxkeo8d5L9Lik0QmHD2/PHw58/ZVQO8tUkibo/wonIDwFRY kWFSEw==
+; resign=20460416024207
+149r.subtree2. 86400 IN A 192.0.2.1
+149r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ALwPUCoxbaiFK7OZr0/LTavFs34CjElZw1KABbHPjlYcMJQ5N6bi0kdM TQxfw33vm2rCBR1hbMzYnsQqG9I5AWDnb687d9azCpN1+0jX84o4Ku2n YqRI5o/KO6MV0vhI9BZj7WFwBSXV1AMME244vDseoG957wJ0yyjTOyH9 SJZlmGewXdr6Ugy80/2/Z+S4s7sBMSe+dkLkh7ctzJuoGMHIJIDr6mrV TTGvhEV3C/Csl20SY/qzqNQMHCBui9CqPE3Hqs5ExdgNcQVH+VIcSrW0 tqaXUx9Ci4OwKZMDUuUWktKlSl058+qdfileGbSLINvIrewwZJyunPqi Kz+0Gw==
+; resign=20460416024207
+149r.subtree2. 86400 IN NSEC 14r.subtree2. A RRSIG NSEC
+149r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . xhBfQabVy2KIGOr1RLN0IT+GU3vhDaQr7fabs+VQJfLG6Z0xYzS5U5Ti 4BrhcRsIQzWQOPKnzUe08o/pNVCqkoP4djCziCx0eo3UOZd6GggXhqP7 U2EGA2YvbVuCXEScq6mEMBZNgTjEUVjWJ00zF4IJiChrfHCmK4H5Eijz h8wGd/hxMn2ZFpYGAGZ1nkrdzoJjsEF6xtUrptTUD2MT+tWKZtvDyl4A MNyj/rHOkOTpSTHotgWDXwdnmlRK7AC9LJPVwO7gebmMFZd7RAGVoLNR PJvpJY08exrpYwNnG1ub146zIS5HQuJDH128JGYaeksuJWczatJJ9OWa mrJemA==
+; resign=20460416024207
+14r.subtree2. 86400 IN A 192.0.2.1
+14r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . wAVrmJPhxufbdd1jzJXHSnYpQVYY4Wu3vlVNwqzRhi4dTEJcsWWO8SnA k9MXqNCnZKMB4KHMdr1XMqHc4swsSrzKyqOLSPlYBWR4YW5igEtgtNV+ VGkWNHBQsDGxA645mRVrOyFAFSL2GMK87IQ7TGXjFjssxUlYt7zoxt25 Mw4bwZi8oCi8ybd1q7IertVelqvWbhohKbKza9suVxArSFjayrThIedL HHd7yWncsKCwLLuK5+ZrFJ4LtETTbdPC5hybUGsJ+S5migqr7t4xyupe m2ZLmHOEVSlzexa5YuwK6UQDgCfUpBWeBT45gEjU5ULJ5H7YjFooaHZG 1Ao7AA==
+; resign=20460416024207
+14r.subtree2. 86400 IN NSEC 150r.subtree2. A RRSIG NSEC
+14r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . iClXhruzeEz+KZrnHBbaDf9Au0x/txCi2DbSSU7Kt6E8gFsrTg5mZdHW WPdJOC+vOGqoSusDlp6VAr4aZitRLXtFaNOBflAMogsgLz30hdzz4CXY jklN4ZYGYWAudbB5YHJtnSqqyjeuqeEL3yQTfX9LkFgz7tPVlQzbnMfZ mz1qQ3ZWFV75h0CTs5XlZgs+n4i+OqoiTTdf95kcwqxhDG27eo2WxJiX jVXWOPe/vSermIgYzTvgW3EiTS8H1sFgPL1kH0u4qSwk32oEQuYky8wV dLwlMvywvFK06hDWDJ3k7s986IOWenYxyKtusSnsnCAwknXlnoyc6AU6 EDpAlA==
+; resign=20460416024207
+150r.subtree2. 86400 IN A 192.0.2.1
+150r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . pML15hMe0UtCSkZozDf3GPKnCkEc1ZshVtv4K6xuxdf68H9ND/Wm8Nlz xKwJeuzOJKuesMGOP8iB4+JBuRNzP4ONCNbUqGdAoCZiznW7l1LwFjXG yzxg/JkrzAIMf+FrvGycU3m/hzQe7w3VzVrxfs2r5GbTwAnrvz8iJAp8 pqwHwsLtLb57ufLA/bQprhaEC9HmlRniBBIZBp2Sz8hWkTNySIgv4CuV tM+XDvI1l8wC7599BWdKa8aZ9N4kVUiJHb2vTuSKrgWJ5yxb6iGhhaAP J06+1IiBjF2JxyzrdDU5xUJjAIYREk6NCOsVTB7j3QApZTFRPc4lGrRe KzYLtw==
+; resign=20460416024207
+150r.subtree2. 86400 IN NSEC 151r.subtree2. A RRSIG NSEC
+150r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . t/yZAbAAQQ10vbM3j38zn8lNH0/hcFQ7cByPn1EoVu5SV5oLy0k1SRtO JdF23wXfENum3Y2S8lWKURLNPJBF3HFS0CQhhSLpQaLZgFR9L4iugiUD SssxUhKZEQNxCcRl8JXQyeVCqGhoKEJig7CoVmw8/V5zZgg6ifpLdfVD 1r5fbYg2VV7PQbcpK7QYEnfu++38XErQMOrN2vO6XUdPAVxQ3P52Bnf3 ICuklnSznX1cVWK+clEjMez5/GdI2DXmiLKOL4lcxm+RYx8JBroLgE7A tL6sk+DNlxi4DlfT80+QIBrIHRptJN/IKMolju37H7P/1kC3LL8LHF+v 10XgDg==
+; resign=20460416024207
+151r.subtree2. 86400 IN A 192.0.2.1
+151r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . D7loEcCxYOJXNKryeIFn4CwBWMRQ+n7z97b9Q2uBhhmWJm4jhjL/sDr1 eNLRRdIjWzHyZFt/iXEsN+CNsPw0iDvUQkeRPSeCpwXoY20twh4Ogb9z A6HyItif07zVVM5DAv0XjznRfC/VEQ40MvRWV7UvnFdpnRnl+6w8p+dg O+nBNCx2TlUEG4tFMxGitU8M8rD5P6G7HLRui1WeY72B3E0GfBR9DnxV 0kI0cdHp/hbQOviLYXmGsMB1GGVpN+ZNtn9sZhBjiXNLkelEgTX2f9Yx QcTy0ELv5BrtTut/L4DxCNhsu9lO6EwYc7G6pcBIwyWpZne5gYVePNKg ANfHBw==
+; resign=20460416024207
+151r.subtree2. 86400 IN NSEC 152r.subtree2. A RRSIG NSEC
+151r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . S2nBDfiRUi60h1AIMQEtzdQ3K6iCewWCSRByQVG3E37GqTXL5jt/rcyP tLYRr8pSpN5L8q2WTi3mtL+Wf9OIlaJmNpasyEscfeSGHvAoUionGMsE OK9WqFn+9JGfRLwkxUNa2SoqbZsegvUzxgFabreFncgQL4tSHaX1sI6L +bL4l25bc8vHXiyCQa6d4aSCBa/5fdlZUW4U95U6mKl17h7eLWJ73Mv1 07vZfOGXOxtA4yuz+YXTt6nRcZm6qj55j6bh2PUFiUTKFI7sGAiqzik3 MJ24wOLOHza8uydnkICFHsqBptAnlL5Y19N7IumoPzsR7OeK9DvHb1a5 lNl5Wg==
+; resign=20460416024207
+152r.subtree2. 86400 IN A 192.0.2.1
+152r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . m0jKSX1mH2X3H0FBaXx2Nm5JCiwEimWXKyxqDTagax69EdxLmpV03k47 CvDNu9TULfhzkcAn/aNRJ+0XiL29Y8DMhRuZW8AkegbR3HV2cSN/5Fgl QfOg4DDVAztIOij0PlKSQ4/nilPFgSsNTXM4AuDsW359t4iH93FeUvOj tNSVOdXh4JeVPQH7NYuhOSUXuFNMP2ScX34D6Bp1W4p2yWTRVLhSWsXE RJGtLXAjnFldVYaagXDse9Da7rSm9GjmY86skEv/kkzKtLK4px39U7bZ wYiQJ5ZnBQCbZNLpefWusnotjjF8C1xl2+MLtwUkI6hTe8+4VWZuf5+J ABrCmw==
+; resign=20460416024207
+152r.subtree2. 86400 IN NSEC 153r.subtree2. A RRSIG NSEC
+152r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . LZY2vg0maCCC4Fmm3kvxMl5gRyWkVwmtWeFfvnLwZy75QJyGZSxT+Rj9 aDU20CeOzsW9jlAUM3iB6Pcmn/smjgqaV/RJ1kcoIUh/f89UWFn4ZXjv XZKSqHaI06GABJXtnarsfiIli1W/bCsQIPng1LLqtl+mV4SEz0ZSMWIc WBsXfmnFWOj7oO59UQb+CYldaZNS/WfpdX75OA00u7sQbIvN4ASKa1iI /UZX4eyDZ8Z9dwQ8Rq8XRFFUb3wklURHA2jF8nCklcbjpcFOiq7cV9ka 9BD4fOrVnkYTqY4Aj7vR05iV+llc7dk5g0SXMC37OqHIrY1uxh5toDb/ cthv4Q==
+; resign=20460416024207
+153r.subtree2. 86400 IN A 192.0.2.1
+153r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . S0fTs2ZCGiI/bMHo9YzZgYIi8MDGOcr/844OdRhuFcxvoU0VcxU6Bhez AMjV8nbD6xH1iKx/4jhoUA6VJms+HxO0DHDjudNpZhLQWhS0UJPF2X3y vUG1XyAZC3T9g9FnOVNbEi6ORSSZ77ZYFQivlwYoIfr5PGJ6MkeOUvNb F8jSi/c+j1rAFXkPheXKFLJbHJ2PT2p3xqiwSOWX1lYfYTCzRlDrpnqb pjN1t726smKfUL+/snXV5r047AGqqvefqBIq8XmUda8Yewu978Ocs1OW ZrNrBDu0x8k3iQshitgSW7ivk/SDSw6mEJHLQhIBb/vyAt2czsHC6jfy wRAYbQ==
+; resign=20460416024207
+153r.subtree2. 86400 IN NSEC 154r.subtree2. A RRSIG NSEC
+153r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . gKkuOJf15J1G0qDffUAjjoSmp3ychacQyJw4YihvCfjZOWtvNJ44GGmw 69Z+dVjmFNlpARRVDRvMANZbZ8jQQ17OfH0ZYA7O3c9FdiCqibppR6Ai f2221EEOGjejgXxVpum2ERPPnlo5XD4OxM0mn7Kyi9gjsNVvYBvF1oo+ DmxRxuKvz/sjwhhYjE9sJU3yIR6EXj5uDEsaX0Vv9G1D9IfkKPqS2jxw tEIWPIxRQsssfZQd43qZxtIY9If+CnFVqvWmWOsmmpNdHLeMB9UmLWpd TFV5DuMiWQNWytdyEa/d4k/QaPcy1d7gDU3w7qXcsTru6+4mI3niq9FO JIlw4A==
+; resign=20460416024207
+154r.subtree2. 86400 IN A 192.0.2.1
+154r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . C1RSEI9k9LlntVROIpuZn9p4T7uMLcZtNQFacghr1qw4VeJFmziMCsww QYazrqkbIbwDufP8d+jHIajMtcYhXm+bz5GSGPSYFk41PzQbhnAmTk7k R79uIjGtdNg6tc3pzKC5+Op0TBLQPjGOzUkvGO8Z1VheCHdEumYBXw3H WmNxMvIICdmsfpN+CWfFk8INHhhw0AKSjW4mLYH4w6SY32D5km9SYXYQ 1q80fHtm4v6iNBUZlsYF2QmiIMXjgAnSbCwSYRv+9yt9gp7ADxpA4zmN gGLw7AG8xC01wYcDiziEkUQJKdPpiEcsTds3Kvc9UqjTYObEbXQelxCW DY1xew==
+; resign=20460416024207
+154r.subtree2. 86400 IN NSEC 155r.subtree2. A RRSIG NSEC
+154r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . GuFnrQeKviEGN7YtsNURisuq04tauVEnnOgAaAyAFX96WAH+pkAHeyPG 7hu1jbPkdEm9kA6j6uZEQXe9WolSlwuCfwsyhsnaXCNesrlRntlnBsRr 99wglXg1eerks4/uDub3DgZ2rtBg76hA6MVu3afBJ6xopvCDbCJjk7Kg PXX37PcCgOcKuj32dDn6GCNIhf1ZD80LB4kIZCS76r7KSj6Arq9ijWYp 7bqe+6rmfD8AgdICMYn3E5e+0C2hqSQLulrLNfQzY3zXkrLXXN94qbYf QiWW0EUcf/IHb8kZerQAYE+6o+hh/IuD1T9mWJ42ueEigd6kCdK40gBL 7ytzAg==
+; resign=20460416024207
+155r.subtree2. 86400 IN A 192.0.2.1
+155r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . gG82211egHdWycQMwsz6FtX2DrYeuaQ8mS0VCek7UIaARUhhdTfA0eQI LlF7aTFfT6kzn9VuRvhRFkeA4rVrGH4BWu6f9TfEBiq2C/VnV7j4eyD/ IOo0lLvOlBJtOooQiQ7INEhTF3vCbboZQJntm/i609vZXQFlSljnBoia uTJkeGn+B5wqVjwuKPCGrInUGy1L441B3ZV/eA3sxbNQXC+5PMf333zU N3puDUfMl6BAF307t9tBcv3LeDB063dER5uZkYHvmQilddssX5yL4jRd KR1VJ8Gb2NAQ+js0KsZ9M5/7yfIEQCmG2qHmsx/bz3niuHC8YCKSJPdn K0lAcA==
+; resign=20460416024207
+155r.subtree2. 86400 IN NSEC 156r.subtree2. A RRSIG NSEC
+155r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . UhDOIagmyaTiFRApGFkIYoKtM9/GDCRDVu4bAjbQqfuayyFE8y49piro zN0Of62liFjV8DQl4iCQ4uh7ENCe+bCefnnwKzTHgu0noDJz06AZ03vG A8USwN/NDKPuvdplGTjTxDc+07obzc6jHfwgdIIrmLdJwYk+fEMY/1qu JY4sdh+MklPZmSKQeaW52Re0cqX06/ChupapJzqTTIxS7j102vBi5Xmu yEz7YQ+j6FL0zsmdzq3t/wW6alNBRzW0HHs05PdhmUClK/PMj4NhFUyG Le2VvtAipHfjw+LLWvAWaR/BJn5WslGTZ2mHfCDegAG7/K3f0Fs9onc3 o3jsdg==
+; resign=20460416024207
+156r.subtree2. 86400 IN A 192.0.2.1
+156r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . exFzVr+RoPKSjpAmZAm1ANj8d0Aiu34YGyKvQ5j/IBUYzE3Zlx/fl8mN ABwh6JXp5nQMoRanVwNBP0JY1GzTxsWXLAfwW5LjJq+t7m0o8jXQp/0M dI4uA+6r29dAqHM7VpWPVNQGDFv6hicM249LU7VhPfd0UODvSEQFqPD+ vLj51gT8ch/gw6B5OlfY+eOLQ7OedWRnmwSJQP5gDCKPxutpmapm/MKY YjcONphDXIX+OINVGfewkq+fqSrjbqE0HTlaQMpwCvjFzk54MzI8J41u 5VCWFwVcEWAzU2Q8tDPOqNen9vVph8ltTSznqAsNDR2Ifzn113uS+jUZ jwuZfA==
+; resign=20460416024207
+156r.subtree2. 86400 IN NSEC 157r.subtree2. A RRSIG NSEC
+156r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . FHLCW/aHphFiJgzIrRrox8/5Y6jsYrhCtc0S91UAI4XXLwnNx/fOhiuB K6+z5GAF2GQiXR5O1EusZrtr5Wprp1vYOYaAZ8bzR58fgvvWAqie2FrU iinDTzDiS2dg5UDlJGts2P4kGc3XJq745EbpQSsicsELD0+XCHREv7vD 4AfohzG+RhZ2iGl+kBbsEaOPUY+3FmSShvmyjy1jq3dAcN0ygS/vHFmD ZHomYG6E7eFnWYB08cHOI2BFheyiDwTHoA7hXmxvMeK6yw9e7XoCGIf4 rliOKjRHabC/Q47kkegKUJYJvVWelvcCjTjF1BIEYQlcFQcmt1RMF41t iA4s6A==
+; resign=20460416024207
+157r.subtree2. 86400 IN A 192.0.2.1
+157r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . LqqdNvo9e+TrHcQFlW+GBbCcheSlWqNwfOhNH158MwhitwaFAdnVOTT5 N6KKCOGX/KzFmYWLBtI24agapHop0lei/wE+5UZkGTRM2OmDHm3rx8JR kHrhhmlPaNfvPl4xPKy+G/o3ddOaKAEsqW6FMTO6F/8AAKvQ6qmxBvBa lJFtz1YQMXnxIgzqfColBenWKroKlxOGW4WvdUTsfrGHCAl7daegoBuO LHp8b/3Okwz1Khm1USCp/G29H/bA4TFZ2AYJHFSsdZdrUSJGycQh+tdL DEx5Maw5pVRBP0sO4gha9VTL3+tEvCMVMeiojM3YuTuUhBUvQXwchbgE q/UoRw==
+; resign=20460416024207
+157r.subtree2. 86400 IN NSEC 158r.subtree2. A RRSIG NSEC
+157r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . fNbdH5MpGI4J7hSnqnNAVKG5WSCvRXQHZotkNbEQwShjwjgXkt9xx8OU fob5B8NKwpG3/+QADxCZRN4krLNCb13idukKvJ5I5w2GofTU/4gRz9fn kX7U6Af80+k1Uxo0APhaXD7d8HOagTg/vIXNmaEcgkNQpw7W0Ve82e3+ +y7g1Z/Z5Q62aHCHi+P4kTO9fCYmmeQ+usGeKdTu4S3LSwNb4xd77stl 4Kl8H4KnRaMZwpJqBBeZx8QDGh+yu0YfV4RTt5GG5MimOyO1hDkaO8QX bGwUYPEL7otH0HKv6EAKSZrX93UArwljjWKSOeK2pbaGJmlaVffwRpGY 10yZjg==
+; resign=20460416024207
+158r.subtree2. 86400 IN A 192.0.2.1
+158r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . GXms668xIDKeVeM6+2Fm/8TaRDcrZivaDUQvTtDjlbhB05fABVPpwUCV MSc27hvyxnVvAro8KgZuXrDUCvIVySR34c1hZRXeOt2U3BTLTHDlUdtg 9FBYobe0Rv4jrdueQXeTGgN1JrtAE3ujQwrFscebgckWC7ZqIOIH7OKw gCuvPZUdJFsy206TNEUjiTbZbNx6uHkvH8EfPI6qQektSTDvzDgBmaMD 33a7xIOAANmImj9KOKx00XeG8iokTKmEr3/B04UA2ToPf4BK+HVNjTwD TY06VSpfe1rA2eYLSX1dD6x4vjomrb7O5fmCQRM53OnvEyP2rRWYAdgt U5gFgg==
+; resign=20460416024207
+158r.subtree2. 86400 IN NSEC 159r.subtree2. A RRSIG NSEC
+158r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Y8OI/fj5Ld8cr0vH5iv7l+0nX2faZoWoUBb+M0MMPorCJ8mX5K9MAxSg Pr2pPThIIbvX8oEvJl/1TwkEKxZXcdBXNje0vMoNjEJpgnRvKPgbuvrV aTw9gxh+M0Mde+nWnMZBvhwVmDPrtfIc7Pkbj2LhzENNzaLbOckQFT3D 4uK1Pv0ikgdMiA5m8Pi3kD+vF9wd4HaDio5Vfjggt0jrBHQ9Iw2s5bdQ dloEmcumOAD+r1c5ut690r9TrK1nAbgqNykg9QtxIU/rWQa2CQy/UrcA s9khzM0pxu0QbFNJ4Z3XCqpoS1X7Hctb8iaPYMhzdwmsrUg1PQ7Ay85O hHVTrw==
+; resign=20460416024207
+159r.subtree2. 86400 IN A 192.0.2.1
+159r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . HegookV2cV09h+mzSBG9UJZ0LbH/MtVnYfnHFKmSgebBi9K6ZZ3/926o sBYj9LrBRJgcsGh1l6h11uXN91cJfSFGfhQs2M7Ak4PYlz4LiV9GTIfa fJTz3kNWs4bu0ljx+heIwo6+2XF39y1rrMLPrrNDB6FYmbs6QKVZxPc+ MUEsorETFz0g++XsunkrtyV8YzNZPrfN8OYa3gmh1oxoBdoHDKe2/+g3 dR+GRHOHDHkqCeleuLUXQ+VN6lxN+CwZwuFqpG1dCDaBIMd0Y3dm0S57 D7txrPaO0qLZjXoe68r8AlgCEJX0+j+R+VEt2SmtLSj9zvxAWU3vxXXi ah73xw==
+; resign=20460416024207
+159r.subtree2. 86400 IN NSEC 15r.subtree2. A RRSIG NSEC
+159r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . bCW3CfR3gNTSI7PV1vTJZEqklFffX7Sd8SBaPVtdadTnD+G/hvpKz0qC na+aljGCxHX8qCrs5l9i+RJ5ppC+rTdf4gWS8D2Z0FK9xFvXELvjou2Z hfFmyOXwhsSejVLpPGhbbQe7Hc2zBrShIWRjNRfBbMC7DfpBVY6c26l3 7DpzSltrzJ+hs0wNlCa7fd1Mp2dMuAIf4zIVPNID89+LWNdOSPAmyev9 eeLvhvl+bCbV+JKsEFY+mBhH88iLnj0NX85+s9kUK0vnXbf7sSIolq4u 6m725ur/k3nNFTqg7F7pxd15Pa1LLwooDVgFGfCNRUvY0ScdhdklbTzT fBUL3Q==
+; resign=20460416024207
+15r.subtree2. 86400 IN A 192.0.2.1
+15r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . IVVUyvogfv20mvIHVFB4Qh2sRM/R4AGiJWllTZDYa3hvF5iFfzOTzNpN GilwUSnQ2pg/iHhJhAFdOtz6usAvOrfQGlWCnqyKbKpDX2IVNTz04CBQ VpK/nShPRTyAbmuoqGmxcyx9BU0gXsUBJMDqTNxllv7ngVgiRAr29uf7 xzGQA/3Xf9d2g1cqoZx6yzoc/OGyu0WCd0RuhxYE4Z4qh5WPfJ3Lf2xf uDg8RGQ52+H3TZPlbbEIt7zr0HBxNzfsOjK3oVZB7ep4qfeH7QMtlBLJ HEpvY82aoVyq2pXCG0hRiCZyvUPPgXvGdrxoARAA2dLk6fkHzXnhn1kD SsKtPw==
+; resign=20460416024207
+15r.subtree2. 86400 IN NSEC 160r.subtree2. A RRSIG NSEC
+15r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . NfFA7jC6LmcZFCe7r0lggnG3N4GQhOgfreE4KBeqjzEaubmJm5iDJh/5 lDoFrQvrXzIO9XCjBpBiTKh4/TymmiApdJs5yEtFo70SwB9aDwwlBznj 9aNtwPYRfr20jfqScBRS5dn5IyLv19jOsFZJ1ke9utWeVh8zJenOMNC3 vWcZuoYwKPJx5uvTIkECABNtNac9ruA82+Uyeq8s3EoUCY8rcc7I3MRI ePRmvxP3ftF+kNZAmuw+2/5V3IKTLAo3BZ1KhlJ7WZw8eeI/AmpZD91J +LT91MzlYJ8XOu3xmBmBhtnRZnWkTgIGqihSrVo8jFQj/qhoYUl6WBDc PJdoEw==
+; resign=20460416024207
+143r.subtree2. 86400 IN A 192.0.2.1
+143r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . KXET0omXBGMO+1GiBQdhTrTZx2rExg2l1uNCNOlKbo0XxZLdBNKmG0iy VEtRDM4AhUfLQ+kDSNWJIQkBIhLESjOSp+2+oe+xBlWvgmXaYxWmZmjj jPYxdPtJoxGFI8xH4QGp8hjR9al+gGeM25Fsk3RvAzwQTVGnIP2aAv0o Zn5Qt7533ahGBuFITJxu6UCATY96EuSIiColKcmrcQ/JBF3eOcBSWyW0 Jr9TM+SYJNtLsbVP0ixjvfDv8jKPh66BkPo6GwyJQ/rb0AzSR7balPNR GVaYWHPcgkx4g6S4h/PvYspYiSulfJ1JywTSDTXptzpOJgH/SVwwRz0o PIqGVg==
+; resign=20460416024207
+143r.subtree2. 86400 IN NSEC 144r.subtree2. A RRSIG NSEC
+143r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . t2j1ZAGhbldo2YnHOWPGBQR/xp3KjRqQtt683GTB0R1DePWatP4m0fyc HnwCk+1gqp7toWFip1s6M8df627pZJTAEehzjBY0KFsoFhfIYxUIO3ut jhbsoYr5N6CBRSDfpBCfHCQNc+Tv3IUQApP/4vbfxGVUIUf2r7rBQrnk WYOv5vEpU0TCm30bQBZVEoG8VUNY8mghazonDTHaqzc13ga6LSuv4Qib cpoJb90xj6QmOfxj/QB0KNaJ58BbM4JcJWOGDdwF8n8yRKFJbpr83EYL 28bgBI3yMp38ZkKjcUb9JDRj7YijWG+LF/nGT9VB9RvMFMz6Jt3D9GhR ISD5Qg==
+; resign=20460416024207
+161r.subtree2. 86400 IN A 192.0.2.1
+161r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . vlhrynsX4HUZcUpaYIUJEHnbofrhvlr6yAIa43KctslqB97zpfrZO+RO wg/z3HCXeylyUixDpXmYr8CMfKmARFbQtuBPJlDBhRpEi3V0QsqUjCFK FlK8VHfwwn6bF9eCPi9LRVdBRXSJj0ouT0mWrfg+yySBHZ/OBXtGIgA9 aCYvaBZPBVBG44TkuoDaJkRDP9asgc5CBOHX6V2FE6/iHYAe3VIE3bBQ cWBWH5IiBakzhJ9qRLIshEBNiGnXF8FEZLTZqc71FneY0+oL2gQ0cvyH RAoHeAMTJlqxDGd6jqyrHiC1yFTq8t8x0Q7Xyq11sV+QJu9TXZPoJv1S wlLUEA==
+; resign=20460416024207
+161r.subtree2. 86400 IN NSEC 162r.subtree2. A RRSIG NSEC
+161r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . EmhrDUGPlpEcrclo+ijJp8/ge5Em4gKy/5MmI7OcflL3ai/H5iSZdqGU n7+3eaM9gkFDQk6bDy5YnfvRZ+Z4LnhM3OYsDc/CjtpU5O7IaAjCneLV HuDuyqtWo6PZkBXRiE5013G30OZXNXOee+75aP+OBvHZxYUmQO5i61bu qhQ5bR4KOQ3/EALyBqTClbJHaYtrCZLL3D1CMK25zV6Oiybqaf85uUzU JpoEl11poqNhpbGvR5oWQUQl32GgtO5E9aMADkqM9vFPjTuG40wysV7s k1ehSfFajlN7sIibEIJaHSeYklMA4MdnoxZa25AErhlBqYfA5xsOMXj9 0ggkMg==
+; resign=20460416024207
+162r.subtree2. 86400 IN A 192.0.2.1
+162r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . KF/Dz/lWJfd94GoA9l7dI39358OyKmbPh5AU2FnmQah5UPIvMFzGer3E Is4KuzGgrpJ9mRnq6zb+OU1WWT/9uLVGks5F0nMldd0B+dIiA8jg6M8T vLz0RePiK0DloAvYb+4DKNGJXVfpgR5Hp4mKGRHaU/WifbG2m+LEN5k8 Bl4zw6a45IYygqBkFrXijamNjzuDgXQn8hl/D0Qmvp4GjqEQedjVVTu6 oFkYS1dClas6dgH4Pva2Q4bD6hoG5imtFnOBiDUc4PmhUQ2tIT+bunFM XzcnLocyOoJL8lFlp+qd7cM+bWO6ZgECTYQZTKVt4duvUZFF6PZ2a/uD q8vbDQ==
+; resign=20460416024207
+162r.subtree2. 86400 IN NSEC 163r.subtree2. A RRSIG NSEC
+162r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . S+jFQ8fn0f0qm7MQNk6YG3ySVgaFWO4syGDeGQ1DB2JwP5VwiCXu4yyd 0yDAH/L9DMIB4+cRenqaXAxk/XPLJvUaozJFhoD9TrhwQLLnvucOP8zU JKch0Ue0AqkVg9LhUbxSCU54ggB+nSnx/Ys+io17WDtksclnC7mum1LB y1ZC/k7OHToVSvyDfByGn2r3H1Q/7FInUM475v32BzjGq0wfHcScf0tS xZAfgGbYch/VbE624LpCXKHuS3J6L0RY4H0EgVI8nblmGPWRIDDWvBrS S0IQIWX7lxbL0PivCtoeNRE01WFum3GJ6bYD//aPKdJUkqzlhZ8c7Rfm skWoew==
+; resign=20460416024207
+163r.subtree2. 86400 IN A 192.0.2.1
+163r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ryyFupS/F9mcMBwD1aJjXDGjREl0TzemgsTAipvPW5lY3o7Mt3k6Jmxe MGsKtrSMXWoDtFzJFEjkiNG6gMGMaJ0RSgZUotJdGZru/wK/W8uk2oKk 5mYBlSgGjt175B9zSYBcn9rot6M+7UKCY+bT02r6xhH2ckBw3HFjs3Ht ZEhj4kawEp1EaynuKHHH20ZklVAQwUBuqbSmoS9/HTwU1nS3f6b6OMGN BesHOKn6jQlSmjDvJ+a6cj74Waql9RLEEKA6HaZzfCm/vUo2NyxWROQh IleeFtokoFEkX8vWoliQ+6xfx2yfVEJaajcW5JLyBc6VI+GUiRuy54VK E2jFOw==
+; resign=20460416024207
+163r.subtree2. 86400 IN NSEC 164r.subtree2. A RRSIG NSEC
+163r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . lBd/dCiwqanWqm2GC3Jc2C3b4r6gsydAabLOjgrkZE2MyxhJofsxKl4b 2TBN4094x+OdNV7TvYmE3i+/rWJKMbGdPFyZNfn4naJBKvCyqgqT+z3p 7XdOzVfpQ7xWCG3RKRymbGWZuarPvaEHLLscOh5WVG3l2hJ24zfYmTQi 3sOtL7S8Y9BbmdziWHcclEKB3CFrrhiC9AhPLPzcuiP69kNZ/G41Zgl4 c8CRNck6S2OqQr/sysZVBVkfygfw6epX6KLfOrXh79Ouwp6IrpG90Sam a3g9OjHEduivMnQ7GjKPZo4xTmwtF8WqWlOo21tEW08BZG3Gb+hHlVS4 6K5JAw==
+; resign=20460416024207
+164r.subtree2. 86400 IN A 192.0.2.1
+164r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . TllI/FXJAkY746MaD3p6Zk+iJg6U05nR1USTnI4Gb/IcIJuP+W9X/ng8 e59kvV/wwH5qBBa637HBOWIsop2aneKobs3Dlg1rFuCVwgFIpjF3Lm7W jADJvNKmp+sds3kk9tCCsQ515lxtQKCr17+FhPV/qtsZHG1A/h6apCoF jYCGgyr/00b/cIPpn7SYLOfjKYx3LW1UZE+Ap1izUYzYkmXRcSY7EEbs C4Dok8Nxu2d+5rnV95mbMJ568RWyFpPiaGYLgf81mOLXQKy7zf4LERh3 /gTzA+RxxBgIV8xAj5OWoCxUe3fdApDFkHisAWc+7ybqskXTyvDHeW/V /npJRg==
+; resign=20460416024207
+164r.subtree2. 86400 IN NSEC 165r.subtree2. A RRSIG NSEC
+164r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . jl4i1uRBqCLOuG+nVhWrEVdqO2COoP4+1IwxGeYuwUogG6qaJfF1iPZQ DZx1cfl5OcyfCKSB4mvQcueU/9vpkHzHKAHmhQ1Qeu9/yKI8du/fizi+ xRsMljgrKWcAjCSrUwIlwghni1fMI8hUQeyQSEGpAl2X3KGQlNik2ijz q+yEIz56/kSNk9vb7gwT2MrFMCA5IRvHZbMXnmAQ9dSwvsdvMtFfWxv5 JKadYIgEw2LD8Wh6kmmR67l9wdzvh42ry+alKsjjsDD00v4wcDDasII2 HkVguKEFYhFQYiS4/Ww1H65cLcfk5jiyepLyzBUIcp/BesXJbcDq9A49 qfjFdw==
+; resign=20460416024207
+165r.subtree2. 86400 IN A 192.0.2.1
+165r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . XJq6gK7VZWwLlks73ujiv54rlxPRSavX6v8vI8BzE+DjgJKcnxmvMkuU thYSazPEq0awGUjDFthHXpRCzpn2KhKaOS/G5HfQmQ/IJpfv68usCpdF UQ2I0MXOb8vbawlG+v+F3BhKBH7eEK3M4tS+TLYK371n/v+PUfC8kmYj cR8LIH3TuHpUlIZenSv5gHJNs04ds+U0wpguOWi+Av2PZSuKMBD401s+ BaRSRe9B7l/eW7ZejuDB3NtIaxZuo4K3rlzBhI+FDKvLkTqUcYlnuKQ7 SANBCV3Bns9tb3vdze5gymVDpxjgbe+18JrZsznknNesmB3bmRpgEjrK fm011A==
+; resign=20460416024207
+165r.subtree2. 86400 IN NSEC 166r.subtree2. A RRSIG NSEC
+165r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . jLZzn94eAVa0f8gv+Vc13Q6qfYVnZIX6fU4YO7EtazZblPBbxT+zbdGn xNwlgMP57ulAgC4sPt5UwbDO25ulI+oUghsjDwUpvLyPtNF4KBqkj/Pt M1B37i387C/jrlMW/RLdW51E/qV60yHqmJdi/3rW46ybieYohFy7ql76 DTh2qfuOJGxTem/cBhtX5HlpRGDc/5+W+B7DtoxGyMFAX14qmjv9qjfG VzNXS5dCrxOo3JwfrKyIoI75oFTQhAj3gvUgpIAlNr1l0D5/+v/0XvA7 0ljGjLEgKTxR0yinJDwC4YM3PFT22ErsgE8lQw+jmEuGS4LNk8vs9O5G dmv4MA==
+; resign=20460416024207
+166r.subtree2. 86400 IN A 192.0.2.1
+166r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . gajJKxjgPg8BynOCkYsDhGTUZgXw58UR2cmtf2poKlsQcZEuCmznQIIX Y2lYKrggKMfljx6QUCfUh7y9G8RP0G5pE+//U9VuGdhoN3K0eaGhS1ti yTrFN1eO9ZHPpCBfAR6F4r8d5AsEFqDtXqJjvc2BsqaO5wHyF9pcUWWQ d3hdCgXYU7jGtGydEipIjDLmwMk00ab+it0N7fJOehkcfY0GzPyOGhP/ Ix1WhV+7PsMMevKoUYCMTo4k3ligZf5v9dOZ83OKJwRaJGpODSJhv7bP 6/KBHZf/iM8OWONfbynh8AsARuXQ1jJmy7tvwBXqGCs5EeCugDvXgupV 5GKBuA==
+; resign=20460416024207
+166r.subtree2. 86400 IN NSEC 167r.subtree2. A RRSIG NSEC
+166r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . oGhGxYrHGq0KwOvD0GJ39nK3n349p5PJwh1nc74njLxdSIK8dU9ohOqA +0MebFwkUgEI7gmv5YGsN6QQVFh/Cp/hxFR9sNcS65csNann4sH82Tto uXVEsNWgX7da+64CUvBYDeVPN5isUFoyApQMMohKWJPvDeR2Bs7ZEEY+ MJHbsFICYv2vh69a7yYXdQsVTRsVisWYkXQpyip8uhus5K3pLt1YsfPE 72lWzWHN+bQKEtOysHPCb3ioXDBSJMmS8G07n8bRcbCWB3qfe6LwAwzv HkhQP5rU7KLNPgVOHkHnLE1qTzhi6m4JtivDPvHhC8FMIFYjmXFnd0GL jW+Kyw==
+; resign=20460416024207
+167r.subtree2. 86400 IN A 192.0.2.1
+167r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . wclhILl4EoyJtbRXmxS31p52DjE8pzCVUyJS5TVcCSb6otcVJfabgonj A1YIPPE12usQoHq2yomquQEBB4t7pY1m3WMrp5zW88d0nwq7orbb5FgO iz9vgqsDsxnFbqnLNidDZTiWEhUiHVhvAfBhosdQwggnGM5f4J6b14eg 1QMbca/DOieXPRyHs7wUBza7RsYZWhMUqlRRjvVPxBlgFqck9lziz1j3 hOi8aZUfsiO8GGeOhj2smgR9hOBxqHxJscEYGD3t3oTSl1fP9gVuDKFc myOKmWJM3c6UrREEyzLWz+7SuNh/CbhCKLTh9I0H178nkiQk9EJa4ywI QugAKw==
+; resign=20460416024207
+167r.subtree2. 86400 IN NSEC 168r.subtree2. A RRSIG NSEC
+167r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . tFwQlrO7Nqpu3vU8jn9WXP5NdG6wC3IXY2tfLxRQELqpjRAXEBm/drRH s1MS+dNyF0F0lLucmtl9j2+Iu6h0hkXIIJIKWE3zbCAmYeQ3dQ8JJH/J Hg7gHBv7uqagkTFVrV94LJvaYcFkZBLHrPe0/2vOePO55q/AIvoXkegV A3gHXfOlktFEGRtXw1W4aN48jw8qZ5dLleaRS2k3S1ePInlyAwgNbpSf VDhIKbhucFodupo9MFWJ5NKfSI9NvwH9yl9G9pMeEPJTw/wIZ0oCW5EH LmTysBH1EMxkGW/FVPpD4BEkYn6ImEfdVj+z+b5aLU74N/GbB/qn9Q+T S56g3Q==
+; resign=20460416024207
+137r.subtree2. 86400 IN A 192.0.2.1
+137r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . am8+/B8ro58VTybls4aXEJuIZOJRkorAVvNIAAnoYsHJj0hrIYrZ2VmV hypJR3AOc1AXVEoEPCsTcCaDGGWI+7dnr10QTT2lq877DovzXc2WefJi 3kB90YlmMnz8t+k0GOg0iNby3iGlUT27+RSJXZ/vTjkMho92sVL6sZiq yb8nTkpC7ioZ2+so57bWch54MeX6QxN1Is9sCRqrbAFFNnW/pCFq/cYM 4qVFius3WxlURI5NXyLmfWI2ed8CwE/qUIg/GnBGb9mOFbQyYrDLc58z lIpxXvPHXyJqd4JxdG6aVfdz262ZduFlrjhhvwNo+pO160uo5qEHSWwD ZCDtjQ==
+; resign=20460416024207
+137r.subtree2. 86400 IN NSEC 138r.subtree2. A RRSIG NSEC
+137r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Jbck/tyncGC0gkLR5c+N9WcR89t9ac9/xqqPdEQAdVHWDG4VItrikPI2 l3eGysPahtWA1+f7D94yJOng7sSbNWtEUdy6b4EZeOnQFh3YAKyntbIV 6grW/iZcrOP9mvX2cSlSokp+p+1tpv904mAlQ7+X7IM+4+ty22qWYgOR FYy6SeL5v8El8H1AU/ZFNCg+4zd7S4u4LOrxZqzDaQasKI/TDhbFSqNN vdc04thKMDGPhlp55MIBr8xWiG8TCpfExNMuge2VyJJwAAMHmczBB58C 2gB5VTgSgF5IEfZlhIOAyVcuN/UIQ/3s0NXWHDuCdoXUuli+jj6PcUVg sykt7g==
+; resign=20460416024207
+168r.subtree2. 86400 IN A 192.0.2.1
+168r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . XKYTQRQKJOYcN6HxS2q7mfUafsa9CPbT9Gfiw9iIL+a2ITwsSys+btaV xcvZzA5Nz+WH/rH7oO0od4NsBVz4pbdDlYwWNBQFmCwF8C9bK/A6LKH8 IGM94cIhp+h0z59pQASHDLckP0d2VCCNf78dCeXkd8X2cSWBoy+9liav l6KUfkxUrQermCyFw0HGRyFYD30/qPFM7AL2fAcWcWO55/YHz9CCkK+W 6kFY8c1vCg6YRnsJ4s1af4YtkgPuQpc4KeMVvERYdzoeQKe07JOQ93KK Z5uthbXXwBvIbTY4cX7I3P4aMJUWiivlvdqkva8AiykfrmEyzayX1swu OQDQYg==
+; resign=20460416024207
+168r.subtree2. 86400 IN NSEC 169r.subtree2. A RRSIG NSEC
+168r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . a4ct19ypRywi4oy0h4lxjgOV8ITjyKBSOOyx9DJXYDfE8Ncs1yp6xzRi vI+/2oyMim94OarZ2Wn21GC920//ZtYKWTvVjl/7EWLjhCSPE1DqfQuC SrWnx+OMUbvRwdk7RLIO67gVbDG0CHS0BWW2uPbflKO1oZacb5l9C+9q wyg660XowU8QN8MPXjvg/tEJgJ8quWetSPmvl/sGS0PnK8D+IQZtavwr H5r2n7cvPjrHyIQlkCrr3outwr8LoDs3tZrRKaI1P6Gxv762z1Uzbmlu X0bpbe+plnneMY8vaKSOkSHrI03fSKgpFoxqfdS16DrX3S8S92R7Q3Tr Ec/KKQ==
+; resign=20460416024207
+145r.subtree2. 86400 IN A 192.0.2.1
+145r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Md3iiSliQq/6iYPsTtnWxvD6fnvDE668mCKL7aV490Ni3sqD9WSkXLC3 ph6zvb2QqZP8ekVoYQOHVnl0IwsDc9DPdWp5evjU4c2HnS9gIQ89keEL QcjP6aoUxL1N0VGyBBSdD1W0/jrcX3L/B9LykoVNyUfVLq3l9UMTGC0X d2LUwsDEPOwKRFt9pb8or/RXT3TieRAyaIGzE0boPqT5oHZ5o41ycsJD qPEhvFaz/Uo4QxYHAQ4/aobB4jn5N9mOGzopVf5EGv6EAt5UxR9PRY9d 5M7wngfrQQd627Kep5BOW32/zMmYuaT6TGkK9s5SdxHVP9SwgNx/xVWq tpcp6g==
+; resign=20460416024207
+145r.subtree2. 86400 IN NSEC 146r.subtree2. A RRSIG NSEC
+145r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . A7MVtDlGXMJG/qZa41htXBV3f7Nd5Dm7VNnuHi8R3rB06wUOddv/vNxL ZayYNiBe/yEiRfyQoY3sTP4YGfEvn8rr64q8+qIhVNQOzyxreQi5j07e lbjn4Pxd7Yj8BlQA4Pq9Kh/Gf3N85yaSXQEz52laU8DPS8t/LyEZFSRp Iq82toLyt+owNCGREyzfN4tpz+XYNqC4gNYOGYYWeGg/1OZCqcc/dp3o KEj6RHzkUFXsL9K5/SBXRlwjLYV1sti/HvKi5ydcqEC+9/CMva/xzo+Z MfMCjuSaoMKpkLMIa6gsQ32+wuqzo93+yL29ERIXm12MqXTeUxWPr4UF bLhGig==
+; resign=20460416024207
+16r.subtree2. 86400 IN A 192.0.2.1
+16r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . G4AP9+de7xsMvRzowfWmGOaCekdxocWnfuG/VJTEnC0/JB5zXwYGWG1f asn03gvGoVatG9lL5XAvHuML/ceZA7nx7/6A15maAXBsaNhgk8XFav2O h+s2XX0KoQtPliODw0VmwkwWweni/KNTNRCTr0P23dYp6w4jMv6BEIgi PB61Brz0jncB+HTri6VOLMQ3iFPXkEqDscsxjzTKoGxP5NBYoCnb/WJx RzZerXTUyNGS7hHCAqCU5GdJvZ23ZK9flyeDfguOr+1CtiPGaCguEeyY 3irZOasoidv0Nzh8EWEZBHyaqE5ungmOnXFpZ1OIsBcmpvEJK7C6gHUR Kbgqxg==
+; resign=20460416024207
+16r.subtree2. 86400 IN NSEC 170r.subtree2. A RRSIG NSEC
+16r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Dxz4qBOcfOFrkjBahDF6DRVp/ZVBRs3PhONIr1abspr4oO1Mw5qsoTlO XB6T/S0W00xNqYS+hkbw/NbrXs+Wdxt3EyDDaPBj9UQmaVKpLVarAOPb uVPU3Bh903HjVNokRew1FQokt21MQNvf+3TA7oJhJO+r2jNYW6JPQ2DA lPzuVo7PLvF6aKyR0R1Q4IiCeG9u2r/TwE4cijzCF1Ks4nNDQQgRnf0z dKL+afAkZ3IGZaktGsVvhQbfYS4QK9HSq+O9rwC9KWHcmOptETlaWE4N mlgdf2PaEvdvkXh5P0P6X9lTsJ5v7jt7dRyY5JulCTf+3qxq2GQTiZKV lSJvVw==
+; resign=20460416024207
+170r.subtree2. 86400 IN A 192.0.2.1
+170r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . tWZDCsmC7lCG9c69zyDtiK253sdW5uB8sUg4ZAhCwqzXeOnja7fHd1Hw 6LXdF/BZAr+1TfWF1G2zlCZt2HgO2/ulc00ae7r9Gg0Lmc9xHwvrYUjW KkxCvlzB28xUytRHGLBjagYdIdQe4+O6GFUdpWm9UmfLhlZYySV9Q5Zk 7iJfNhRHEqALJxC/TB/mYBVapc8mODZUwvRVzxWJGoxzPYhdEQ/SsISj MrP91NojvPBeh3MnzPfTFwFIqmbTG1iNje6oY2JM4uFVchufF3jBRvBx HNkMb4IkdE4xCZdLKSZgtyG5drb/FuFi8u0GpaaJX9/PiojPbpFsoIXN Z5vbpg==
+; resign=20460416024207
+170r.subtree2. 86400 IN NSEC 171r.subtree2. A RRSIG NSEC
+170r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . B1GDYFzFdnB8bsoPCM16C3A4ij4kL7Ohc8AUXmgUnoPdowdAxU7+LiI6 lJqc75i9v/1Q3VfNMT9ztnk0S+XoopsjdGmU0+uj7pMGw9jroF8k6Q+7 Icjh5J5zlU7Z61N/P1qsgr3nLYI9ehaY35BBxPPYmHD6+tikAyUVTliO bk13vLSVM6h/hJKZo9WZ+lIiWtvonXcgST0nr20cahGjPw0t3xk7N7H0 yxwVqDTW6ZWREhQhXQmRJeaRieF0YSXs3TWpRjaNDOmA6/R5tIasCSPo ka1TAvAQsJLS1zGhyCBbtfTy+RzpZX5AFXA84JMx8sztR1KABJmyr5yL wjXPHQ==
+; resign=20460416024207
+171r.subtree2. 86400 IN A 192.0.2.1
+171r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Px653LHulvqO8jOFEtBfBo7HOueJqdazMo0RyrGcGcJkeg/n+jfjisHK q1gQbBmbHSA6haP9kQ1vnNFrUgOfnpapB3IBHD07XxVE5arDlKzq4ibV 8+6FONagFSljXPBpLDS8e7b12IN54LhtJW53D8dnyGiyvIbnHMz8QkLj zfhG2kTVIsRPGTAiBHZ0tlH9P4y2HEcUba/Zjr15cnXwMWMY6IUur8Nr QrfwS/LFwoTFEa5NRJCAZB4Iqf+dzL1id2wlWWei7aRXUvqBEtWD8l3u ILQB+UZYUJaStVLgYd+ygvDhLukhRpQFryj5QcAnAXVf9vDQrze5ZXV8 myUJ8g==
+; resign=20460416024207
+171r.subtree2. 86400 IN NSEC 172r.subtree2. A RRSIG NSEC
+171r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . vW2qpOg6NUTT+BrwaXYqbtvgpY74qo1RWHkC0yN7eRILMu60O9cAowxP aenIu7c+7O4xtRumUOhH0P5K2XWdUtNvaA/jlKcwo8WoecaW7lagRj1P 0jz27/rtW7Mlr1lk5srSWFm1UopvGdAFw+3W4u0mvq6zukArkvDPvCVu UvGuFcHaXj7YLGxfWY7PNgs6gccQSapbmAgKkzc5EaJSzeY1kKzh1zXe tE1RFORiuiKNrDUBlaVm3UDABR6Q6566m7SrutnvhSDgOsbdMP6YKR9a oZtKwgcaSDdH9pJZSGttyR/Tdav9emt1hfh9Ty10VXEciKAJYUux/Uer EvVDxg==
+; resign=20460416024207
+172r.subtree2. 86400 IN A 192.0.2.1
+172r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . hbRpeJkC2TvCOHEBZsddt+NkbOjrzvKuUCD9u5n0wpXl6JhwMVXYkGuj CXIMEk6ytNNJRDCGtkrEtWdr4Ou2XROX3LY6t2R1vl2828n0hLpfSJk2 aJgyLb/9UO3LFb6l7BNHFWluQW/Ap2OgqQjY49BsoFS/ltYWqwRmyU44 bVyFyUUBuMGSXP4xtMw5sCCAHZz4ReCaJ7is0BH/vlLVwv70CYyXddAO Ak4ibfjX7drnWfZQ0D7ysHkAkFbeg7ryEWhAimQlykn7TTK5wsv/O72j 3r0xO/oI4LBupMgORamWuewuaNxk9zYoeMsfb2px5GZoUNwKhJPCnCta BYgsIQ==
+; resign=20460416024207
+172r.subtree2. 86400 IN NSEC 173r.subtree2. A RRSIG NSEC
+172r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . v/sTxRVW2njY0M0JGbSnnDQAW/o2G0z3YkdSKHFUJbsBGwC5eFzdbmE3 uLnQCfu9nXNv/DS1OZGcxjgNwcpZgrZ42MHn/QjcazQHlUEjvFUg1vEe nubJVc091esJ9NUS9+RPdYtGCHUXAwT0PerfCrW2l7JDSJuL2PIVrnvT DhNBKGH9hBlYtd0tRwYHGCSjk8G5ptAWjfQCwS1lrcVrF4EPAfxRya++ CXwivLvmPVoAMM946F0dPh0BSq2iF5BTEUNhNFroDg2XPsMUDm0eCoZD H4ByK01fNR0FiUUPuNYyC3aaFi8HQ3uhKjTQuA5p5F0VT50TTfFiBQas cUzswA==
+; resign=20460416024207
+173r.subtree2. 86400 IN A 192.0.2.1
+173r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . guKL6dmbL2dBabs2B/mMtO5qZ0LNyZbzdZ3i2zM/2AA3ipz9LhIPnhwb KiO5LY04IUboeV6d1q/QYJJvVWpZGVmosS8N232HK8+2/LYD0NoGZTlI QhYOC948qMmfnBbEaXfWQ9pyAvkmLgJAwQRi/Ei7x7DC8xx66R/ibF8t xcp45wcH4PMJNCxE1lNSmHLu5ZWzf+hKHo4H3w7Gof8LiuVZOEBXVs4/ j5yeExAILi+vXAnvOuNB2+6TbiLgVUOrj7ixoEaK9rvmhcAv6HeipPoW TbUjt4oGmeE7Zmro9rCPblHeEp07HAXdKyDfkRj0M9NRHedW1tLTamLi Yd/yxg==
+; resign=20460416024207
+173r.subtree2. 86400 IN NSEC 174r.subtree2. A RRSIG NSEC
+173r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . qgYaLUdwBspbKvhBBLxy7ieuMvegHlw3eYH250W+pmMyJHs9UGQq/Yv8 Ic13KxjYjkDai1fxwJjbZIvNQbFfXg+Fb0ZapVG6O0FtksnN6arKMfgh oXkhwSlp/xUBFHiVUAoSgPQHGhv+OmuNOQvomIuvWq0kBEFYnr2pYT3Y NTnEtf16t5Cw38t1T/WfepZeeqG9uNCJxXy6L6ALIQvoHy8nNjq+1n+/ nc1oee7Hf/zlKeirFMZKV5CuVlkjwC/n++JZN/LA77vew8uXk7jfDUpm taFXNclFyZHdWs3Wle3OTp9DMfIYx9AwiZGGjEW55VzyB2r7Kf5E9n5z Gz/UMQ==
+; resign=20460416024207
+174r.subtree2. 86400 IN A 192.0.2.1
+174r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . mpdvu0jUL9Zw2Na2Mm2Rh/D7elKhYU70IEwJokldQG1MjGz+uWA40EyE cfgrPF6NUQWIaOn8s0/zwwpXZ7tUZgUMpsaYTt8mHLgthPa4KoH4GuM2 bEZvtivUkmrMAEHk+hKt135f4COXeYynKYNnqs2tSoKQKcPVtWEqTnzo 66HG2B52C4xEQCkt/ZcmHpblUPyvz1WPx91oCOs6DNdUBKe5Txwp0Oks 2qTlEmXUPuPRI6vxG3HvxspkiVhEmOPSV+mds/A+hWSj8Vjg6rMSgloa 6d2hpDccnh4z9wifIcI/BtDV8hyV6cCu2eBw6asUzHDaVFHA21iTmT4S U+8Pbg==
+; resign=20460416024207
+174r.subtree2. 86400 IN NSEC 175r.subtree2. A RRSIG NSEC
+174r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . jM8a2D3Vs2ov8hKGWdijThYZx1fTS+0NGNUQP8Qp1+gSQ5GOJh6jvqGf 5RA0VDkg4wPozglhkJlnxcP/OrMU6A1Q4pC5VxBbYjWhZW7QjqW/E5AP coCpR72TbJh83DKNnSf+nYcA+Bp+FNjxFppVwwSaFIvEQi+NJqAqyn55 pjq/OXlZJ0IfxYlHxtSYeNeYAWfKRj5o70GNCJDSkrfVC6WgZatX/UlU EcuhzQ50GV+O37tTOTWf6JMhxlU3yGhMpXMkhVK2sO0p8H32ZXiDPD8Z ufaMrAORHbmGdRaZA3tQe1eyHAJxidKONX74/YfIJlB46mKPUKS2QHi3 w8xANw==
+; resign=20460416024207
+175r.subtree2. 86400 IN A 192.0.2.1
+175r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . utjEa8lBcuP5lS+jGFix4ZzVposyVZLmUFeeQsZwtwqMI32KQ0affOl9 fvid2qo8FlSmOwwB9RhZaAH/q3LD95clwvOrkROxGmOPuMeoCGo5d1jH S59+314ROpaiNyA/CC+Gt60wc+v8n9tZmhVAKKAhlE96PARCRG274bBh s7wc/W7Ip2V2aolDHUQst+rTlE7VfsC5OgzRBYe68ChrUu8F1ahpxytu EkBvfClukGDGWxpckG434mEfPVHcJL5QsorB8tDkWcSo41igSLDl6UMo 55/tXqQKS/KyNZ6+OE42tunoQSpVFXH2hUKpEra7cv8teuWmGTYHJn4p Dp7qhg==
+; resign=20460416024207
+175r.subtree2. 86400 IN NSEC 176r.subtree2. A RRSIG NSEC
+175r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . u/JMoUNzUyurSpl/q2TgTl/enDjpWoL2AqkCaKePgxaBNbyyokLUPxTR O2LRjFWEAo4gQtX2UAM+wfAKOmKioTjlSg/y0J3FAWnJUwguhjVZTSUY /vtUmI/xrBEG7eIezzIB7ibJqTwDQf+AV6Z5PPsl9yuT0t4hz1biraGM o/axt4o7nkoo53drcP5CnZ60RVkrJi2X32DDizzw3C0aCrMM1egggE5d /H26v8wUiFU1OVrNY+oW2OWB6UNNl0J/vwoRY9++uKzSE2janv2eRr68 Auz/RWlBgicvvjqKJX5dYR9C2j01IYdPzwGksLHFdMguWWFBDdyHlYbW u/FH/g==
+; resign=20460416024207
+176r.subtree2. 86400 IN A 192.0.2.1
+176r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . uaZHB8CPM6Qj8LeU0Uze9bf2AReNgfwt1MmkZ3u7i7HGrztentHGQrmY ONo6zKAUiL8ZCejno4aIPD1ma+PqFoeUWxh0QNA7hPQbQ9yjlBxUfhh2 3OkRfN1AKx7NWsCZ/F4nVro/VjF03GZFg89FqEt8cRHZvzE8EvlU7xVb YZu0f6CRL2/SIz3MWODZhJYpurQpuGi62G8LhCCmeaTYJb3EdXo3Rsmg SnhTGK1atKuawNAtwuTQgUD7Z0iUnUCEkaTtUGLuhs5qmhF37erjJWlj vuWCwEDWpftzXTQijxj7Rg30WE6iI/VB174xd6NpyxG7DZ/2HdOx9CM6 73Lwbw==
+; resign=20460416024207
+176r.subtree2. 86400 IN NSEC 177r.subtree2. A RRSIG NSEC
+176r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . AUDXf4nZuDxYVZHFkcegqpTtb0KfqdtlWBDu1i1IGwi5HID+7tSuPgzU 1mnKSMjT7CfgMzEDWL7R9i/aF5+f8OjwLo1J9EVxBvLyl8zXoojy7SvS ix7HHuoJIr4hkCLSA4YAdkVq1OeIEiH3cZMEsPSBopi55jcc+Ixbln+h XowJok5Q/cV0mT5PD95XScGLhFUKqV6B9DR+5ptltU63sgPkdEhXp31h +ywApZcqxRCWIaJ5bSUNeycLpNTmBmupDwNzFvgPmI2tufRQPKkwNeC5 7AVtfd9Y2uKKSgSakhdiIJ+lcOPNfoF+53V4i/u5CKvn/VylJp9mXNJu hIebEg==
+; resign=20460416024207
+177r.subtree2. 86400 IN A 192.0.2.1
+177r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . UxCDSPBVRcEDyJMsTe1B5Wj3i0B4ppCNpIutplRGK1TcWlCoaoZYKb7n 78C3wXFiICE1lbj4cnHTeqSKLnUTxI5XEL832lRZmu5qUR9W8nhcSxfs YzdmtMx1VOwcZAo25NM+0883J2hXkveuV3GjtkSvaZ2W1zqMARVi0dDQ z9RT83gHjwLEmL9zPbpSdmj0OOddw15qOQNmXEUJJzZzaIPMaWpkpQIZ pU5peA6EBti4LI/902HEzJeydwZdrMa89FCL6J/UIoxUYHrXIugsZkcE ptptgPQaW5WNizfKYqanNhN9JX/t+/u8JMM1mIk1zIejiwuKtpZpj3mQ WPeNFw==
+; resign=20460416024207
+177r.subtree2. 86400 IN NSEC 178r.subtree2. A RRSIG NSEC
+177r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . gZTPyJ00JsyjTV3HsyTnB5279LPKJIibVKXDdEwU5148kvBte/nxxN3F Df5x0mPXbuERbWv+DJ/9/7vZNFkXt6EDkU7Ec8JAdGtj2/HImn3TfNvh 131hgK2h0ynNlsLnLRncJqe3gs8CXXQur181y5gQs5fAKrWsVQByLINb tTUDtLOffOvP5520CmE2Aw2u4JBkxAXQOqX0wAHcEXKS9XhhJv0IqVJS 4UxYM7TefGTFq3uvYKdIR4FTGcRE+liwpl4yOIITQ+24ik9bQcyMikVc lCy5ukMUciKP525XqEAgfSpTCefmiFaf49yg/FJuft4GA6vDbnYkFOCa S947Ww==
+; resign=20460416024207
+178r.subtree2. 86400 IN A 192.0.2.1
+178r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . PqlL+UptlSvJyT0dD+Uwu6nKTXGPpX9lhRrAp4H49OhSMI5CU39Wa7TF wjRYe9TtjYKjWtu8vEqzYPg2jTY/ic76nv3l8BalRIPKvwtQULuLmZYl LBL8h9JheqSWi5MPbgwt6fm7RJ1rcrHNYe0oHmuPt6JPfTLGywQFh9U6 nWoCa8y3xuOFgu3n8uJ+aDCl+VwJp4MmJvXygP3rHevpC27AqBIQdFc7 x02xrkWClVlfXC3PLrRvWRAnrVYjNYzFyT8TIe5ABt5oJ0I3xI0d7wgh Ha9IZd81bhNPm1wLtcBcK/X+ioxpaprQgD4/1QWdnUywRORfgwXpAyRA H9DilQ==
+; resign=20460416024207
+178r.subtree2. 86400 IN NSEC 179r.subtree2. A RRSIG NSEC
+178r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . bMQIhE436QXSvlMOTipK1JC2iIZFmX9G53UkCiJtlSgsxSsdJNslyXy0 Ejv/MaJ7Ljh10OtZ6EKy/kv32WuQB3jvtbpVbrfcJAbzTF+rMI9O69Fp KZSZXBGKwX2hy6IyfCVXZS9dOQS9BXyfQH0RevNr0+rf+c8tBwJyqgiR xyj89eCBf7odNHzjVGGPuhgT4MDXGnrgqoM3y8AIy75CpX18PJK7FkS5 22KCVh0Cdp2eFowltyaJm5K6qIOnkYYGwXRK90pTgZmt+VYgEx214/Ge mZx7zaS3a6+/UWgcuAiLs84iT2kWmGERjeyWwURJZT13IK08EZ3/mzLy d2zl9w==
+; resign=20460416024207
+13r.subtree2. 86400 IN A 192.0.2.1
+13r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . sXsgdidETamVj3hMGclONQ3xs/5wp/1272MujPM3+3/gKvVNa5VEggXS r2pA5t+Mk4PWuUXLog2ihyAi9Us4UVNSbBMxvXnq3fAeISGPm2QonTsT O312FEmqWcWyVv12sv3BVt/AEUl3O1YlPICr5XXLHRmrj6KF7ITZPy+g DZd7bseKOArxNqqsU/Gkixz146yV/IkmYxD2fQmErDqN9CEYkZ966f6I NMhTyDACGAu7sYZObCw6ruBGHik6Y8R6S/R0viMq4yC4B9TZEAsjyj7O FS84crAXdKYj+zywq2BUllrMVup+m1TJOfioxtXT7O9MptZ/CZM1qErr +nAwTg==
+; resign=20460416024207
+13r.subtree2. 86400 IN NSEC 140r.subtree2. A RRSIG NSEC
+13r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . f0UIocyE7wUpGKh6HM0renbqeQXha9E0ILL8OU+9wW7Kc+we6u+GI8M9 y7FQAlGIhm4ThMWxZE06TgwudtU7deAhEfryJdsmWjW/gBLmnfkSkaLU 6XNQVrVpCwR+DcFXvH1zasDrYZaxGc14BOZVuh4/HP5RYm1cQBA/cYX4 VJDBI1IYP7iqJEJyeYCmPDvgUZLtoqw1i7rey0qv+0C2yJ0clqFHScBU 4S/A397GkHzge8i2gfn6L/5r3PEe12AoBLk/FUQH12jjqP6VljOQFSa5 1Nsz/nj3SvoSqNIWO3uQ/tT7WwKxcYGEwOSB4pXJUBGp7jsoWPHn8kWC yW33bg==
+; resign=20460416024207
+17r.subtree2. 86400 IN A 192.0.2.1
+17r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . qqay8a9aVj9bppnXSWbAAFQRl7FgyL1Pc5BQBktPBi9XV7W0eqee2kOR jL8/vmYe/EkXkc1xIgb3gonQV7mojtkHT2pDckw9T0cY1htyYxJ/nyZK 3Ny1/SvJHTe1WAPxTwd1ZkVZ/vkz5FXl/uTeJ+nCq4JAlPnPviKPk44q FTeRdSOdEF0An9H75DruvlQ6cGA831gEUoe2oXiDEQeQW5wM2wfSdmNa FG8tshu0EGp7Ss+FvJ+x61rg+Qv/B9V93Bc8g7YSl2ugalOZu7Mu+sw7 wGMAsnuw4IMWxX8L9dlpQQo+9/BvXmGsw8715njG1mw+EZL5hywUXG4B CILSSQ==
+; resign=20460416024207
+17r.subtree2. 86400 IN NSEC 180r.subtree2. A RRSIG NSEC
+17r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . AMLTNqKTOztHdQJE0gthIMmfuNJJNrbWyt6xUiY/1Q+SABIizXRCF6vA 9hbDuQ0uAHybELbw+ge7ebkYVubbucSU31ukzw+F/E3s1TeXcSr+V9R0 lft1YlBgoAwE8tqj8ZqM/4T0OdF7GoXFx/yi9blme/1o6dnRzLCy4uX8 qQ7G34k7c97H/a8BJPNFzuIZ1xhuiBpaC7qf0VS5ZklQJF9J5nczMtNC R7FI8OCKeq/+XAW+43vETW3p808VFwjVs7qBSCEYG/zCfUFINDe2SOVd LyvoI8cRpJ1pEuQsQkJT519FIlkBLXULpB/xjivT34vdGU4VsMOs9e8w IeMXuA==
+; resign=20460416024207
+180r.subtree2. 86400 IN A 192.0.2.1
+180r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . xq1SIKwFhr2vnPWv4roDf9O3ghr4e4zAc18fVMUqx9shSuP/a9WPFNGD OwVLzVC0/VZ3hsOBy5rAsI6aIplAKdhGL6PFMv2k44SSweu+dv7KsqfH qNm8ZeZY5prxBEe0V2/blXpvk1zfV4AP/ZBvYNtVf/tacbqa8UqfIUET 5vtj0LhwPQL9R6OeOlkBhekX1/4w8Ow9lDvsdcphPHwuQwLubRWdmdKN AdCAOsrjeI76BNfuRn4wB0tuIUWSCP1yTmc0vwvfAn1gCCXNyLzZwgR8 Aeff9SGrOpSHC6k3S1HP127J4F08R86As19PYX5q85EzO5J9X2lzVPdZ mXFxwg==
+; resign=20460416024207
+180r.subtree2. 86400 IN NSEC 181r.subtree2. A RRSIG NSEC
+180r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ItEKZ07Ib/R69g7gIconfMxnTDiEkaPCnuJ+Ii6a4u6hJEckeRGeTkJk 59KtkxpBQvtWydPIJBOhPkahRa9Ru4dNb2cTeBCPeuAEXu0hLOOon+h2 uJSXdku6q0JBF3dNvV342F+9pZzWlYdKXzNPXy3YTJI3KPm7fGCNqDpR hsNLdjNfaTHZE/Y7x41bCw9ClETa0l+H7ToSNs5gJS4X3a7crxOAbPJo 3cFKbgwGJ6bbzvEWMlmurRlzzhLqbq/60wFM2dRjhY2Ras+c1klh1rG1 MuQTUsbNm7VRw6W+RsYudQURzJ4jvt3b5dYyePtmz17ClO9Aql3k16zO LaiSlw==
+; resign=20460416024207
+181r.subtree2. 86400 IN A 192.0.2.1
+181r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . QyvJ2cXDj9HwoqYzIB2Uy/ohHfRkbQEF7f+X1FPiVF5WUVNOi7PWuNNP c0ehr21bZXAp6ZdROatrkKdO++Ut8U8O0DfqYdlCBJNFNvuivf94W2AZ 6JnGDOHcOtvZq+vilxmdwXOxi5DMr6FstNS+qlUfgy8Y/PLnm2GKnZj4 n4cYCk9/KQNY6V7xB6remD4oqqJwDX+Q99VPjz7Pg1EW/GMu0Re5ri8Q AAhymV2j2ZgtmkxDZi7JMdIblR6Fv3sD3Sn4lmnV+8w+cH0oAX3Zw+U3 cr/SjGWyfzYpiAGs8XTO94GXpPSRHOwoU4bs052c8zrYSRrAfblejv5J gWZA2w==
+; resign=20460416024207
+181r.subtree2. 86400 IN NSEC 182r.subtree2. A RRSIG NSEC
+181r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . P7SN5d7c0OgaJ17/MVKultmuH02Y121rwRoJ3hqDo01Xv3T2JmiNhDid Ij8XiZb3Q/z9lvOjDY2WvrDsNMyLi06YfbQ1wYkibobkiz6qY7qPfI8P KB0LgEI19XFcTT7T3Swz407URR7nHipGfBeAoeWUiHThAdRD26xZzhJA IB9EbTs7a2kNyVMovaXxJPq+HA2lDxB8fo+buSdzwq+bFXhzZqpzY10G 785jT9iRz2jZY5vFApisqt2OAh9HefOfxcYOhicLvf2JxcGdPHt8YZhY i2Yjd3sekq/0hXOej+Up+sJ/1Tdkkxgbb2egVaFRgK3rN2XYSybJsXSB yvhgkg==
+; resign=20460416024207
+182r.subtree2. 86400 IN A 192.0.2.1
+182r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . SwcpMSUFgXG1DPNHv5BBBrxviDsdjj3P4ee2pDUNeKbLpsvbydpLari+ 2baprXJa35WreaHmz5lKbip0g7wlCePvmsfPe2Voa6Tc9ZGKi0Xtp91R gug3QL/PrFd1rjClMCyOdXyrrN5jRXVvEs4uY7CNS9qZVj+JzVJ0W0ap PYO50lYbAW+qNtYSSXhOJ3qlNqCCyYUMXaCmMvSI2JOC5pmxBUrhaqpW jAjt7NWi2GVVexj5+OUCCU01mdvLX6GwxWwAUJHq8cJzrjRag3p14Fj4 Lim0zf5fj+zBJavEN5TeW+2O4D/Wo2EcHP2XUbC4xrVzL2POSliPMbgk +QXSVA==
+; resign=20460416024207
+182r.subtree2. 86400 IN NSEC 183r.subtree2. A RRSIG NSEC
+182r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . pFiBdRGB6jTW4IvDdn/kM5mYzDQVKpCtk9X/zinOe8JHN2jdFV7voDfe dOu7jNE0Nh7VYlcIyJDp0SggthbNIcKZrkbeXTHiJz7svKMq0LMkGuCM p3QyKmyC0vBOgGrcTMpMEwP5dJJmk54WXMMXodekt2LiUpdo2R1p/Q83 z25wbZ0+UuoZ4EgaFPF5evLjX3TgNJqavhNwO+u5ldX7mUBOHTpG/O0F 8OKv8AoM+el69X+9irqklleF0LCuAlfac5yM8Ns82bZS1c8qnercNqUI CavjgGJAOtBpexVi0eH5Q6nLXf1OMXmc9lqtYkQ+CH75h7Hitbsfs4ja bMPkCA==
+; resign=20460416024207
+183r.subtree2. 86400 IN A 192.0.2.1
+183r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . wf0jrowwDmc4nXD+HHbB7zEnmSZrcJexZgD6pVG++RHkXx+LiEFwJr99 Y6x9D98pNC/o9+UpaZ/Os0oncS5RuXf0O6s+CqHIfAI3aKbFJfI5AoqG wV7YAPaSoW34IxPvDk/RRU0sJDxuT4W+6T3j/awaA753VxlgLKfvTmir S4XBaYPmwxk8EQlFpDOZFijwoP7/MdgKDc+w+0uOI3ASs8AY0Yd6Wh7E oT7iAn2y+iKeMzm0g/YIxPk9psgl048bKCnVFOtJq8T2BEMq8yhkr7/p F6RAF4d/VxTEUWFVbKWSK/6GSMaO52zp/pEgnf7ff3tifTokHBCq8xHb c+Ckfw==
+; resign=20460416024207
+183r.subtree2. 86400 IN NSEC 184r.subtree2. A RRSIG NSEC
+183r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . kSxsEXVu92hNUo16LnQ8x7Z0GgejAEgZtujfYXDBusVRxChY/QGAvzfa qCY87MGehyXc0dVGHJgKnOFEPS4knYQfoyAlyW90h/c0UMMD0za+VWZU 145m158hgCsqNiWoCWl2GKZZuA0ObNX0JcIT/7CsxrP+nlCdWuYCVgqq JCsX/WLcuzZu5mrPklK8dyYjZj3rZHqvFGkInJo/M1Ey7QB46KD58jHA kxw6YiK3tezmf92RTyHTv/kXM0dpgyBWZPM5D7ICc8MhOrv3B7V1T9ir q++8Z7/0jNhnjPkoH6zweCdRqI62PbKkqdnqfh8Mbe7xz4iIEAE33w/f ELq+mQ==
+; resign=20460416024207
+185r.subtree2. 86400 IN A 192.0.2.1
+185r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . fo8NvlBCCWNX5TQ2kfgtfnwS5HMDvww56htGU81xFLaRe5Vwh/8mqfQU Wy16sbwRGfqL+0OawZcKtuQvS2vSkeM7UI5LZkp8mYNeBozlPNO8sSI8 zVNegW07ykZtlftPnnnpjjMlmPnqBtFDxMKvjqBYJpiw00YD3qYPDbDt udI6j1cLhygruBDs0I0QPtZqUqlMxFES+jRtMF+pEK9XRF3N4Ibn1R9J 5ITbheM6zfLn3dU24raZQ/Q+sKM3ezr05qJMXewMDj2E9CV9ov166QLH v3cAH7o/dRJueMO+iUZ6YLGe8RsNCb5fhgdgp9ey5Rj1M0UbtVQOsbrg MYOXFQ==
+; resign=20460416024207
+185r.subtree2. 86400 IN NSEC 186r.subtree2. A RRSIG NSEC
+185r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . eWHh0SzUo4fWaYotGJ62oN1/on4/2os2nV6/EdEN2TMHcVI3JYT7Q66d /JjWJbyGB3UcfJXO02nyYcHDzS65DTdXOwcx+G4QJaXvsf1+3frSnqD3 RMm3PGrOxNe06dmPZOAWir1LJho4VjzV3aJQTmC10TE2vN0QcZryAeKe 5aFHva/DoclqvuMA4V7n3XOL/tnfZ+hK6LD38NetFwSUGAspJ/WSxQMC aqEDoP9XrmOJE1wgsIe0IFn7CRg5a84AJ56YIy0TXGgN/GFNSkembXDl latpUOlQw7XkdaxpXFfAAz1bmV6VTWMU4GtrspJSIlSIsYrcXVYgkFwj vKUIBg==
+; resign=20460416024207
+186r.subtree2. 86400 IN A 192.0.2.1
+186r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . w8I8vrnUjIPhi21ByPsk2oZ7ozJsrJoXDKSdsYj6+pq7bqrahW22IzR0 CmIxw8Kl+woQ+I6wui0lSJIqaPf4yr/eXL6fVHde3Clu7+1MPHAJZ2ss FxZxUuxuBXMTp2JzBYuGfZV3sE10Tg/DEISwC+ZM4GqALc9LlGPU0AUb EXcWBHpi2NeA6WKVR9ayZPZnG+mjWu2Lb8bcFtwilHadmooTDsEJX70e HmFZoixX54yjvkixWtNSmJcIveXYyevWTrXsF7orzi/WcsGsdCJbwLzc bALgvwxx7MuySed/M4AuN2L10su+jTeJfo5AX6tCoIdA03q9ATSDCES0 udG/zQ==
+; resign=20460416024207
+186r.subtree2. 86400 IN NSEC 187r.subtree2. A RRSIG NSEC
+186r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . OLNBPWMExsjmLeTQco5tvaZdupvyZhR9mT5O+6X9FRXz8srWXc0y7Vsl vk2B2YwzyJyXF3Lp5nUveE3XS7bPi5wxZi9dPa0MO5lVpmGfR9ju7Bzr R2NPMnEqdk0jk6plCqzTJBpT0Tx6g8RiR4Xp3O9UN3o/5zs7eiQtDjf4 npy6fAJihRP7xNd5qk5PwIv/cjx9j04L3HUXAh18oc143mplXBjUR8nw Zkmpk2hsZm6vYmGhz3GIGM9jXc+wzRkAVUj5NoMTa8hDIOEoe44E6+zd hboz+s5VPpjkTN5PymlCiAMqYN2vUfHmRhO+iGj+dwyLUzM+BCKTDWUa 9Zx57A==
+; resign=20460416024207
+187r.subtree2. 86400 IN A 192.0.2.1
+187r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . dIDIozfz1Bgm+ZML/5AzrX05Clt4mebu7ymZ+D+LHR4NOOhJ22gBnuwN LhVhUBi2zSopTmq8UxrL3CJzpbGBQFN60lFMLXt4RGTag7QCZZnTHXD5 3oK7VU67uGtjVp4EYAcrlIHh5NzHHyrZkydVvJogYMyG/KMsh+Gqi6hu +6RvtHInlxdzD0Rc1mq7bzd5iYMBB8dVFC3eFTEf3SBKIpFmLcJ2AnF+ 6AZEIzKg7D3zUi67A6Js2jJ1pIEX56KBmW0hHDfjSIkHLxECV9lRbx8+ p8qqhKFNqwk10TYj7QWcjWHfh3hi7UAQc3A7hnTFun44DHTybjojQ3ac 8Plu2g==
+; resign=20460416024207
+187r.subtree2. 86400 IN NSEC 188r.subtree2. A RRSIG NSEC
+187r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . WbioNojsR2o+n8tc+LYffEJFEmyq9W1uxT4R7pKwFH/ZJI7pqj06F4DA 4oRElc6NPKbwY95Au4d0Ys1Pt9zpeWIB5gzepDT300n6vne01CTtV/zn VA128Mywb0XsGkoKQpyOAXfApuNMwr+ehjsp8/oEITV0XyPtCOmsW+Rw ykopYhd0BbwwNBgiCsDrvWOvJCrRG1CNAOigKeMpln8SDCUwsA2XEXG0 YaSrnPGjJJqJzvPpJ3Ou13PRNGcyMCofqn9cNgxyKwwzthHoY+KBjrkz bz09228nhuICKDszvot9zI1HgWnVFAucdkfeCYLnQ2/gAbybsDIOOJqO rDOybQ==
+; resign=20460416024207
+188r.subtree2. 86400 IN A 192.0.2.1
+188r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . tbpdzGbYXHTgyeCDpJrZWppotY99N45bXmBtCPgcLLjdPcnpGlmyzEsG Ff1AIJpl7beUHJlforTD6aYPLVKNuFLIKnPti7lKHtd2sY5aRUIOfHrf 689gPeobI6fylrYlojmp1EO6W0aSy+AJrZf2vvmi4dmqc7WWn2qVSuQ5 1yw8fVa6C3GVvvVmia6wHyHIvTCqNjPANARSove1y5hVcU05c8/SOcwI rifwaYiarQ4PGYQwDJCGAfR6D9/VNmEoVH5RYlpeghHUOVphR9OEprR+ GxVaAi7xSuVzh+qsFvDaU2zsVMHC4WTIImxrsFufax0dAjYwCGTF0OVq LybCjQ==
+; resign=20460416024207
+188r.subtree2. 86400 IN NSEC 189r.subtree2. A RRSIG NSEC
+188r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . WtfODvpf3qTum3G8VHl+Cxtuhl8/oJ3g8HeUJwCF1FhDnKegjCjJV6qY rbP9N+mjqwi/CE1BvKAT1hsrWiEGk3TTWX2ss2D3GKjyH8p8Fvls0GzV X4+T3IwdPhFshoMCtOLb8DhxYp4l/tc91HgosTF+mJP5LHJcFbYcQEtn Qbwzs1HA4t4+8OHN6jVSDwbydSt8DbQidI99p3i2YM/Kg4PUPpMhuJD5 tEuUhxqDlk3tKGMh81WAk3XU9Aoz+AOu541Ko6agbUEs4d3Y2SddtAEK qOt6m1/vUPvDn3Q6BKn8X8oB+S9Msz430/vIPQoxqMZogc1hnZu7Rank tB/ceA==
+; resign=20460416024207
+189r.subtree2. 86400 IN A 192.0.2.1
+189r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . UL14E2kx6xvEAI55fX5qi4vnpfvk0XJ82EWo8hhs3hp25zNt5sd85jzg TlNr0x5dxbqFnxtdCT534ao/8Gw+zPvHxDmDWu7ygFpDTDQYub+zLVqQ PkUp/vZlpLkZ7OVjY/84x9tiQr8q5/5zGwEL+/LhUfn4ZbZo7YM5BnDu VhG0pjuSv1vbpwVIht5t9HVB/1mBsgI7Hy5AewNno8WytUzcfm3pt+Zp ns1bI7GDRvranS4y/cvcO3onmsshLmOmrWJjAMyZVHxHIUNt9RIlUJQC s0zJCcOu0yQxQnR6Za1KQBG8n7ZuWSvyRPLBZvd0OAWOJjKCBqtCof5D nqcRoA==
+; resign=20460416024207
+189r.subtree2. 86400 IN NSEC 18r.subtree2. A RRSIG NSEC
+189r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . cKcDMQGJXcf3v3RfLJF1zH1zleaK2ILfHWSb7+hB/Nm+mcLKm43KxWNZ rVJZu7EOUj8M2cHzm5NwEtmZm1SBvxa0NHASwavhNT6VfNswOxkbZy5P XanQBQ0TGc6PecbG2l8oeqhr8/9fDjyYmbN79Y73gZ2iIAIW/CFmLf2v GQ53Ct9L144s8KvxfsUnpDnob7pDnMBkM5GaMrbpgMXcdBgUnwRKoJJB uXxyE5yP5VFuWLGH8rOEIHWKdDRos5qlu/eM/ZZIf47nYy8G3rJqTjNj eCZumTPPOOdMI0jmXBJyGSkVaKQgcHjsnrfVTJqlOOy8dT/Yls6H8lV6 CBCXYQ==
+; resign=20460416024207
+18r.subtree2. 86400 IN A 192.0.2.1
+18r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Bd3k5r5o8uJIV88DwRHdF1nLGzEQ59pHGw+1ieisxP9BG7yxyxZIJmJf ZLD/crYRWeFrVZmqIHtCRJDaR8C9Wz81qNbTC610Y7yKdBAT3aFLR9OE 7ooBv+oDBeIgaGGjcWLxEaoFKA1QdrbtH3uv++KRpcCQaD6sEi5wHn38 J9DQDVRnTot6CqJykLwUA7DKt/Jemr6hXhwAeMyHwfRZtcFrYbvC+To9 m4moJLnEvyRBu6cJ/VxjIRfagLlzaUlbzy2aMkcUYcWP11BCykigd9Q6 qC7eQoDO/Ydbg2QRrYOP/3fBjNzmiSTudV3uuMOjWAkMELoPikcP/WRB W+fEcQ==
+; resign=20460416024207
+18r.subtree2. 86400 IN NSEC 190r.subtree2. A RRSIG NSEC
+18r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . LyKx01F3d/M8i1Y+hZ9AbwPKJ8jonETPnZN7PzY9GflfmyUlizrkxK1D mO9FAFbEbCAFu6ZuAScQfU7kGeYqFCDgfiEe7JRQVJTIUXloyZ2a6LGv sJWk60omdN2BZrEEeeQllh4jUquLzsMKcei3NIYmpG4BpeLbNr9jfzhC y64lwOeYjvbqw4MhRrBEWjelLdk4AwvcbDrsk3QgMhtEqOul5jQZ4SOH 86RQOy+2bVQWv10J7F8jV9OzFQwgrctLB4fkcD85KL5Phd5naqLiaiYH Lig01cudq0HVr+nlpoaNvMUzAMcaNQ8tYv3R0wmUF+pATNHsYzjHTggW 8VLbNg==
+; resign=20460416024207
+190r.subtree2. 86400 IN A 192.0.2.1
+190r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . q6SCWjzJDoMY2aeYf1unyQQAtRNwqmMJp5inkrs/4kOydKi/tOBhQkCQ +f/hqn5xx0llx7HhAzfFp8GO0L3eGH9A1kCLidcPZ21BkcGWhJ9gmEpW I89vhCBR3GFz3dtnpenQOaLYgidEiRdeXZ9qKSWlGi6zTxy1QE/XSt4C EoD2DBcPHnLJxcKi7ZaWz9+HckvyDtkfOIiX8uiZe3TVXAyAX2z9n2nS 6xQ/dqclFwI/xnUV0roQ5xL7REpVXkCQmB4RY5cOXC84mq437ksPDrAE 9m8Mzy8UGg4pgBmH9zvTLHM9aP/lSa5jD5RXi2BFiKwMm3FZ0vKCLffh VGmYzA==
+; resign=20460416024207
+190r.subtree2. 86400 IN NSEC 191r.subtree2. A RRSIG NSEC
+190r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . jvrQXPmILWkgzAqmNBvUAfUaMOLhBBJnHHR1vwgt3i1y/ng9w5NeDny9 aqpcfH0rOD8EqjjE31DgetAldcmwv9QIAtCHj8kLQT4z8RUluXSanvTA Vc8Ql074dsIUUBAqBJdAKcCTyjBNkZ7SzXtNXE8+wAInXG3yvvoHRbN1 DjDjJVSTLxikgZtLBcmE3wOylSWFII4VOXXJglzA5BNxH1dCrD5hGDZu d3SZ4d9u8rkLvNbh2ltwWbeYb9l9PJvJYVtPP6BEIEcd9viFO9zY0ibx 1Te5jZFjiWj2XDQHpTAdIob3AHJy58wDO0eSh2yZSMzJy/N6Je6sF8Rx optODw==
+; resign=20460416024207
+191r.subtree2. 86400 IN A 192.0.2.1
+191r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . coAWozM2Sbwb9a+o3pnwXDoOjmsSQKehpw9mqmcsao2389X87OrlHor8 Sdu7H+Y1Dp+5WzTH4ZAaa3CzggEGqJMrTfyaeR7ClTJm+8a2fBlXQn3D etFXVBehookIDHiphRmHazWos4ztoHcUky9ck8l2TaoK7VD7woms2HKS K6PaeWZx0KXratCJNrwfQKbqOVayWOqTVfyr1V1mRC/e2omjVh5eeag2 V0H61uq3MW8/dhFAY6BvAxaZ0coGEHipJuafphpfy8d02pOGLjmwRM2b nVWLsc+DgxSkKzgWEtoGMu6B9f/X8IZANkMlf5FNR+gurAxH028BaRRs Be5cSw==
+; resign=20460416024207
+191r.subtree2. 86400 IN NSEC 192r.subtree2. A RRSIG NSEC
+191r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Dltwr32CEvj1XDBLBckhHfu4uqW5V6+wWud6u7gJshlpaipvAnw0JFWO 0k+4a6N9aH9pWUjqXGHhnGXa+IDQtluAhljKfYNNngJ6yuPVnhDDN2yA ov34eFbGPlIw3yTxQ6ktZdyuMUyRB9nIS3+kmHcMWdOrVFc6QUDkbOa3 3HMGw4ow971BTpbqB16PHi8qPaEhDXVHUBxTIjJA0ekD0pFD8MYCKZxs /8eWWAzyjy0yVaXgMw7uecWPjARkvqq49CK7on8e537tkCT26faP5jD5 y/xWaDm+4dQpQVJCBjStO5cjCB9SNtFQlma3ZLqFLsrmRaHo6zJBQN8C ZLYFlw==
+; resign=20460416024207
+192r.subtree2. 86400 IN A 192.0.2.1
+192r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . bkaQetd2sGz+FIva3mtmf61a9UGb8EJF4Eyo9Ta9ahkxqDdG6RwI/ccT cd1pjd70uvxA74N5K5g15f3V2jJmJo/mSTiF3BJLT+mJffYGiWsX3rMB GGW/UmQMq/bL2uaijBKO5Z3WcOsE6E3rTL2X8haO3EmsM9y6MMwZ3R1p 6pq0jI+FsU6rSIhijYON3plaYhjOt68KidVHovRPYJzEpV3vVj9WGQBw APWTiX3QW8AHlDV/NJsMdQMUQhKGfdCPEIEYbW+JBoxylFygoPG+f+lP 2D310QFgOTPp5hzQ/QgcjvBfJLdVPmuB0rskxg5JPkJeSrbjW5fvu+Bn YJcM6g==
+; resign=20460416024207
+192r.subtree2. 86400 IN NSEC 193r.subtree2. A RRSIG NSEC
+192r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . XCkTY6jyHmhfZmlPhHlYopBPHUMI9u+PkLgF2iGChfVTheAGSc8vK+wr Ja2B/e6w0V2naN0BNu3aXPnZ/YvhaJymWnHDyFMK3fzMAHuD+qtsAmIr kmVwpM95vP2ZLXnoWKSoiJyCCszgKxnB//zMQAfHOuOyZ0c3KCkbIkLW DlHovVBEBYQZSA/LphEdzKDlOiVCfZ088KfUXgJnIf5Yz2B21he0G53B e2s32BF+z4wp0A9n4Oo9vMUozS2t12yg3pQtDh355oZF1f2l3gEWbyWo rjCqwGGPVwKmulbpkCfQDUaeNPl7TymYwKOiDoi8lARmpvRzVmwMemcb GeW5Lg==
+; resign=20460416024207
+193r.subtree2. 86400 IN A 192.0.2.1
+193r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . tUIiI0iSpJ8g9yeENn0D/ojKe4mqkc50dv3CESzrAD704LMx9JvhOHJg wX7Yw88Y1/6hquq44H0sJRHHKDPVBHpJ0P8tYW+pydJMbwDN3hs8intL oNJgJuH7G1p5rGanBfbXXEn2wZvnY/zZZr6a98JE21Lq6xEfO3XrRU82 BdQVJLjUpIQqW39gyXXGFNJqD72kfnHa94GlCYshKzygPTvN5dUoHih5 k6MRRZpYpHCPyxRwDKdjx4Z9rkCWcoGgV8+uvq8VhIKTv5C7WsCK6jAA 0zxsEd891ABw53m0+spPnXun7BfWqMg7IsIN60cfhuy31mu4obyw4KQj 4iF3rQ==
+; resign=20460416024207
+193r.subtree2. 86400 IN NSEC 194r.subtree2. A RRSIG NSEC
+193r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . IWMCS26Ec8KWZKOM3HQMPuW9gEEu9wSwQhIPK5zB/8P8jrS2RlCUyAzz cvYExkTzqMSs6PnwA9JNDcd+z2EDr90uCtvihrBA0i4wtIn6MYkTae0X Qwse+XoWYHHG7OFVX/iu5EBVUgO3Hu93rcSox9OUL/eAoGMBjGVoUutG xj55h2ntAfo+55oE3VoqmUswnThm4uECXgRD3uynjuTgJWPK6Kbvm7Od y+iWXpqleLDzXcFHqw7R6dzI2KB4I43DDhhDCUBoL4kccVLIkmqzuP1G kP6gH2vSGLqAO+LWbboA+c2hJpCN+dJgaX+qNF7QH0pkGYlaZz1Q4nW/ kKJeSw==
+; resign=20460416024207
+169r.subtree2. 86400 IN A 192.0.2.1
+169r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . si6aWmRP77c5v3LOogMFuTO9UwJ+tiiLWOAFT682h9kBLEp2mAnYXPUK I5GOLoxFV9R79alTP/k9QYATgttTnKYQt8FWq/o+dlPNqZh2fs48SBk/ yWlHeGW1trsDoAJS7m0aLtr/A3SaB7uLz0jmHHbdlui+h4ZaeNj8KAOW yDjiCqaOM7VEY4B8+iFCGS5Q9aXesMiKpky0aVQm4jh6r5Xvb4BNEy+O R/Tjv/X5j1UkQKwwe4mDe3wVP3UbFurPy7IUYpXvfx/S6LLrahhY3P3y tuhS4/G7y8QINVzS8ojAyuN6xxr4Y0ZCbSqe64QapDcsk68PcaaigJqh RXMG9g==
+; resign=20460416024207
+169r.subtree2. 86400 IN NSEC 16r.subtree2. A RRSIG NSEC
+169r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . bGKebx2uDgCnZArPh3h5Yk0FDylJOn1t4n5nxCXeD9kyIpOkPP6z/bka W7sRDSNa3ekqwg4ZHwtdnY7nMRbw83msh1CBcePQF8AIv6iHmoMBc+3p ++XgjyHVwQ3TbZ0sIRKmZwuKty2oSteOPZQSsDxQcgG0cmep90KWGE5h +xQJM92kah9uPJWCR/KF0wDRbwJgy4c/hSEaa5OiO2A9eRrXSy4TmyIF YNNVXl7uCDnRCUnH5M7ZNHpNVpUZzBxavmQgOOEPvaqxs+6/gpP2V+ys UKOOrDKz/7lsnr2zrq2XrCOoYs6tZ9n/wLTarYmL0gyg5mc1KjEYBYag vsLt6w==
+; resign=20460416024207
+194r.subtree2. 86400 IN A 192.0.2.1
+194r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . kJlB9dWd6vWbnvUV6uB+f8Oe5WnY9RUES9yQMdnYhZOQmWkc75KVpy9v sEEau+gisX5U2rpC6nNdWERMFhPMoCR0Wmc8xMWQKdNL0dpdFap4HI9a FqDswqAqjUlzSslZn6DKm/IErMzI4vIZPFe5lSooJDJtl+MM0qhhKmdk jww1Fb3x+VLKfLb+ry4+gAb3XXOhiBNdfKKaccpu11mfJsQ/GkqEszHW e84I7wFFwG1xj+O859GX8K50QD4EPzQCT1Y5N55m7iX31jogCxLmO1zi dN3l+Ul1FBxa9+rJXgjXp4DYCJ3G8Spgp+cpN6WLs00Q/E7WQ+RExD6j A4Thcw==
+; resign=20460416024207
+194r.subtree2. 86400 IN NSEC 195r.subtree2. A RRSIG NSEC
+194r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . q14OdHwGJ6MGHiL0xrXY68tPGX6HFIomOvKTYngIXz5LqlIqZJ6Ff+0S H+ONTRFR3Jt9Rl6N109MswOYWv3xLFUyqFJOGHyrGpKUpn5I1+CXEBcp Z2CG5ABHh2G2M08QjPYibrMhVX0ydt9U33LYVCukbrOyH2xkPnITmJ2T EHe4fCPACVEtPkBH8nAceaOLfajbpyBgHnXgeQqrujLoTPonTo0rRqr2 t7dp/48CxA3VQqvrsMajn39CH0xFJfcjHD5kAOn4L16XWkvyYVfn0BiA V3drHDaErhTeRoFkDlwWNxECrfkHqccqY/AWwcz3cSnISMAXWUVYd8ZQ GsU4oA==
+; resign=20460416024207
+160r.subtree2. 86400 IN A 192.0.2.1
+160r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . jb72zIgxTA12OlCMJwQuMC/YsMJGNqM8kTQB3QftDma03gPTFy8SbSkn Ewc4Suy6bC076FbaV5TBuX9mmxa2wQL1+Km+fJ9yiKw+FqrhFWmOcB8H Q1PZm83c+at5Yg7u8jvNF1O6QxF13dz6FbURpo7coH1Y8OBKHu+GwDfS +8yiHGsPqdrczCifDmYb3nDc8cslVYNP2AmytY5vbQdkPW6EumLrnTgd HR/vrDe2SLTNwuh+MNMSfS20oKzjYT4SPF8AHelhGqci096tRGJjKboz SpBmKUHQWyOWDiL+cSGGBlz6dMzD3P1iHAPprd+zaRREkVlmAwwvXx4N XscCxg==
+; resign=20460416024207
+160r.subtree2. 86400 IN NSEC 161r.subtree2. A RRSIG NSEC
+160r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . G0iabi6n6Lxce7Q8i6zYd20At1R9I2rybt7dkh0cx14cXIoq1mt4bqvw HaT5Mv3eqazO+/RNIaJ6urOsFxDr+g1uk4HCVwEA8JBs7jr8Gll6sFuj HF8Ig+kdjjIiIc4cjnygpW/vEd71TOqKeMj3zWtx7SEyLYHl9JaUKqHX ZKPJFA/25KdLvK/TImUgGWs5wTdsqWXgzYH0bGZZjWutToNbpL8a6r5Z smStITjxAxLbmqBdO01qVHufzlsGn5AnteSNie1lRsERhGgm7/QBDz0t 2nk/wsdBVyVJc24lgngL3fwypw0w1qJf25slT73wXayCiOIqRxGkzBg0 m4V89A==
+; resign=20460416024207
+196r.subtree2. 86400 IN A 192.0.2.1
+196r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Uv46WzWSPSF7dUoIG5MEXEkf0DSXGDISkm3V+3Gv53VpzWb2nZ5UhLp/ jVSbCrBl3lST6UV5XyNphtkYBHRFq0/ACUtBSTWGFipOGFHVn9OUWMPg NkzKrEGrvWmACRAkea9qVKAFwcSLEvudQM5LeeKKPRonUGlUN3UAHr1K WA87BbQPJ7cvpkHUqvuKnkdS0HkUXAD3g5xH4lHY217wN5SPX936QdWd cyWKEvC4x+grc6hGvHniOk8/8AswsHF6bFJJdF+5tvvIN54Nn3wGdWh4 HxwAG32NW2u+BhP2KBPAHZr5PXtROXfk3x1aOIGuOwI4jQKLO3eH62Aq HlCKow==
+; resign=20460416024207
+196r.subtree2. 86400 IN NSEC 197r.subtree2. A RRSIG NSEC
+196r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Vhyr8UxBBVv2bGlmO7vt9az5doR4DqsNL74WJU2vFDI3YF0oTOdJhl08 BaYUYQ0Fnq9YTaRAmTiwdmMKvbJWWw2e38tZwUUPmUmU8luVSBXcqGKd wtGr/Ys8zhj2SAY6yZL3K5XkpEFf4x+lJG6PQxGJpHPXmVxcfVdD+yiH IzUmPUTj4hOTwuqL/dJuggbED160MrzhsIcRInt2MM2z2rqkVVo//LS+ kKfTfYCjQSa57CwUrmiokBsiMqbZX8v0f3qfGW7TD03OrEGQNWrfNxkO OEgQp9bj89SeJ15xjog31LkVPILEnPA9Xb5eAjKs/GJP8Ltal8LWCXLe DrQfqw==
+; resign=20460416024207
+198r.subtree2. 86400 IN A 192.0.2.1
+198r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Cffyp1+C01GAOk47wKdsqcqrOhXarX2w/VUS9NKTrCFv3E6CVyGKwqxO ddRx4jEE7XcZG3p849CDmnDBDaimzG5i7PbGAt/GGoH/2IGRtKRm2N9p reag4cFOELL1OgRRAZ/Qrvhp+jSiNJc5/uT15lEK5/2KQm7CP4AS5LM/ Do4VjCMQQ/rsXrqepw0fiBGoVU4fyGAzzsIH8mLxdiWMymOYWsF2fHfr fomzr3mPeRwIRDzVd/LLrZPqHjStcOkihua+c5KV4SA44criNpV3b3OD BIgEXumqakOb6J7Gld23peqd0I4h6m3KIY8pRT1BA61g7JfzR7s6XN98 qm3Sfg==
+; resign=20460416024207
+198r.subtree2. 86400 IN NSEC 199r.subtree2. A RRSIG NSEC
+198r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . riY9qitfYMNPsFQue3GSigjgNjIoJb5pu0SNRP66FH6FEJeIQlVKNBiJ 5psuMDKxJdC4KbvPJ+A4hcojeuAh4oKU1HcXArpo27OlTU2gA7aSYHc2 OJkIQUnl+yH1YUdXnzHV9W67bEGW6qh3DwdahfRBQoB+lZW+xdQomdp7 HlswZ0yn7ldfaJhc78JIcHGgZvZAI+Y0tnWFlQpxxeHyQOJa98IlD5CV 4XTnPgr/tAwypuWlKxhYRuOdLszqodpul1MqE6++x+4GnZ6cil+3cWy5 Z35lxQQkwEmvdxRmWqsL0wpTaEnQK5xpTHSjS3CkjeR3pYH06q1nGdI4 5ci5Aw==
+; resign=20460416024207
+199r.subtree2. 86400 IN A 192.0.2.1
+199r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . TBgjH7aBcIMs1KVULSMbahd5I1ltCHL7P1p/Oos4jxMsVrVmQ9edWty5 SFqDyUhgyMk4map0ssQWk/8NBSnv3WpPBIaQM8un1/S0vWZgnRXVY6EL aivSXchFMYOD7pE3wx3cce03aKlFOkORZ9yfGhyHL+8J/aQmfisx2ucS 7wm3qV8K5EFY+Faf5JZhyV2tnQhRJgrISiz/hVH29q7nGOMelhAIkVLe 7da8uVGaUrG9Us6AFmNkpI6+Q9tbF68h/zuk+4fAB014rpBCJAaREYMr Wh/4pk03BRtEIFJeknjhiVTBapmL6Ok2/4ztvYvy55l8ZAqxNSa8o5Rs 7Q7MFA==
+; resign=20460416024207
+199r.subtree2. 86400 IN NSEC 19r.subtree2. A RRSIG NSEC
+199r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Iv/ytTUdTY8VWsqkOz7qvfKExoAky1y3HXaFCqSzwLY1fHHVnQPSoeBX o9TN19xMA9EyzV8rado66UKjAiOh5i+cG1pGBUatKQfpAOfeleGkY66M M0FfOu1RfO7kbyEcQaAwqx8/nyVoHfZskhrU1Ztpj4AzwqbO5WkH0CHI 7xQIcG+pNPjpQjOV88UpHlCYp4xaTzBZoMgKKwbS/abtGWw7Tg+bgi1g B0ItHaynIXDv45hbguEw/T+yio+4nIaGyEO9V/H+TjDxp3nbS+E7benA kZWjAK6m7BqAu6HSIRitwPR0JcCEdjT0LwUgDbj4komp11ZLl7FJlKoL Dmp+Sw==
+; resign=20460416024207
+19r.subtree2. 86400 IN A 192.0.2.1
+19r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . I90j4OdKnWqA+ogjg54CDQGIqisKOTQqEMrknuNreiCPOJTYubGFkTcm lFwWNy4ciQWpYdRBT8bZoV+KELLqWvD+ZMRnWo1kQ0/ewGdABuYVgB9n 3qSh+IkfnSla2zeqyYpVly1vYjFs1umXNVoIfHvHhB9w5+PnE65cQrnt 5Peci0x0iVS46TYH+jlrsy/6nicQV1JQ2cqKKo4EzVkgYw6w5hBZb/xl AurUO4v4NvffNV1fDjiGvDsrq5lWM72wwsZYG5Sn98afpTjfMiFqFzaM U4C/awi4sSKrvZ9mwQHDrs7mer6WLlj75OyuLRwuK49aKwDDWelNY1RJ BjUAqw==
+; resign=20460416024207
+19r.subtree2. 86400 IN NSEC 1r.subtree2. A RRSIG NSEC
+19r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . P3aASMLhQQIb7UGT/uKDap1pTt9RmqoleePeWFVYeLUZ2K9nnYxGaJE8 WS1W8tTzcVGkWqc4+P3LI5yDR8Dg3QtpAqGqvDt4qogXtTaopIdFdWub xIzxXqjT2SXRtpXlUu+KOldTQRFrQq8ttibDBcWQf9oQN3hNiyYy6nlL FGQ+Xa8+CAq8dI9nw5qd+AUxsDBqwi4qxb4SUM6RIOiDUZU2t2v+jX38 O8Ps5yZOs0DocS2PirrPwP1m/kZ1NR8RSw+vursZsbs8H/jkrQ9waCxv S/9yj5CC+Y6hTzE4jf8Q5Fbsv0wIFJTQ70lhjNhaSEQerO8C5l3y9crI Yn7sxA==
+; resign=20460416024207
+1r.subtree2. 86400 IN AAAA 2001:db8::
+1r.subtree2. 86400 IN RRSIG AAAA 8 2 86400 20460416024207 20180815062314 48409 . QPdm6umQ6hgZ12897PAoWH0w+doWTmMWyy6rehLtYI6aosZmdYba/IKf USQchPvSbbpRjZl3SyNc2DJXkmZIr6DBg/4UpTsZJgCKsregVJzo1vi4 iv2NeQ4IAoXFqkGqkOMVzZI7jrBtXQe3BCXtGMh5ydSyV5vkQPFO6VKg yfObVC2g8qpMzF4hGu/KOe41dmkuUKg6OMKkaxrxg6ppscYIa+8MZS+6 8TdNJZFKBk69PmKRsmSBwxAWyLg9gzN68vyiccKJxAYeIA3cZPPZTM+Y MYPwoBLBRxDJ2IvKpVkyYHqjljDjI/SUtBMlYrUHZcxa9JlzaNlaZT1Y P9WkzA==
+; resign=20460416024207
+1r.subtree2. 86400 IN NSEC 200r.subtree2. AAAA RRSIG NSEC
+1r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . bmKqaR1EKjjdKty7b+HJS9ZckeQaD6X6CV60Ly0zoWsWR+buhfO51Fm8 p4j7YuLohXqREy354p+gUq5bbSL1UXMtw0ABJkFlmP4fAvuWvIfhW3Mr ZIKDaDF0LilZyyGHa9HAQBMA/mO0xoKsMEtLwN/3zRpHm0A27Le7kQzl Tt4qjLfdhDnW47g+ISxVNceHHKgJXXmFxJGrnLUOzh85kUTYq9OECJQL luE7/+n2uDTcsHUnoDxcTupMU32nXMMSR1k/KFSGN+XGM0rkdD8gdLZS 8px8nU1PkH5UlwJem/08S5Wf3VJakKFeUBbFOYhHyCAZrP7oZwzZBcOR 7Hqr7A==
+; resign=20460416024207
+200r.subtree2. 86400 IN A 192.0.2.1
+200r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . AdCcd+Um++YcFuEPv4aM5EjXQMa9EEM8JfJzUSbtnNgbc2dkAuhtU9/y v+Fspg0RhVw/GZeqLvikGrkvIQliZv4CETszTJt6521PTrveFkQjJRjt XztatkTf0GtwvJepxitodztMAZK3aOyXQkYSjfQgybG5c6sCSAU+crqp IJxBhV1eKIJ91ZVMq6rvlhUmkKQg2nCrgLq+V8lbaZHANar0k7O3st3U ESLyCRJ88rYhlvITZzKtawgIFpZxl8ilF6dPIqZuz1JDnI7u1N8Yk8ec OHHJReEDhK3XhupK9Xqr//laHHWiy2CyEivZpZFnP9d5ePt2Fe1/xm5U WYZtdg==
+; resign=20460416024207
+200r.subtree2. 86400 IN NSEC 201r.subtree2. A RRSIG NSEC
+200r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . arMHmTLOYus38Fu2zrYqWz7IDdR1K+tkbUbUkQT0kUhD+9o2f2JOBKSF UHWGl/sLof7v9ZDNiO2YDRNqZqqfrbzVkZ5TkViJ25BbdpLyzHQmTWkD 0jA0Vc5bASOoNBArxKGLctrdG2xTlOzr4r8larjWmAdF82rSum2VQEzg RQW+32E+fi9P4/n9N0lL7LTgGax04Fk/rT41T/00A1FY7y0WYlex0KPk 6SG3366RsA/D+ci+rYcV+o1gowZWZ2EfcIY/dqjaaYKsPlkTdWKXMFKn e5tlur46qyl0ds2Sl8nzxWrym6MBtVEfGTONQiPymhxtvfy3+x8QxCP2 BxiZxA==
+; resign=20460416024207
+201r.subtree2. 86400 IN A 192.0.2.1
+201r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . WKRZCWCyCWWUcXSdPXGuiqNJr9fp2TBt5T2Gwd6gK4D8kzyr7fzyfkex QoR2lb6i5PC7DwxP2JdMz8DeT4x8TQmsNhlgRk5W1yEWie0SXdgtIGMm CrjZqktI8qtdqoRTXTf7wk+8E9QOcOnZIErQZOcs8GWkqU7PTteyHRFx FwPv/b+ynLks/GnzmiwIuKO/5ITbFByQgpT6cuIR6r+Xl/f1PZ0yThnO +FC8jBJQVZDYAmMgPt9wa8vQeLANdVgOqrEBi5dc8xHRVESw7Yo9aHOg MN7R1/HN/1wXe1U/IGXnAfIBQxZbdICy7Ht/GBMUW5d4EuIHHSIOwzv3 Xn12/g==
+; resign=20460416024207
+201r.subtree2. 86400 IN NSEC 20r.subtree2. A RRSIG NSEC
+201r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ZDr+YoExEf+b009kZrDByKSO+NsyPNNizYUiEY6rr3+LTWvqQxghtU+N Udt9OjzETSWQxOKxTjpPr9PAcaPimpxztob4UxSdyyvhBey48AaMEHGI sYoFPaftUX4lwXDaaZRyzEaqe4nv55VJQUdg6JqTN5wvlSXWocKrKhGm gAxhfXbo6IYUp9AH41FRM6zv+AT+hqy2y+2rMpPXDy7SUafR/0hulVJ4 0JTxPIXVrhJGV7JxxWUfO4x3t0w/B3z0bCXgEro5ixke1iXz53+zGeKh RcIIwVUFx14XVgllzWkD8RaIdRLHiTOH9N4mxrw3bKje4iF+FC8XR1M2 Ao0uaQ==
+; resign=20460416024207
+20r.subtree2. 86400 IN A 192.0.2.1
+20r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . VElsKz86WloMD8ddFst3ZmP7o7rv85lui0GuD/cqc/HDHxNKlkegTzi+ RRdSCJw/XoGoLv0Gsc3oMffvX+YMombqctFWS58tK7msoKJAAxLNc4Eb IqSNI/shXP9IWLSqLMR6fCZSgNd85Q5DlvjY3ql4tRyn42299s3YCkXc PCurh+/LaePwqfj95nKD+gAj/c89stTFA8bfQza7mYEZPRXbnjKhKHoV KcWQmYVZIeJbydPLQ/dcUMbbYsRIQvzPmVGULcpCngTJQlsFG9E+nDto h9DOz4/DeMjN/1DRZP8FVVSx4b6QXJaw+lsEL3cRpA+FXzXOxFSyVxRi 0QeviQ==
+; resign=20460416024207
+20r.subtree2. 86400 IN NSEC 21r.subtree2. A RRSIG NSEC
+20r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . HKndM6ggvOdNXfO7/v20k3RwWsAKWxFGLM0o+EL1gvkw9vi1BSpGSH/6 OvRHlECyZ2zD5UZXquNy6pjP2sMtnSdNPDARm4MYAYDJQWool14QRSFF JHbEdXPBQB4Erq/NqdByq0RuEOykROi8tdiyW5o7pCWjpgm6YSmFvNFI 3jaLLYAZygTuonnJX9gXAalKo/QtAGw5blFA6X1GTDuhdMIK+ojOBDr1 QeZA2tNJAGKtkta9L/NrwZlPX+Ij+WPlPvCgzGonnYHfLGC4j0X82Ctm 5pNpzjal/ilPYBnhu+wOuNkjdrbPnjbA4q/o3zHAcTw1RHh561geSOfZ Htkbig==
+; resign=20460416024207
+21r.subtree2. 86400 IN A 192.0.2.1
+21r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Ddwfe587CQAQmgV6aWxYN6fp6gBQu7ydmCThrOYl5yHcmH46RKLjP5e2 QBHtj3EJEGSh+H0ZRaYgmuX2QNR4nSfAPs3kI4cAr/cP4lyfASm1890f 03nc8NGeLcl1t9tJR6Nu2hy979252mREBWt0hKkAtnDhu3u/CCopMkFy qQzwcusRXU6cnf2GVMOOgSXJvC9hkK/LCAlTPR1SyIViXTIyMrrvR+0z O4s+SihfAt/0LzYGEFCB8UOTj8r8U1QWrAuVxZCVPYM70b0q2IzB5aRH QmU4fQ77HaNdDD976qFOFm0MSAA864S69yBmn048yRO18wc9T4xJbfzM Ua2O1w==
+; resign=20460416024207
+21r.subtree2. 86400 IN NSEC 22r.subtree2. A RRSIG NSEC
+21r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . V5B9ljXa86R+eOEozvkkKYRzr6WmwAYblhCZtfYoH+nEcuImDLOcKppv fgpmzY6LG8yzMt8V4Cbk41D1kIvCD+balhp35hpBsvCZdzuAEBqSB//y 7/4n5CUNaCa2ThJP/rVH6ktwja4Y1YE8dKgJ/BMDVMNx1sB9M+J+xiyv +jmDM6Cnc4ihRgt5+dHFG/AkGekG4LHSEYquAhc+0ZypNoY8stC4dDpP 9HTRusbvJR5Be7a7shOkfGgUP7CdZhZPTXGhC/etGAXzrVMpIg5+vQA7 cRzH8czwgpp11hQcMpbjHRiDggC0bkAhC8Se3MyrEL0l2wwWt3r+VVWP BTL4+g==
+; resign=20460416024207
+22r.subtree2. 86400 IN A 192.0.2.1
+22r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . tBL2RsmxH4sQBpS+lGED4ig0o/jHn1kEo7ctXl4F90EZrY9PE2VOY/8+ LCq0B+O9tRZaOwZ0H9/P1AdssmdaZDX1PajDuP8+WC9zjjA21sPpOW4l eOPXoE1200W3cX0maOp+/uA6vHy6FoC1d1/WeObc8NpN7J8mNXU/tzH+ xOlyeccMsxapQ4hXJFIpWeTGFfogB0eaNOxSMHinoLWEdAXYcF26B3Jj Xxagc9YQFn0hUuOK7CIc6OYuGXnEeteegZI94dKPUF0jfqLfJomibQlr d6vsz5EFvAbJw83Va93QT4+SHcPDzEgsraVYBFd56WHoXbPZ4NZiPL7o BsgOwA==
+; resign=20460416024207
+22r.subtree2. 86400 IN NSEC 23r.subtree2. A RRSIG NSEC
+22r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . mQ+h1nOcLbnohGMzdSxudqhZ5AJoV2l2i4wkAL+syqDiHnuGVG0Znxwe Uy0Qjf0Uza+XP81Y+q4MJonVYpfriJv0QJCY9D8/r7fBrmOTnz3pdptD 9ABo0FIDtIsNzMBdMTsVqNVHESmXRs3DaJcGza+SrRl5TChGU+jkTth4 i1yl0VO2wAfox9z39BKnkZ8+bXwU3LJeBKN4EasQV91X2SJDYLzvRB4J v9cYER1xX/+HLG9pBN8I2iVy5O7UBCb94KgYtwB22V2t+8i2RwcJ1a3x FdZCEyk9fTGow8NCmjCJSMTLCqLZgoCpWhReXTB7mlJ62eYyxD39gPFz FvuhAw==
+; resign=20460416024207
+23r.subtree2. 86400 IN A 192.0.2.1
+23r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . vt9Cxd3Qaaz2ioC4luCCwsjE2cD3ySiqfJEC4WySIs/ONMlWC3ZjKW5c yS9/R9FsXMQfqQ9tpqNpLDg09UDU7BRJPW0Ouf1NlS4sPc8yfC49v9pO 7sN+gcwIjehQuPLCweXFjuckZke92b3DTgEZJ1BxnvzIEfH9gQYmDk1K 1Ul1bmK20Q/sKA4TgJSIq7rQF5JaZ5L2xR/+hZTNDhT/ApX+0By6MPSa uEKvH2/SkPS46VfeeR4iUyJqCpzZzm4rruUq7pGItL28PPPefchebCUo +85TDv5eRVP2rRHDixhqYcBNG7HVu+G+2wmX9caZ5pEGwGYdOdhzebJQ p6ECKg==
+; resign=20460416024207
+23r.subtree2. 86400 IN NSEC 24r.subtree2. A RRSIG NSEC
+23r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . DAJWX9VaLiASU85yHGBARIl9kcBXWozpt5yQSxy6kYVY/AvG0j9SSEcY 7ThX/eC6hPgHj3MOpFfJBWfYGAIKxejnzr3ATtevOYC6xugBRoc95Uyb dYo5S17O6yzI1OU6d/ll5KDW+gX4e/1+10P6FmJLlS9AzhAjbzXXIdZQ 74dcDIQ8y/cSl7VoqlwUg3LwC4hYtXOaFsrkwId/a2Ly3k8qXNIIYw63 Phu17IuoKZBceXBDNyuQkeo0CX7EfU60M0SRtaF7vohv1vy35BVA/SW5 UPEbBDHGdy4ygJv8cCwFhcdqXm8O7aYjS3xGscKdRa+HfCl0rBAdBLdo UZ56OA==
+; resign=20460416024207
+24r.subtree2. 86400 IN A 192.0.2.1
+24r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . oREQ6/5Gy5CkKpd0m/7Nfu8dGqXe3PdR1wUrLjfmsbcx6I54VQ0l0L1l G1GjHM3ci/ngmAak+4Iy3NUiDEtUxPxXFfJHDac0QunCscGpwwDxDhRM VoyB+DnQ0DZcAPUcIiBSyS56ujIZkRvfAY2PnXH5WYR8V4CfgI7Lnq82 R431Lqz1TqkwX9XYTLcloD1D6uPez3xV/fdJ5jGoOQbQYPV+mjjDrMPm d0mqRw42ubDqrjf1wZ/s7sSKV8T2e7rnpjreoGcD/TBaGQ6JYNx6NZCG eOTGBLf2NdCkN5BacbZrXMUVCk9qmgPZJ/zlcbEwB4X4VgLjHfU6l83q p2dShA==
+; resign=20460416024207
+24r.subtree2. 86400 IN NSEC 25r.subtree2. A RRSIG NSEC
+24r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . w4jw5BOrRRbRs1yvVoAZDiW6AzBNZ9czQSIx0zbPEzOytGplgWurm7iG UyHCKSnOzB4TQ6hjh1OuuQXYm5x5iJifHpLD+o8aGRoae3hnpHBkGDn1 7zlPCmNpzPrRDCNKMwwMnvzaqsXu3bBfh718dVN1o/kiFF+AWEdDZaWE 3TkAHpDIa+UUQr+dA2IEdNMzTMnH/iezAQyejxHdQLw1/eYiWOmvjzvZ 5lRiihRfTCumJXtTeUko/FbqB3mdt6ioZm/RYbONw2ouZ8OTys4BOsqe Y4pE0QSxWHqKAU4pynCI3unZdE3ckTAiu1GxlcTwujge7ifjKu+JuBdJ ynGItA==
+; resign=20460416024207
+25r.subtree2. 86400 IN A 192.0.2.1
+25r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . R4hAmO+OLOqxjHBlu5r+I95+zNrbT5deDE4C7hoStCut873rs7GXs1Ti ICmud+4oWP6w22TD+Vc+cnt9GwtRY2oZFAomfaZVPyqiUrxDI5Abd57R Nc3C6la5u86Vh/rU4dTaVhOxCe0g5365DJba3x1TCJBh9MIlLXdZC2K5 jmxEgrYatyo7tDQv4EFPxXB+J4j1IK29llcEmCkdHosaNo81bEDeH/kZ l68+SG03Fq3mHkgd1EWPs6M1IYOXVxBKhoRQZ4e8K89TAUAsyscsOVje HEg7BhfOsWtuuyMPNqDq4NhYVEQR3VQIMzMDRhJQ1njIrJgVzrW9AyQO YPVKOQ==
+; resign=20460416024207
+25r.subtree2. 86400 IN NSEC 26r.subtree2. A RRSIG NSEC
+25r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Tt14ZNNTv3ACWX5Qs41v7fB58iGZvKt4r8gDgQWSHYk0eefMrQOV7Qob LWZ+MQZXqcuNwmpbW+c0i3gsHywsU7wZTyXIJXhEHo3AKYURDf6FIjlQ VXTmLmaNvvA+51MuKzv9zyg64V58xIl44IClvNde+vEgOukzgH1UNkjL DdZKT3JpZfThPtPR5TPsuKwqKqtKsod9/D9JGNswow0ebFXLEA8X4qZG +4OfDIU0OoiGmN/5QXfor2p0RTeHHkhan2Nb365qEg+wdigmVTPjLFSv 8p2b/Mhkf9HYFv9M8/8PaXNhtAZ5whjySb+iRvTwcA5MyrXugI0nKqpH jDyBQw==
+; resign=20460416024207
+26r.subtree2. 86400 IN A 192.0.2.1
+26r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . aVWoA576Nhf/V7aCHPru31D675EVJHxtBqMA1jAutDOWtQIRnuz9CoS+ 6zG5mfywi+MPxMYHpcGYM+wxQkQjVMijBBkD6QBPE2Pf49VmYfD01AUZ R1CM/CdVZi81w7tjMDbKJT2XQs3EozsdsjyeUrWWqmtv4xnWgwPwSBcP NADFeuXyWhyJPeVIbR/S0lqPkS1e3XPZaUWn7ipe01e+dhHvMbZU1hZS y3h+EwHdeeTzG/0prP0EROMbRZflfUEdS94FAMehBDnG/2feQiKRJsTL rnrZ7y66yWvUvjHnfgW4XqAsDPq3LzEGApbvEOksdVTb87o3VBFmFpRy OZqQYQ==
+; resign=20460416024207
+26r.subtree2. 86400 IN NSEC 27r.subtree2. A RRSIG NSEC
+26r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . yBNyvINpDip6j5Pe8i2lmYd3MNeHXQrW4tfOgqzPgd3fIpc06kYSG264 FchLrX89Bsm4q6/ZlfZN5XpYHXYCwFzNWaC+CJJQh6Wj52S0UKfAsTRA 86jxe1Y8Ld6kFdjE94UW/j/l3VO3j6oZw96kpTj1RH6dqPtPxGOqhbWi BgdoMBmKV+TCGHXEhEK/MQ/InLrBD4nE40Ryr1kYhGmqvFq6OBrYwSX3 JN5R3NJQVqqkE1/cQxaUKhJlw515hucNar37ZaI3IF1G9xs5cny8HVdo mo/CSBBumcR/1ByIy3S8ZYqJGwtkTGj3E07JXVgvRo+04gxUS9aEBBD4 cLPf2A==
+; resign=20460416024207
+27r.subtree2. 86400 IN A 192.0.2.1
+27r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . xrpHpVUJTEBYfeJgoVXr3JDlXNT+v6G/S2sKYlYwpQO0w5/gloxDJFL4 0X9tEoUF0tvV5+zsqRxbZD8J5PWJlWMiee4CxzyfZtwOYRgq5Pq4hUQX T9n6jfNlL4xSKkvIFz5+upIzqfopZwFo0nd7LxUPLDakBdMMJHX+6xA1 ITBktxiCrm/smFzcIUCekMXDMBXrScntqdutctOYYmoAnLIerJLP5lcv yp+NSjZKaDCV77kV7jOf4yJr0LNpV9+TlVCQMFOUSlQiT+QtoydPoZ16 vLSKrs3jUkFNS5WgzIam2EG8KYAutFxD/f1p4mCcYtlD/axOi5lALldo UdAZAw==
+; resign=20460416024207
+27r.subtree2. 86400 IN NSEC 28r.subtree2. A RRSIG NSEC
+27r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Sn1ACKuYHZBNOuLZVaKGDJKqcEQCcVBYjyNYdXez4uPCwD4YipOAjVk5 fNbse4YUxwokWGUMA2+vo6FPCNtL4aAUdbBlbdN+wso4C2vo38AljMRp Vta+yY1QNQwQ8iocX5njYJ3PNqCqzLaRvV1DPZLNgzwjHGD/5aUlQzPl nOUwspcOA5QRT1s2yx1WkDIqJjkYhNXFsp3Yy6FZR7CW2QFvBwlGVjGs nq0K82oZJfU2TAlnWU34hz+NvRYG00irQVdsd7M3OhVgbtaAykdse+pr PSYHbWTXFrrXwTY8RamnxUDPjGEBc92403m3r90OxNY1JPlbecKuGQti uSjiRA==
+; resign=20460416024207
+28r.subtree2. 86400 IN A 192.0.2.1
+28r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . U/xbcFOZhpchMwPGJFmpAJZkMVU+RkyUIlwrfRpDFBASPEFd4tron+eU 9lOWuwre3SwQk2Ul0q9rCZr78dj9Dg6Hh5OgwaP9pmebsg512O+4j2rO z+oj7b9sI2YVwDX62nTxv0v4buhPzFuDjY+L0z7EdKTaUrb0N+kYehze RXXUMeu8+xVg8vD6nBOClI2KjcjUaivCJsQ4KHtBrXN0T1CoQG1KT2uB SFT/YohxC+9sTmiS6wPplxolxg5vZBBgDV0l9ppV9y2dE1fQsC7N30ch 2mSSgPm4/nZSmEX7We3TnvLaiyEoi7gSTy9txaBRRyUhQNsPTT7TJ/pK UA/o1A==
+; resign=20460416024207
+28r.subtree2. 86400 IN NSEC 29r.subtree2. A RRSIG NSEC
+28r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . A0707dBFvfAGDrHniS9iR6Z4znuSifFb9yvIUQBEcT+1+u6gbtyAhABC w+tNCEgOnkkl5fB651d9znwAzp889bVZiAjQF828v77vcMS992OZ3bvy 9XseZbdzedr4TRo1HgcdBPmxXR7a1PezrhZS5NgZxeBj4nEPfXW/MzT7 I8W9KcnQoltu0woOrWHyiHCQGqBKQysUHnpz26XVnhJ/LpyEhYbHa9WV uksBUDDXkONgBdGSfiby+Kl32sW/PgjdqTdy4+GZHk+LWK3C/D98UPMg wzoz+Um6k7M+ZrkZHwHwQde+l83nNgn7WYDPUe2KU5MlVCoGbyWKSaIc s4PKrg==
+; resign=20460416024207
+29r.subtree2. 86400 IN A 192.0.2.1
+29r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ilPHZ6aax0asJooMVb4cjhHUxgPo22Pn2Z6sXM3gEopb8Wy0K8Yds78P A0BUbioKG73l1PAXx5wM60Tx5Kvm88OiTKDhqUw55S2eUZ1h0X3W3Bgv +8+PKAWhp572gSbWlLcgDkwvXrl/evTapxgg0tvNGpXug8SYPUcxUmd0 6QqYDJIlUfAt2dMPT5WR0bdbNNGbsl4hz+G7KsBHTJxiHD+poQoxu/zK M90ZF+wJUPj6F+PZbPOxuP/bAnCA2oNHDQPNyUJr3SE51Iyp264vpgLx wtKV9nMNdSq8kyG84l4jf9EPGJsO3ZnNLe8kT2G8UE8TsS+cWmqHrw0t nTjrrw==
+; resign=20460416024207
+29r.subtree2. 86400 IN NSEC 2r.subtree2. A RRSIG NSEC
+29r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ZfXwUkPRWGLLhYU9Sgnx/JeybjuPWlT0bptlRsFijYQyxik3q2JyO7Zj AKpc8z3L0dTMyQDHqmROsbYRI+F34Sck96waEV2XZj/IxnISHuxvlBL4 uGXtVuVaMlzaCPMJemhMY4Bw3uliaDpudR2cWB5Yrq8BAsp2fUnbfI4i zKJ1RMpzG73ot3ps0MxUNs9bpIKm/Bce27rMFZ3dYfVOwQAJC+Qqq4DY h4izq2abPYrJZ3QBV5AyMP6b2j763IfgO3IVVEnMqe6l3lDadfG3vX8M 87Ow9NEnGhYQYJu/egivp0N6Vkgcz/h03rRvcvj/ZEv9dj5zpfPbM0yh uMN81w==
+; resign=20460416024207
+2r.subtree2. 86400 IN AAAA 2001:db8::
+2r.subtree2. 86400 IN RRSIG AAAA 8 2 86400 20460416024207 20180815062314 48409 . Hprna6yPTc4fP0vehuoaUXSrarTYKQCWXuVR+qkmpziqh53a9hmM5R7p CtTYOGGJuAhdHxBoUimhyAeyBn9WgjdikDrH/UvtXzi66jur0CpCdP/M wD4w3agcjtkLebjqR7iUJxZOYshqnu9YZpr34Te893c3Glv1fShnqi6H uT+TyEs6a/mDb6b6t7+ezHaE+gM716oPArNN1k/8A3BA56huUBDqBZWi Fj8ootIjauKf06HpOCEYHOH2DT9juLH8Z5naaWj5vjXeUFhuk/X3EfGB clnZVm/rqLyYRdro3T9Kv11KjI8vUvQbOrEyYkkqIAsBSsfQAKUjOHXp IIF9bw==
+; resign=20460416024207
+2r.subtree2. 86400 IN NSEC 30r.subtree2. AAAA RRSIG NSEC
+2r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . AqoSPZuchYPArsyXg8tr1Et4yhuBpSO44EpMxMRDNDbkLkNKrr+OMGKD OClSp5vW4npgYykJ3gxs6TAvmoywYT442Hw/SDhbVEZ1qIeH6eZ1BU1L TAVdldT53PyPfxek7HjC0gZyHN78J555pJRzif48u7aqsTfVT4Q2N9i3 GIsozjrhUqsB1Y3ovhXFqmcR0bTKpZncWC3yByjgwhlLFrxhPWpGx0t5 wJza8ncTpWab0DzkAiVxd/ErBzqNe2oGrZPcCm9kZ4dI6RRPfk7ZY8nN ekp5YKa1LCWP2uE5Qr0IOhig/IUFr9ovOuM7j5gcF44JR8YuPmV7Naaq Vt4z7g==
+; resign=20460416024207
+30r.subtree2. 86400 IN A 192.0.2.1
+30r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . TAQ4R6EVK2GqE3WKZbkV7vCpq9LyUWNWhEMZAt9732aRhTdrCa7T6tIc cqKOeBk3NP0Nu2HqoXMSjiFTu7Sqt9bWheRWPQpa+tuuhT4FOYDOMQOR GHAGyg0vWllJioJnwAUbF25PAxSrT7pyU1j5EZ+svmbbUSp2LFzDNPno exbe3f6C8MobjlJnYq8dkS5x2IfnsZHrG+IBpCAaTCeBFL2lX0XgbkoW CIjSgU7akg+J1vbZZm/W03mY46U3A9KVepnxPX5koIHtlWrFzywdPpwm 3dJHQXz7azK3/ObzW7zBkF6q7qy6fwxQ4tnrOo9q8+owHBVIJkeoeSib jS/tfQ==
+; resign=20460416024207
+30r.subtree2. 86400 IN NSEC 31r.subtree2. A RRSIG NSEC
+30r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . wS4eVreFN8KNi72+TIjrs6mnNPKyetFgZa+XOXTBYomcpB/FfJTfwTxu sSNbWXPCPfB/ebMQy3hEU9dSmEfUjsjj8+walUzihYdB1zFD1Uu+JwqE /xJO9YIbnzw8yg80bzomeijhHQ84avfEsJR2Arekxl/OcPBJE087bKxH JYtM+atz6gyoYFF6HjwEOetotnIWjKGfD2cjFKjGpIWpcQQBSBb6jnh2 /aHxtZoZQy84xnh+eH1ATFnqo2Tg8803CSDIewbj0wmnKGBBpiEvjJF2 O4jVfcmO3NQuLvFCOe+UjGdwV6w3yrSOXsBV9r/YLyYEvpY/1D4ISruW SE9Gww==
+; resign=20460416024207
+32r.subtree2. 86400 IN A 192.0.2.1
+32r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . lqk6YrQbBT8MuA/E1BYoeyQz+FHQTLDtEVPCqCaZJv+wLfUMbEUAMP6/ kZrGfqjKSzVx1yGt1JiznUAmrR56TvGqt+f3XLVum/o19P3wgHgvMjF4 /l+68dbeFV0Voq5ZyYvjJJEXm52lYmJXMFIVnG9ReNWU2u5NHa3rq7F9 5jyXOkL4nf41DH+TYgMaInugLn2ILiv+xFdluunl32JQ+CRY6dJ9gGh1 2Ji2Z8exyiDTw0yzMy2d4Wx8CFVgq4bODj9HOvUaOrhkqdFWx6Oa6wSd U4ilIpiSo1jJtpXmkydJfJW2ktr6hXWYKrimR21N8Qx/x+AnfbuRDvLA ZtPSDQ==
+; resign=20460416024207
+32r.subtree2. 86400 IN NSEC 33r.subtree2. A RRSIG NSEC
+32r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . nlM0hf5RT5YJpc6bSoTlpxtdnDcEA8tn8zJdfqscAW1ryvgvromem+6t 6xqT3S6gsfc4VTyWDsQovcfo06pqJ6/MYGgIoxG2pz8KXwahAnCvpGvg FKb7tw6iTRH80Y5AI+05X+tRP84szyPora1Mslx+2hs3yU0Ksjxb4DWC GGxCREuhl1gcoVaoCwFLym/fo2dc05Aot3qWwQ73SlzkC9rLYIqSIr6o X1LTNhQhM88HnV05tMyqAFOwI3QBMwqAadUjFH0sTTKk/7rdxBp1r7KA Ovj3I5zA6kGvzEj8QEJ24QNy2de+muOgOyiBc2irKJiR6Hcs4WOq8S92 5bMyug==
+; resign=20460416024207
+179r.subtree2. 86400 IN A 192.0.2.1
+179r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . LrFGFdv8SxFduIWMEtdjxDERVMMcMOS9VBgCc8C9yytjbzMnOJMk87OE pDOAmeeQNPKrVWRm2hmj1+m2fJ1yYeIweC37OQsODBIlfNVGjGr1QjBL srhOzgO7944hDMcn+9ieyTsAVc2DN0wnMqMceYwc/gMUSmFZvLq2cWJ7 J9d7pcvPKuFB7pG7j3J5OC6qyLM1f84eCsJ4ACHHwq9yDOIDR0U4mPuw ENlaV7hYWbv8Z6ToDzSOUAaUWDU/ux+nJ5F9jyK4gpu4gBQDbPhpTU4N AfPUBDoX+bVOqfMvXezMtLlGAH6N+bJqqqhE+dnnPTa1cJdU6tRekdGG xTlKhA==
+; resign=20460416024207
+179r.subtree2. 86400 IN NSEC 17r.subtree2. A RRSIG NSEC
+179r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . RfqIuAFqyazXWw2CBSNtbcPHzXPGCx07mw9lZUYFupendhDzQvM66C79 60qWJZY9wZ6CK28Z2j9ga2mfqkR8yFFnYkEIuDNs+J/RCcPYT/CGYFXE COv6Khoi+EjNTWKMACXU7noHW/zPHrR97M0UnakDf/dv5TAhqbXrRZgz N7bxpEofHDAM65DbqcrLqUArGWVPz1Kv+qtFW0y7XvuR9hgvcf0iAV3w bnnuRmkntHXAY8sauP5VgM68pw2AVvD2Nv8ISSC5a9amSHaJ88IRjz8Y zM9vA7cfCdYEFSWl0+RFjI9SN9H2TOaWKx6eThr2+oZ31L8kg6+WvtZL MB6Avw==
+; resign=20460416024207
+33r.subtree2. 86400 IN A 192.0.2.1
+33r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . rT6kqQOhR1sFb7OBZj7I8waE84EkXeUERjRoqS2zaPmo2xcji3DzsykK Hn92fZGLnx0xqWKM7a2eaLZgd3UJqu0OMp9BqyqD6L2eHsr3RzP8I7fq 0Pg2R1eTmILdITobl49Ef2NB3e3pS8MQPFS1zBbPoOz4koltU0JedGLm TJcq8R7WAS4sVDzqBB10N5EkZw9f7qwZnYMCdwc+Kc5gbF3b8RDyuG2X /7cKUamFjOxa2vT6aZkciY2LlIjIo2Sov2q3GCKaZXO5gAjTZjx2zn6D /YVGwrQ2+2kXgodu4Jmz1DP0CPbntI9AS7MEU+pcwlsQsZeqBrIvshj6 ACU/hw==
+; resign=20460416024207
+33r.subtree2. 86400 IN NSEC 34r.subtree2. A RRSIG NSEC
+33r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . YWg57hOL7GY6L335NC6/SAKzYEN43au0NwkUbFaUqTlYtCFf2eOFjBvV tqkU+Ft9zXV4ATqoFm5dTLRHsye5Ahx+ie/Afm/XThUKtPwJ5KTJuPei eDH19gj63Jt9T1e2BnbBNlWTWWicRBbsc+18qnPYZjdxc7KKv9RMEWmE sGp4KdmSbPAXFSC97924oltJFQeQzSwrVj+NxbvwwKjLtSJziyF16wTR sCt2XGmun5dI6hwaFAydTSgGpjeSvuCJs8KwOmRJg+ofCOluXgPbFaaf 292CyrT1QhZG5/N7PHrsgsixc7YcUHQzmCpgWFCcW449MMPwPP/IteAX AK/r8A==
+; resign=20460416024207
+34r.subtree2. 86400 IN A 192.0.2.1
+34r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . qcimjAuT269n/yygTYNnWHtjvZHyXfBabyB3AWDCnW2v4Sku0tXD0Mzf Ps02oZFoE7lglN8TYoqDlucvghXTUCpdw/Xrf2ijSM+/ulsOWzq5Q5Kv P99kO+8cbaYyXPbwLIHTLiZrxxI91yfvThyDylw1bENWcU1RQIjLkIE2 5dMkVpi6OWIvjFVyOSR2/bFn3E5pe/qJsoKlQ1H9S09vEo/TD7zRI/zl hNBdp8HFpY+qkEOMCfGyG8+nbEXprxKbrH8mMBTM0e6HI95jBWbfM3yJ B+Z0TXRISYQN1my2RH/2W7PxJnaV97Z/+bT+tsb5p/Z1ihwDq/h1EamS gBPxtg==
+; resign=20460416024207
+34r.subtree2. 86400 IN NSEC 35r.subtree2. A RRSIG NSEC
+34r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . BK0DNEGbK/AHsdgiLxzm8JVE5PdwwyIwn5z3lLtZ3YrR2OTFmxYSEpdU DEPur+1XiIMxT+Sb2yoNTXxK+gwb6HGIQRdTh3o8k1v4QLh9RbaARvO0 i+/TzVnGBmE3K1mXq/PYaV9m66vkAJq2dIs8DV1fCbQNNowsrjYso1nT PvRL5grVwt2bYq3qxeHByYWJ/XSNqz7b+ze2ZLa/y3FYDCeLoEF/zaT3 AVnoRvFqo0nvl4mTpY4Lh5IDg5XbJRyqyYjg9Y4lHJXvXiGJaTN1yCOW K02dM14Yva4eGp0HQLiceaEzlNOzEbFnNrehZTme4TIfONYLVR7/xuVC X+vEVQ==
+; resign=20460416024207
+197r.subtree2. 86400 IN A 192.0.2.1
+197r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . TLip/k1giQhyMSwC1uCfuRILSHacd+aUA+0XhbEsh8qs39mgZv/DdY5E 4xJaoZ4MzjmkMHYPofxwh/hXo07b3KXyzbGZq7ujMSsuNUlZ+gjZbzUY 6aV3zYhG5ucsTAZmBX4qQdc3ENVkz/oVDA5SV6BXgK2+b401gwy3sV+3 uZu/g6TheRyt56xRZI4Kc8NRxyjqXFBsZLJ6qTz6+WLuE/c+toy6t825 yPZKW+ZVQJ+qz8SThKZ3Y7KSRUTE/F0DWb6flkenVrt8Nw92Jps2wTn3 tnzmkGjkYcw7pB+ZZPs07zLl5euy8XSsfaSbEgfAGEg49JRwtdEPfwkU m7/xuw==
+; resign=20460416024207
+197r.subtree2. 86400 IN NSEC 198r.subtree2. A RRSIG NSEC
+197r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ACb3fWFqMV5squYz2Tscj7wvkUgL/T6OTb0dwFEBOqSqW765Dvu6qYEH fW0kZ3bbHgPNXwiEHBEa9R3QwBW7uBQtflGYy2OvuCMzh40KSE9L63rj uSpEzN/TkzncNO6U1UaGnYhc2BkfUCgWNZOLAZ9EzoFxb5aOaLMygrYI W27zzx0tG21v7WFlo1aSEpHHbIYTcq+JzhuFGDPSkeJarCif5ZzcEuJK 8OtqxTPj475hKBAv4WuC49wX9+n54koePs9KAyAI3qrkUksK4+JgSmB1 lJgW259pQahZTUrnZUC/HK8reOe+jlsYqKyynZ6lSI2VUUdbqvezz3Vq ZjiRBg==
+; resign=20460416024207
+35r.subtree2. 86400 IN A 192.0.2.1
+35r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . bi5mS4szw8tn/sWCtOn0l7C36ckESCPGT9F9ONJocCnFHLpLM4TRStGX I2tKmsj0n9uKcF4zrIGGdfGSnjiHSMwUFC6S2HIS1vwEStNUtB9M6M07 gHr9uzSYJ/3IqDAtbMUgvmzdPq3ZieuKLD9HqohgVbxJHll9wp8AKm82 M/5kVgj6z6igfL6WJh8b7kXaOjAodcLu2CO3yV0A75UxXejJhFEarGDE gOcz/WXC5EugWfo3tC4jWzUGTAUtZXmSblGRlwQlDxF8xMhNguTnmKi5 KdBOVK1Det/MmrevbIH+xPkM3jSj980VBgug51VjjYqffgfqNJa85PWA nmaGmw==
+; resign=20460416024207
+35r.subtree2. 86400 IN NSEC 36r.subtree2. A RRSIG NSEC
+35r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . DgvxjQQHnA3sEgn3bwFwS0AZchch1WLPt9p/HHEFj2ZPhV5lvHkLUGog 5Kwy1MIQBg13FA4ksthXrmrfHFKVbY/6hoJQZl17QD5Fcss/5BSmffrK NNFYoWSWZBnS1fIT5GZht1PJOJaW2Tgr6Znre5qvMTzuBmdjSg7XEw7J t11xkSVGMcjDeirVuSV6f/rGE5F6sOQFm+JjIUK/eHfJnhoiyV7xfDXz C5wO11z8zOYdtUiQL0swEG9gX8agnFifILSJhiJDKvCqweS5/dQbWw7i NA8kjolnA4/d2CXSwjMNgD0dtRcECrRVZlZPWpPIYqpnOY6yTsDoSc/V GG2q5w==
+; resign=20460416024207
+195r.subtree2. 86400 IN A 192.0.2.1
+195r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . RWYPW616aK8DqdJYrm2mwQPT7pbx6/XgT4CYEHnhHh0RZNidjGY5YV67 K2+PzglmTWR2Uzedd7p84lbyPO//1XNaJRqeOLd1omwtYqEiCjUEPbIe MH0Z18BR0GKPbzzy9DSRAAJZnOGvsrumu0XDUGg2Ea4FM+qAkFCZR+z7 cp0H0mhZZLQIGanjRC5q0qix6ZKSxfiwD2H5Y2XGTcqbcRcrd3vny5Of HYymaLjznirsznf303S0itFM4tulV4CqU5HUHWoLsVm7qYv1WdMisrUo M5UB/AVxOt8I1UCx1BJQth68oVYABV2aHbyohA19hv7QYKeZA0EJrejp tks0aw==
+; resign=20460416024207
+195r.subtree2. 86400 IN NSEC 196r.subtree2. A RRSIG NSEC
+195r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . D0VbTS60UMRR9Ty6w2HgDmIp643V3JjSt/MI+CrJwwPFYIfjDA+8v6T7 BIo9m2QZS1hPJdwQEz/PV5lrkU0l09WD73VjskWPr2hiAQmQGVylPENW 38v+BNya4V+6aEckU/ldwC7V0y8zoN51b1gBnDYb2Yj5Kd/KpcpTcHhI na/S7oeOsSl9UufRAh9lUkjaYw8Y3m8ino6SVbDS7ANF7CYCwlGlyyBp bf2cSZ3TeXSf/Y7GfzBr7PTTUOUMW9AH5cIcbnbG056/kzSNXvNl4K0b 0CEGBZt3FTuT1fX3zKOEfhxegDHSaaerHvF+/PkdexK4SZNODoRRTnaw 0IKjLA==
+; resign=20460416024207
+37r.subtree2. 86400 IN A 192.0.2.1
+37r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . WA9J4U4AzItlWmFpWAfw3oRmA8pLslxmkE+L3bOTkF6kYty2ofFU/zYs D4YwPNotnRFcKYsTbfaoW9RNztVwykFAS4ibQz9FFgg4pnXoE67DgTLL d+VCU/hQOOcDKmg3Huzmf9a94GY96e0fb51hcLcSZlPNgay+7KyeNMWD 8qhUDhbum9BuYccVf97GhnxFN/0n4DI6hBPjeU0POeTstCSafVayBQiq pWX8cr3TcjoX8HEdFDdBtTs4qdS8Pz0oBZY4zEkZO5NhuhFXRoFnbRQ2 z5NJwhVNrcfOvGsUkJlh4offEmFjTVEgq+ofZocRr3yl/jkQkwXdN4B6 Iesi/Q==
+; resign=20460416024207
+37r.subtree2. 86400 IN NSEC 38r.subtree2. A RRSIG NSEC
+37r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . dRmtxyE/pd3HrfPLz4z76U2BLf7nJaGrzQXHI5h6Sbsh/t9eaAImp8OU jQmBpzOvzg+8rvZ+TFuQZ0mYMaL6bN1t3+txBcSA65pueyJK8e5i20sl DzhVmEvEjqkOVTu2P2iiUGUf9TNbAMVELk9W8I77iK+utruxgTUrMqnV Z+U+Mxa67i+NEK3H8PQ78mrLReXici1TjJMwnZpm+BuZU50WY5vMH3Rw 4FTwTZWzgpTnz+Vu/8Qih4VT2MmzoUsZ5npCxfPxOnOYv54RLX0v7otk JhYbUBIfkVq68mParT9bJsQwSRr0pY4Gwq+4oZ5LGzfeX6JBqyx1k3l4 laI+jw==
+; resign=20460416024207
+38r.subtree2. 86400 IN A 192.0.2.1
+38r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . pblieozsh+O0C6VPPENu2oJ+lGA18ZeEC+jYDhklIgLltx5W+RcUgbjq eWj7gjecaZMrI9aJ76J2JGoairSj8UmIzZ737pSwWghRypW/pyins5fJ lcnTMzRmx2vxfwJLS93HcCu5/kHZS0NWtGzhOZmDVy1I1yCgsXF0jbVP YGSk19tfpYS/4VVRhVBB2waiS3pLFAxqe4svVDoTuoFQU3fNRhr2bJCe Maz4J9d8P76aZklq6ayJSbBkOMRUVYPJzCb+tXiNam4jbHQX5PXN32eC soG7hSvUMLkWHv5lh6qlL6UkbH6TvBIfWFWP1249JRCzVauyydy8w0oD cVd3bQ==
+; resign=20460416024207
+38r.subtree2. 86400 IN NSEC 39r.subtree2. A RRSIG NSEC
+38r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . fdUgsF+Ooo37b/16LwnzUT/siY3r2sYOZbmw8AzAY7pUGoOkBTtiS18N tFxfg1FLVEJVEI5RgFio+UVHCXqo4QHi7+pPVX1rJUE3l3W6jWXgorek du7ltSs+0lI7eNLsVK3QOr+tnrGDraYEPDMSdFSHP6ut7l50nhA0z011 T5cDlTBbZ0YoDmTzRJG6mfcBJImot2fjnaXfLj7dnTcTepPYymaD2DzI 5ehDBcIEfbDitLesT66hkfPZgEiFpBgesVuXWH0Gw6UKtzVzWWy+OjPO sdOjIQSCQOCmTjleyqNOYwyjPf5Gk9G63fp5gv5u8M2P0Zz2YsbYpaNy RudjMA==
+; resign=20460416024207
+39r.subtree2. 86400 IN A 192.0.2.1
+39r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . P2mN37IFjmvQJS8FOFI9YmH9A1Fya7Pssvowi8aqe/6/vxLh++kIgQwb Qpi0qZ+q7hQPXHNL4sW9nnnSUL5EtTNIw0W3XWG8Z2iHbO0fQiK7rid6 kBuexJGDg7nwm0URucUQAeF4kDTeLa+4KVzGd+YMph7QQ0quUvWNFhZD sW3dtWs6Nyq/5KNgcUHnMX+AEMwHDKl5bKjdXdO+oVWlClx30bTjw4Y5 PAuDcyZ1qIJ6U5C+9FF88paUF9pJBKBIJHuxD8FT1yxibw2tjy7cceH5 +aeEAzgCKBPlyaqa01i/V6OvjuiVHmWkbe5NaCi4MG5tbcPBmFz2BpYL 0ndKDw==
+; resign=20460416024207
+39r.subtree2. 86400 IN NSEC 3r.subtree2. A RRSIG NSEC
+39r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . fewCkslhKIV8nmOAHl+4sSZfpEtN/wMQv1J60LylOq9eTDO7P6UkgWrb Uhp0Kmy3rTkSMUASSf9rBF5Kvp0gy7tSyzB/63AmXLowfc6DqXrudUXA yY6SlmsRoMCnHh4ZOLi9QeDnkPJ+sR0Hy88IA2OOoFmwVPxInXUT44m5 xONo+R7vBJbJqChf3tZQ7DYreGstYtIsC+wa3Y6rJB1SVISgOrOF2Thu PfoFhOQSySS4fZWTcB8VNA/vh739FQfEgA6T8wrHco+GeIUPQ+VrQ7pM GuhjU22bwpMIUbJBPzVPPhsQX4U2qkBQBfoMaaiE8+hR2DM1+lmea4EX RW/e2Q==
+; resign=20460416024207
+3r.subtree2. 86400 IN AAAA 2001:db8::
+3r.subtree2. 86400 IN RRSIG AAAA 8 2 86400 20460416024207 20180815062314 48409 . sU3DOOce80mR6N3WVfsaf2Xg0Qo6QnGLeahMzLs4YjwYS9XBxUd61VH2 mIvtoGFTfhoC6gy8I4zP5gIWNe5NjiS0BPemHH12i8Lkb+1oCo7KnWsH JsN3fSB6eVnDxV/LxzTCWh6gbB6Bm8q8TlcfvWXJtU5hCAsy8HgWf0jr CgVWTzFt51+wNpIARB3FvZyLr726l1wS7KswNywDUe1MH6wa5w5nFiE+ l5X2K3T4T0JA7nwkl/are+vHOEG2t7vAr1BIdMeshVKctmlwhd5liAp0 hYchVGlSI6OPItTZmNyPRfeV3THnDGmnyQG5b0AFqoj7Te0l2zWjtY2l PG3iAA==
+; resign=20460416024207
+3r.subtree2. 86400 IN NSEC 40r.subtree2. AAAA RRSIG NSEC
+3r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . xKHuiecej9QlBaMSRUrJ6v/Ypf4uoW9QoHwOegu8smoNff72DEqPJqae D39SxdHR2ScYSMC//s7MJWYersa3kNnlYf3SBwptmJ2ujbRtE8wnhQCK 8FYKm7GlhI/GQucUQ2wdepnvK5pgb3kCBXjhdOU37qVm6n4qZlF8dhdM dOk+lQVYOPQO3Y9B8mjRg3mZQlMElalsy1po5SQ3zl61hXNnIBPnx9jA 0S60PGW4ld5U4x5iB1FEPPA8D+Sj4Ws9bFrqg1dCb3XIOd/YjqVLancF DfqM284ADkrafTJ5UPV50ZfwrQZFGBLHEoQKPOmrWOyDOxpy9vYHRG8m Yf3XfQ==
+; resign=20460416024207
+40r.subtree2. 86400 IN A 192.0.2.1
+40r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . A7xKECnu9zOgb4F2UURLoOyUheOxxKcktttx79i1yQUSurRNYk6Ysjkb Q0gT51XiNlSUff3wmFoouSfhLf4VZiss86KceBEhFMjjQBguGCYk1/S+ xqYJv0UJTyFWOM4or7seTziJc8TmD48m5kkwl6//mTBzocKrzx9TDQuT /we45WxJ6zuuXqr0ZuvPpmCL9KzkNb0HDyPVqkZxhhiIqWFTyUupJP2g bMKZTg8sFw8DZpW1K/u78Q6d4tYT/GgwSI9qa4O5wh0XQiTbjF3n15k+ ZmvIxf1GeWByPPtKwNEyC/aDqWKO5ItidUEot++m7eiOd9zEzaz7Je96 4wA8tw==
+; resign=20460416024207
+40r.subtree2. 86400 IN NSEC 41r.subtree2. A RRSIG NSEC
+40r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . MIaDOwIBq4PYeIy9D1ylPnQ5HxzEf4VanIQA/gMOPh7Z1fzL8GeQEtSY +EvdfhdoepD/uWdlYufsDI+LCJcPFi2FCn5yh75IzDUrQrZwIkjbu7rP 2gc5BLldsVXeyWYzE6bzIZLQMjrtzRNrabm8j8S7dugK1K4XEaypnZy+ mW0ChqV9+GjiO50cadKsDvVTl559vxnHwJ7bqPJUPFjU2c+frvnjIeb0 zIhcY04c3moujdA2n6s/7dJrw4OrzeolRCINFXw2t/tmErIiY/cmjj2R CXwAs1VVJJ9k8wInh2sUqIYMGHHw5gZw926waaf6VMSaOgDqGfbYrhLB ROhzlw==
+; resign=20460416024207
+41r.subtree2. 86400 IN A 192.0.2.1
+41r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . bsi86MRk/KpA19c6ZJzuyn45MfDYEASbGe7VST5/BBofJ8IbRyl0r801 vlQN9fR3dGhceYK8A/9TVctwQEJL3D2cOA5PC26GAQffdMhAWa0ymcKQ DjjIGc11IXrvHTD+m+WCpFoCEMXXjLXq7qizZMZgMH2cERe3gv5xKITn tFjGk54f0rPtz7D61Or1t+O3OK598ykTHerIuPtoBKl8jktGvuv4N0ef SLF3aGa/sVigDFX4BdMnoJ6JlLSISoEe2uPM3ErzF3emcK8cl922NrF6 HKpZN8DGaPrHHFrTST4/S7lrLshI9PI3WhAhZz4CaqgMD8rNM9CM19sv dbORgQ==
+; resign=20460416024207
+41r.subtree2. 86400 IN NSEC 42r.subtree2. A RRSIG NSEC
+41r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . LYwfi7/RPz55u29u6/IV1kcKwJ5kL7HLq5a6fy3jWyx5xgIEjNuiHbyT 6HnmsCqEEc5P5NJKd/LlOqRi5gFXdzj1oyXp1fzIjNCnG9Kwc/8PxusM eJ1COjg4FHxd9+ztUo6/PDaIrFC71pjPctc0GE3ZYO0q04mFd5XspZ5Z kEgmA0/BgeCGgOPfdoXQcgiSDUHxxVm++8zXwGBiPxoMHLFjZbSFWZUB GYyEF+v5L2bVIbUj1Bg0bCoNB6NjwRLC2i5uYp9UNIphsmL6cn84Mn4b 6rP2rZA3h6VYmthSyt9Ky9V25214UnSsjavwqwNGtye+Z70Jx+I9sG9k 5M6ZVA==
+; resign=20460416024207
+42r.subtree2. 86400 IN A 192.0.2.1
+42r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . qRnF+bZGZ00XSgjQKU4lCgVDrSJhVqGbS1Ol/V9qn8hvzCB4YfHWcA4C 56sQuaQ/KGIC+WmJyPNpvh9wgcBduHkS1hUlqPij0Da4dDGLMEMrUTFU RKK/2qzdUilH9Ults69x31CYWE/ezqcjevRqPbjn3cdManj97nJKhR5w EXB8ysIsctl2f33lp0RE/JPKUpazIZ4R0sWt4vv+CbR+KNiv5EPU0eWf 8CO89fy3elQNBVmPwERRkBwRunWpQpzbS014LeNg0xPn9nDAJf2Qe3i7 6ZO2zr4pHipvu5IGlxHXllgtj2XlWa4OTnzZlVeHk+28zHedGTaUFWR0 E3zolQ==
+; resign=20460416024207
+42r.subtree2. 86400 IN NSEC 43r.subtree2. A RRSIG NSEC
+42r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . UYhqKt3G81IiJlxhFCqv9bU40d5YJBIolLXs7LOF+nHQq+9gIIX6Bfff wbxMsKlSzJ9fJ1Y1+IxAXdh8Y/YlhrXR7UtoseUyq5VM6x0ocSkaWOOx N0+cRtQKZ6YhKJccGtkoNHHrri1uSoZ51J9j+LXxycJQ9HfRj/fDJNMo ymgX93WHDJSGh0avAWrr8Mb9MgGjXyFDsLR4cZNtTAUga6TGkajEyiuZ c6m1j9xP6K/KOolkcQ6kz4YK44PS8kOwmQs4/SXGPu+3MFEbRW4FxHMq p490lAFBaZJuVT5DcnOep1VRMjROIu0nsTYqp51ewyV2zkMJoUvCSnnn sM21eA==
+; resign=20460416024207
+43r.subtree2. 86400 IN A 192.0.2.1
+43r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Kvr544ICf8eFbn71gsp1sXzvkI0Ex0iJdgwsOWol2RpMZQMy0uuDCgJ3 EqsMrOs2ur4X/iQGdl7KEzy4gb+geMX+PiSI/KRKL+5/FNRUSsWWlSGl LE9ZibQOCG0JBatP2pr2tWhCtafPg0FYi3YoM5/o8j3FpwARAE4WoLp2 44sIXy+gr43SxGO3t7tqM9et0R2ZXcIxm/DXDeA0HvmXTHNht2blIwMM al/hKWBnnv7nFL/tdcj3ObBQ7acQvLHG5UQEs0KDa2qgEg7uhC5X9XfO x1QRaHcWsRUVdg27nSbimjdkmQ3EeUos2m51bKWYofyunxzKsghz/tD3 o4Zrpg==
+; resign=20460416024207
+43r.subtree2. 86400 IN NSEC 44r.subtree2. A RRSIG NSEC
+43r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Fn6k1x9T8zEmERcMIoy2xSkEAtktFQKwxssGawlCbMx3n6crJrP55izf MCX4XimmDGQhRo0m4qBJ7KHqwiD1+/WvAC/XTobA6MIaOXlufoX8pv5n 68DmKY51PeBVbXSNHUh8WYuH+6tJ30zGJvm6hjtq0+HuoG7ftfKOo2u/ 4CtdeJUHJZFHFe5fcWuG0Kw/myPHxOjnsG16u4Fi7VdVVBEPSsIjhrms fhOVNc7tmxboVqovy/pbWGF0OlkR8eWWSau2vfWObjz0tBCnvGpm23y8 qFiO4aE2C1U4Ih7NGTFuZTqjDqinXHDGUXqvJ7DV7GM61uR1da4Zcq05 qhXMfA==
+; resign=20460416024207
+184r.subtree2. 86400 IN A 192.0.2.1
+184r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . AoAoqS2lHHYIULSHCvxh8Ug4hHZH9jLaqQT7jp+xEo3U63ZZS/EU4oYn vafgLQVPT5mFOI5bqxewmLRJwypAom+B2dte17UwScQV/xdFRO4rEPyb p6evXP+LezVz7GxuOC4itvgqjv8l6nNGSNtelA/BmreFs4wMKk0+DHqC mOtutq8DU1wcBpqmY4DuRKoqtpa2qu51hQ2kEx0HaQvAb1UgI2Zw+uY/ nXER8wLzo+WKYhTGWjLDg6ESn8HpeJlkU16Pbs8A5kAv/sca5nj9TFw5 h+a9bSbQbD1aCWWEFjU72KynjkLMD1fZpw2PyqdviEaylyhf2AXgYhfm 2lH1Vg==
+; resign=20460416024207
+184r.subtree2. 86400 IN NSEC 185r.subtree2. A RRSIG NSEC
+184r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . OWD/nVsmu/buNCdLxqtVTdf3pMHPIed7KNxMYZR1rhMFeR4Bc0mEVmaz UXg5GY/eUhaQjehddrSCxkytGkwi4d1kwaaIrpoXvEIGAWBicYMmg/dM ZzYMfzayK+L1q49hjaitJy9k+sOQpqPY2dkKijHpjPf/hEE00SXVZxeH 93s7/CD8W6Eg4TvSGcVNaaugVqLkuOyeQ+tWK0Xln7nJvjv04OaQKKaP TaEWP+3aN+X7mHVQtL7HMA9+WrSS5XD/CZDiuhPozegtu9uHLhvdKtSV 1UcIuMTSwSwK5FjHbC9IYQ7OEq18g1xfp4eztDZgcB5Ol98WC6CqFesg Xkg/uw==
+; resign=20460416024207
+46r.subtree2. 86400 IN A 192.0.2.1
+46r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . VKFVihKX3Q+MWR6GdmeAUnl5GMsh3KB+fBEyvEEw19M7ynjMfZYzVbjH V+AG19Bv9yuh43xL8fHmEAQENB9Q9WBzjZtYEonjVgSw69S2v1R6+NYW PLtMpDaH2oOwiGZzLi2Qia2VmBt3Ahc+dGagb73ex1IOS6ufo5ypY6vO 8x1ASGTVbWXv5ohumDLHu0iS7D8Fy1uiufzozz1bacOurw7pujk9TD9l 64ez6AFWHC+nSP0XdojhZ8Ce90ovVKe/GCMTpx5OoSAMcjQtvX4Bg6J6 ujAmHyGyzm5hiDeiAvb8WVhazs2t06e2ydDpNCsI+X89HYYMcNqzFnuN QcDDtA==
+; resign=20460416024207
+46r.subtree2. 86400 IN NSEC 47r.subtree2. A RRSIG NSEC
+46r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . tx5Hn5ZIx9soDDoHxWHwU1jDu4gkXQAXTFA007LazLVO4n/egQY+dejh yA0zh6Y1G2f+sTlmlv8h9lh9ErNwjLmbAkkBDZabRRYzuypu3+94alFp MVyDHPOSycmJeqrJ60iMINk6rSMjK8W8YoPU08Nb6kuZzi961RXrUbh8 GmAQ1dkb956i4y0o/ziiJKqjytdxH1eioDRiM4Twq/Wf87kK/FeSq1XF S7naoPim1ByXoT5+/6iJU9eaCfGGKAnlvLo4KKpD1xoyGxkuB6qED5yM XhxYJC6XwyD+LG7DAAMW3om6pXIdyzvaJ9XAgzLTSBGa7FhjIA1IoUf7 3m71jA==
+; resign=20460416024207
+47r.subtree2. 86400 IN A 192.0.2.1
+47r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . A2e7UIuCTt7O/bwgjHxQRM+5g1QIqlSOGYUzIiEi1xrfvMS7Ap70D9PI uU/tRQJosxE8E6Hc07wesaBAAdXLuWKX2dXTJ6cjAMsZY//phpvn7u4y 18sRFci9QYkre5z4nYFL6Zgwxi1MM7NLM/J8lsTVzgoeOK2V3Ey6tpcc qH1BxUCBFgE0IWz1Xg5P/ZjIBdv0VMK/nGipatg2mj1b3szoT8h+fPO7 RqBAFU1PwBlzuc6bDi+KYqo6JIMF23qVR1ePTTXSkzgY67J7O5+/CO+G SXM3uoHBgQV13L8HUgcp2+KRYtwoAHupBCL2Y5UWoa6sB0i1fojPBdlK taE9Fg==
+; resign=20460416024207
+47r.subtree2. 86400 IN NSEC 48r.subtree2. A RRSIG NSEC
+47r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . VDLbZl5v/FtlQvH1xGkQBkx9eh98GJmAMBVCUx5x/tcesCBE68TJveyD AMS1uq8McnCk0x66Qlftp1bw1e1rK5HtMfXQEGENCBGEoXwCes8vpjpy uo85nAMUUsGvDnAsALGqe4DCuablB60Lt5OEn1pAMOun+DsBwFFlyw1G zbo06EHp0hzdaqNc0qcsjIpZDhUqen18KRUDoKfBNKpMe2IU+X3r108L G5kGMpWd3OlEFY5veK4t7V775oBdCE4CDfbSCpa+o2LZZDM4H0EEaDMt vFIYqnLoGO7wohqibFsbxYUOkeoPEod4CWZqPyHf/1Dgkx8J7yzLxiU8 1uCK6w==
+; resign=20460416024207
+48r.subtree2. 86400 IN A 192.0.2.1
+48r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Ykkc/1flKPWBnPKjhiRWy4wz/LAh8Tt3yNRhGnOGuDBwOHO/9Tq89xDT CNeDVQTf/+pLL5r6R+MVT9nhHQqgKdaIGHAy/d9WBswp3Pcm7ByqgNgL gnYwtGpbLrc8yOEkHCWHSfwhonB24itU7+glSdb/y4mYnkNAxV9ueaXv RCHIXugid+yLFUTrhdhB96qQbtlxzRxQ/enxj2b5O5YHyJ2Rv2FlKatS RqFamOUdG7IDLkCMFi17CrxdxWJZ5DTrHlLcke/AegTK1Yc3l0Gxa/aO xUN7CBYuFsB22Q00pGEISNAmD7+pm5Dc5TYrEGyoraFtPD1MNMHoQNI5 9N7vLQ==
+; resign=20460416024207
+48r.subtree2. 86400 IN NSEC 49r.subtree2. A RRSIG NSEC
+48r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . QymfCKG4unijOqKrhyvBanMddtMkStDiIBh6CvK3Nhf8hhk4OSBkytpp 3M2Ttx+jgKSAgSTqznWIneoNDP5vVcbyIYmgXDEkYtfAPc1DptzbfpQq +vszKECAEmyVVWeqSZyypp57TrIYErFlnEWor4w/E/EtyErisCI2i+TK Sc8yXLp5PHfvT7EFh55IW4vtFABm/8naMNPcjFAmcH7fb8WG9IvfmXtf pZ6XpxDXDK8qpmmI3R+3A406Q+odh1i2H2ThhHqdqN6CHvdHDGsPgsSO 91dJXzO8vk8JKnV8Aqv0BeOS8ibDr4Qn82HfnRzVCDJo6LD0zNSxZQRa iIfUYg==
+; resign=20460416024207
+49r.subtree2. 86400 IN A 192.0.2.1
+49r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ezm24FUxsSADHw205OzxUPFqiVQqmqU/ulXC8Zp0t3WyH6loCR7Zvkg8 J+qY+6UjHAsLsntIVsgbgdrz3hJyyMPNOv/S+xuxSVXsQtisEbLv36Pn JD9G8Bb9bKS/1jwHKG80/AHeEOWNMHp81XKw3Ed5rgygvtpN3qHsWU+2 eyEHgY0izOthVLETkAEbPXdz87+32GRunaNPExsd/yJuEB+y+55bXzzV zvABwWFPqujHtRiruqheAe/eb60YJigIESp4C8PKBgWoD+JEt1wyqssM VLG+M4itKZJamxhOkhcq13R517e1ojVBahJVN+ebGXPYu84KhThDqu4c FtHXNw==
+; resign=20460416024207
+49r.subtree2. 86400 IN NSEC 4r.subtree2. A RRSIG NSEC
+49r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . hdzkIuLtYPu/vNfcHDQKqNfpEZiya6TzbBl+TpkHVU3B6VhD56Vlu6rg j36ZBdTVBk8X6hB6rFjfBN0zFBz1IELsfrKIM0tQbceyZcvAtal07nCE bmDOjYBwKhgJAJpIm9FGHJp1NXj/QtKDaLgjjG0pDSieSb1GVBqVRurg D4F1Uu50O0BkvFSrtaNkPpvfnVoDl6jcXW7SxM+9AzNZVN8WXxGP5qkf 5zrAhV1AdpadSJRkCG/K2QE8s1ieNKnHunrbwZJddQmpI45GKqx6Y16m sujnAFuMhQVpbJu96ZJC+bAcDwZJq0k2yRRhouP7fGG8U59jDtdLrExp ombApQ==
+; resign=20460416024207
+4r.subtree2. 86400 IN A 192.0.2.1
+4r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . F9q3QcjC09ypEBWW24dfyAK7ir/MwOwPBmn+mJTDOV3yucmdddGJPIj1 VbkasV4bOfbAAZo995tpqOzajtv9GQXRJVnFPOXMhlsH0ivTTOpXNJUG uLIGE8LQ5bc9tBBFtsyKjmNzfi8wez7gD218BLsFcHjbvUtd9s4BKA8M bUQ8bmGWx/GXKQ4Y9M19kPq601BXdZsYgEgVSIR4BXnp6eN3Q0nI8SS0 7V38fkflVmkJ9bpNyd9RJ7n5uWLabp9zjYzeAKyEtYV9HEcwnfK6e4Rc zh6iCQwFzL1pGWj5JSf4kj7zmgFXDXtAl11cYQbN1dwFraA/omdhqCLF KuENQw==
+; resign=20460416024207
+4r.subtree2. 86400 IN NSEC 50r.subtree2. A RRSIG NSEC
+4r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . lWmq+K8ctJH9sOBT55z1Eezl4nH8cOiqOT/1NqOYueq37sasHFcaQjUF gQyI7y0yXjD28cV/qHp25Q628ykNdCI05QkFeZIRUTEp+VGtvsw4yz5j DdH8gju9y0eLgAbHz9KshK5mSAj/TkCkVs+nZIWlPNiphSS1v3o9bB+p iMW380SYckAzzcxMstgc6sjXnx7zyXyXOm5edpSbAKgg6l6x7/gjtriY VhwhVorMgUFCtVYlmaPMmTaLhVoZIBArnjAvJmjjCIgNqz7yCLxZjKKC k6HHFB00yEBM2I9AxoR28fIYuAfnjiAnzbjJPm55/ZMHdvHw7s+2vnbw pGOq6w==
+; resign=20460416024207
+50r.subtree2. 86400 IN A 192.0.2.1
+50r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . lLNbG28vWwUBWSYedEh/fKMmV8/Us4R8x9VI1mq3+I452myBhHOsJ3bl p+qOHv+cZEqo+xzpYDAUeUnNaobLBN/4j8kmfxNFuB1z/HkbRDXvJNr0 nbA6d6fTt7fJvnMql4fU3r5D6OitXeICpLCcbvQiwbneEohqtnHrwR+3 Q9WMazvhOw1a/2ByTejfZpc5sLON124g6FfVdMxC0XaRIEqYNM0nMAxF n26TYRn/IEuURr2eep1Tq7fptk5GzLyjKi6TayaTY0UEtcE0XNTAel+4 lv1F88cexbNZxjL2SHCknlMgqHHN9AS8z9A2gSBvsBm4jzRe7XkWuaKL FJA8tg==
+; resign=20460416024207
+50r.subtree2. 86400 IN NSEC 51r.subtree2. A RRSIG NSEC
+50r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . H3cmRJXJ4X45iOimYZCErjCfKfbHduVSaGI3YMtHJSogv8/Z1yFzmJ5I pn1GxWLz4vpi8QUCAZm9owO9yyTaswo/WW0gw8VLccV9GDLaKWFvycZ/ uHDXldXu3fc/Cm7PN3vfLMl+Uol4bfYFqeVZek9QfI6Ircx8yYzUGcpT LvnCHZybdEojzcrrvG0yk1LlZtRJNuGRw94hvDGLwnZ+a7bmLdcKUqEr MLN/TwNSnVql7gs5rbXVraf2aCoM63INfPn91lm2SpzwWmNac3nsBlaI r/5Hg/OsNoWGig+Rot7TgJjxtIv+VLY1+YN/hyHujzYpwyfQuIxigB8B TFQ1VA==
+; resign=20460416024207
+51r.subtree2. 86400 IN A 192.0.2.1
+51r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . gBNGIHyXcyLQ+ZU/0R3kRf3H2O8kb80etRU2/+5288n1fVE+nEXlM+Ya Oc2a9tl+XMRye0ZkATzgC1638pgnV0z5CSzFL9LyCMx7xe6W4gXeilG9 Aesrf02goUqc6UJ68OdplU5dcfdTT3Ij7Hl9uw+UQN1gD2D9CLSntQaK ME00tDN4Gyls3HdUOp/TlpqsD4ctntH1G/9L0Nls9YXVfFD3WMQNG4+M fmurYbS0pDBMKEqq9K61Yu8m0urSbg1PFueapf3873GefLT+4qzwGjta 9LOSfIuVKlQnWtWKRP96OLgTLOQABw1jP/p+DSH9AdNoN7BhHtJuQVBF D/fOJg==
+; resign=20460416024207
+51r.subtree2. 86400 IN NSEC 52r.subtree2. A RRSIG NSEC
+51r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . UEPbjqGh7XaADbhF0OL7V045Dzp5EpX2uqeEsXI9D/FO/yHaO0QJDVNP C7DqGiJN3MJtlFurpc83hrGf1SiTI6CADAGz+osDktqYVotn6hvXRKd3 0m3KUocCfbBucsL3sNQNGu0fBgEoVxvA0i02k5E00T+ltdWHLPsdSWMm SzdbjKpyE48Y2n2gfEtoaFKPonf5EmjjT3YdG+qW6CwhvhsfgDizXmWe y2vjBB5r8Ectcfk9RRYu4Ru7xRBioH3zQaG93RspAHfvcSACgastkBQy AP7fdnKq1LWCE4Kc/1zVnaS5CMPOw9vbVy2hr3geonnsRx3/+NXb7Q1G MSdvqg==
+; resign=20460416024207
+52r.subtree2. 86400 IN A 192.0.2.1
+52r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ewHkfBF/8w9XGYELtXG6Fp+d7WeqJTTcd4zMIpYKBsYKxB4XBi/YQvnK 3uYzsBV0sK3OlSZf1jDqUQ34lrLfQ8nGBD+ljyyvy5OgXgQS6MBN0u5o 5kD1wnSLFQPcrAzKjwVhxOK/DbfxoVdtSFII7gh0PXrXfx5jKTMd7HDc BipWsAZIz2twp4QeFvaEI54iwds9rotagYWmPYOoc5QlKYlxu81siI1g m3EmMxtPJ09BcGhsQvBU+djGqeF7oVzrN/juUGeQ/JWlRQYOrlrdUvon VQgwgjnV1rpFNqzhZ6L4md/SGOE6Izg+rdyp2Q+tU627CJTz1scKFAzR M21J3g==
+; resign=20460416024207
+52r.subtree2. 86400 IN NSEC 53r.subtree2. A RRSIG NSEC
+52r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . BRSvHeJ6gloEmQhGqk2jSenQ6x2lfeJm9KT3rDuA9z2kAdoTAQ+Wg9eU DJ3ntqXBL8sBBcnHc7U8MHblkt/jGbSsvDJ/9T0AWNMuBpflemuCimT0 lNnmNcIzUbOaiwubIolpP1FhG2OSA8vbbgt35Ne45GYw247YDEbOTjvT hB8nnh8gQ5G72qjl+InXtCXoP5aGfEDAOQlenvFrYF7HI7PIykQThodW 5KjEnKo7pPA4feI7FwlTghtHBZzFzOaAfHVdxzrLwgh70WQPmyV9AxBS o9LXuCcrPRtNfCOvxK/j7LpWUP4R70t6T0zR/IZTlzCtERxPqwtzDuxn ivNLgw==
+; resign=20460416024207
+53r.subtree2. 86400 IN A 192.0.2.1
+53r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FFU7L1h4L6Lioi1W8cu3Cp1/K1EJFvkv0zAZz5gykSQuqeOs1RY6eOQw qXndjRzl02Kel7QZK4j/Qy0rEaJ6ydwA3+c0YFOznWMngWiZiVmSlyRr m4PROgaZ22tMdYp7z0mdyquSD+CljiCf2k4PXqsD+C/NpKP2gUXe7agp rsuW3wP39QkqgbtBgbkezFWQDX3TOLeRcg8beXasRB3vsVIDW4VGWaKi lFJRBsLkZ64CC0yk6DXDQMSIeYmbOHVMneL2CIPDKFVJ67IMXg6IJnGC N+ADMuT3B7fpBZ4A59tl3tWDCYWniFxXsWkKVKDy3eZ1HXeJhMc2TpUd NXbwWg==
+; resign=20460416024207
+53r.subtree2. 86400 IN NSEC 54r.subtree2. A RRSIG NSEC
+53r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . PjP6fMBsBnPv9W/U21JDOZbPc+ZYfsmQPv1yhoS9mvIdJ9dvQ+fC+94o 1FHUp20e6LhIvtw59Yev7cqE7T+k2YLVS+7/WXDPbZbi8q5WILZWdFnR +D1pJL8c1AypOYPGEEcxi5tdyGJLPkenbLp2ItHY5qS/P7Wqq4GZYUp4 Hun+2aL53pb22DSbKYaB67as+MO65th+UM+RfR1yfcWJy2npxxrpq501 FAxFUKYBEpmav+nqb0EguHbrcIr1vuP8RN6P5h10ztS33Dm9z6QWSevU QRJKH0pibyjlx5/JUZW5dzJiB4KqwW3r7hW+VaimU+lgEX77WHCp9DiT tqsGhQ==
+; resign=20460416024207
+54r.subtree2. 86400 IN A 192.0.2.1
+54r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . BuAtQSYX7m9/L+aQNy+fGFjXRJXJneIsiDYy7pdQtzxzVGr3soBBZHjN fpjCCc6UhFWuTBnY6FAabq7foiZAMeEACFLG0vpSPyvIudRs0muhdFwG mh/BCddf4SKZO8tcdMmgqa+zsRDqGFiPf7HMcQtVVoNxZndRxP4/1Edw IPwk9KNFQH+N/QJkrrniEN1vGK3Zw0b9VRY9oUcItIhVmzeinA9a+60G Tt8MEZ+HQE/3yvuV4BtdHghwLW2w7Y7C8XRd3MZNdA0n4V9J+pQAFuaD 7JWPNC90jgrzIPqRVBCVLT3UtZcArGtrUsROQnL5OMdXFH+74OH5yCBj ioOZaQ==
+; resign=20460416024207
+54r.subtree2. 86400 IN NSEC 55r.subtree2. A RRSIG NSEC
+54r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ND4W1IR/Lbxy7T5te9P4gAj67ils/qnhHvJDZ409BLe0/Mh+cdJNZaF+ zERJElLqu6e0IiJI4R26AYoWNFgccDzLIEQLJRbHU8I9i+GoA0iGxevn /gW1hoFiWFNLONkjL7948n3n0qcrcNtfXwDmqjWB2IsblMpy5lBqAXu/ SinsqHuwqD18ApZlWMrQHwRKd5qyE/nOGv4Jc8/+WqAII4ViI0CuGrGG LEnx9rpZ9JOBg02SolW5xZiZ8SmGggm3BwWNti3XLjBun12leVWdBjB0 0z2gbPXapoP8luwm88eSZa+h0MHJCVB/980yCZW0YXMP7dYpshqMcCy2 ROJqeQ==
+; resign=20460416024207
+55r.subtree2. 86400 IN A 192.0.2.1
+55r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . MxQe6tBY2obbRhjbQS29CMBTrdxeHthfPokgR/NVaS5oWQWw7QiLh8dG dZKMAYul5kLUx62vL/Alke7O0yX+T8ccpWW7adiU4P7WFBTJ+rsfNsDM 3pne/vQmjqvwwAAnhHkj7BjC0gvWo7JeVzl32IMu4Q1MLRJ1wCY3kGRS hZi7WZCT2U6+3Hm0JNAAOY6Ikr8m/daWE4i2g4xWiixCNCcWN+bT/vxM 9mRBGWMqipLS0jTufklwFcJtp9/4NbaEa6v7D6EhCOgRddzSyCrWaxlL y8DVjGCi5c6suEKnBuC3Re3EvQVuf7xgQ+UHe/9AZ6Wxbjcwr4vqQkwI i7XKbA==
+; resign=20460416024207
+55r.subtree2. 86400 IN NSEC 56r.subtree2. A RRSIG NSEC
+55r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . u3Bl5V2fXDU+BYUu3Qgj15EqHUGFQho3sH1QM0wIK4xEQEA0uV7jRHQi tet74jndvgk9oUbWNr7+9nFkgXzP1ZY2A/VS2anLRQNnvcvAX8KUFS4e C3v/0Mgyz/2B9Fu9/5Ak7Aa7SPZDyrOn8X7U7Y8WWdCzBuuS+5JIAgxR GSN7AWD9I/niNSfRej3YGWYRWk2JrHOgyKrzKivBn19y3fZagc6nRv7I 4fDWVHIXj+zzHZi4JQV0gLB6kqYbWhylAoamA7lIGw6PJbLgm3FYZoe1 CY/YNiTB5mGfnIxQBNB6Lp7FgU2DbiKyVLna3Ba8QGSkiZqN/61XRQX0 qlUriw==
+; resign=20460416024207
+56r.subtree2. 86400 IN A 192.0.2.1
+56r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . JiBnpUpcjruH0PMTIpYIqqjT1ICqbDzEVTcJqtvrh5mGJKefbNCe8NGX ISiEphy9ZC1XFXgOBBdzdXikndfppENnRCcIwubE5ovpF82iVkoibyOZ KGuUaexu4n6iD0W/Sj1E13q3fmsncaES1C1dKlQg4GVeSSteJJg22L2d qIgzd0E8PaszRRlKL+978P3x7CpXl0v88/32L4p7oNfGzBRnyGScke6r +2jMIKJrWOLj+XLDUb4L0FzdaJiX1ty1B3uLDy5PZY37ggzwLerXgGQR Pn23fz19xmnD8mi+9+hZarFGEwCXeId1TkZN8sBe2Fpnx/mYavdzV8n3 OIVe+A==
+; resign=20460416024207
+56r.subtree2. 86400 IN NSEC 57r.subtree2. A RRSIG NSEC
+56r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . pMB3cAyTKGdteiNe1aPMT7fVDy9u45vKPP5vYLzomve7cnwKg+tQsaFi zEm2ANNhPCjHdDWmVah6Iq2Y6U08gB5dGeKWb82l5p7aEMIVTNRHYrGy sCJOYC2ByzY/opPw931B09HA3h6Ir0t2wxCVYWORnZ+CmLeh6AI33v8x /vUxvw4UZTTgDUrIPcfiHnSSs5lhwJxe/xWPtzZpJWx8I6UFwX47L3Xf LY85ddZjetCtu219uMiXIKwtJ4YPm09tVSnoJbGgiDrA+KT+n3npiEuL 761Ijp7HSx4krixhAW4QXbSNQ3C+OFRkgda5lVw1Bsaynvw/rcExT5ZY R+cg0w==
+; resign=20460416024207
+57r.subtree2. 86400 IN A 192.0.2.1
+57r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . mH91kJhf7yPY2QEGu1ypSJU/gbNAB0xTEBDCC9KuW8LC0zs2anMU95vZ j4v4ul5/9AwPIGH9yrB204vUFK3Z9UZMNZxejhORKlV+0/rJa2CFxu6Y rJMmkbrZNvqgIjqFybdpH4ZrMq7WrFlYtZ8rl84pNRvboN0O8DdoSAzy KdneNy4GdpYYsbIaHsByOnVG0XC+2MbpokznzmjHAw5A9BADJL0nT3VP 6wsWEdHbHMuq0qcBWGcSj826XqHqXDgv54YJReG00lA4Kbz3x6j/7I71 fA5pZrSH7y5uU1v4Hgs59sY1hFO6xXMA6JMwUEKxNFvik2W8xoUfmvCY i6UcsQ==
+; resign=20460416024207
+57r.subtree2. 86400 IN NSEC 58r.subtree2. A RRSIG NSEC
+57r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . TMfDYSO6BPtor14f85ZOdKLZ9hMsi2H5HA0x89CqYxUDyKU+fiZeAATB TzibP8ssGduHQaUs9OFkAyW8Jgi7xUu2QnagqYsC2INgYBm9J8hM2zrW J4TC9IhBSTu6i9YNShT3rUozgiHmSJwZNtcBeNOXeXhJBSsItpDHztQj dAjEW806vgusYpK6//s+lgAIMQPsMZfbPk2z2SxBdUA63l71rGjp1fPN XkHkPP/PYCqGh/iwN+FpLRNxcgI4QxfHfQSrto9U7sMgD7ltLhuKhsqo 3nGyRyrxscqapciJkx4huPRZqfr/j/cbCPlAg2TPROiWVuAy3/OAnduF jWvrEA==
+; resign=20460416024207
+58r.subtree2. 86400 IN A 192.0.2.1
+58r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . lmJT/mzPmeQho/b6Fqr+A1QZYYlEIsblZbrfmeVPzSWjPMHzaA3SXfAm qjXgFaLmQ7TtwOQycPYgisJn5lHefqVTE2X6XjploIGOZvT23m5Vdn1i vTSio7L3Z107E17gVXE+CMGwOjYoCOTJGV7QLKS2glj5nKi+v3nSxTbG uNucWG1bBtzoYB7kAXxwokFoJCSJN4CssAKeIoxuqXggut6DifuLlhjI 38huZRDzOnU/1RA5GwqkhF2gYHU3bVLBByp9qjK66azRaqJK3QRTfWMD rpNri+seVFLSsCWQmIiGa+1urma+6oHLq4HF+Xw4CdxeHR6rEJSNEQV/ PPeCkA==
+; resign=20460416024207
+58r.subtree2. 86400 IN NSEC 59r.subtree2. A RRSIG NSEC
+58r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . aJzQ4RCaukrSPm4TzZfw9VYqlt2qOeJMBhZOdTwz+CkkQwCnXK2wo1Nf TPgyL+dP7pIN8v7Yjo/oL6l3bZaH59u5AIm9W8ycT5IPRXwAMeQJcReb 0wpV1SbBzCJTTDGzLMIZdPXD3s3dHjwbe6rzfbThPiUlVYGiJcmwaU7/ DjCcf+7xlw9R1n6z+wvMpu/FCCWqz6XCWIvrGv9BH2aAH+XdoBJ4zWdU EfrB17VYFydyXBmDyN+jqepiUNxvDzLDPRWXENid0ODq2iUGguVK3avZ O8jM5F6jCYCWnDwo6n9yfMzPWrAqrx0p26Me9a7obj8yLqB8fhKJ0I/k B9b6Dw==
+; resign=20460416024207
+59r.subtree2. 86400 IN A 192.0.2.1
+59r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . I96IVUAMZG2YTYz5RUx9wQdMbxWi1F3j/FAR1Zen3JjJiia2UNf+pUx1 +cR+Tpxzw8gBCB5hzP7CWaHi6DSTAtiiB3G9VbGpXNR8Peg5aZpdTuEv 6qwq2MkOcjq/e8mo9uEb6cFvz5QGWqVLGhw0FYnsInZrVSYNccCMo/5L As9fKgnTOblgzluR1mLj4COnoJrzQd6zsPgq0Y9XXvCjmH8QOqcBNpI6 5rRt2jH9Y5triFXc6ai0r6DJHLEG64ZoRwgX/gnMwk3/GPSR+/OP+VoQ 2uzzopnPFc6wPhMBy0PilNVk8NaNwKL+1Ib4kDokel83lWnRSQ1x6Kz6 XWS+xA==
+; resign=20460416024207
+59r.subtree2. 86400 IN NSEC 5r.subtree2. A RRSIG NSEC
+59r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . OAkAaJYQNOsjiPjZ2OKi0fHojtz7FsSb3kGCtRs54ALIhQGN+wvIP3ec PlxRB7cycfmfCueFuCOVkmIlg31zcHPoJgnFmOJ/tKcjn5WfEFu36wxu e13Zu3zKRAqqkRaghFvfn8IIP0OtxHToUGlDV4Y8BmqXv7roCHowaAkl KNTTjm+x6BGHyHfMwZGA7QS1rHhvqmKOBqIDUdSxJm/t90tRs7t9HHBl +XAMVfl5tub8VY32QVhb/eWXXgZyDeFevbcmAAi/bBXi/5AeLsUwjGfA QQBnw3MC99Te6XBoTxUgIgFzYZ/UdKKz+OVPrhV9OWHD5fTeaYRWl0Wy PFFk8w==
+; resign=20460416024207
+5r.subtree2. 86400 IN A 192.0.2.1
+5r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . egV6Vshc7lVwL6tKACT8y0f0N+Z4kTiImRmwJFddB+ykfYfR6Q6Lbec9 DkaFz4RZEaMekxZw7VSID4/UAojGDiOjJfMqZ9nPIYjDEcFLE1k9Shmm ZuIxyed1VpUM2dh1v+62dX/6Wg99Sx06ZUcePA90+fTKyWK9lGCEluCg CMe/7GaPsUkxne561YGoS6rlKRnlhOqsh+shzLYhr9Udk4sMHvdf0G3H S3UVitbkfTHj7XmV5h/zF2Eaxh2512jyNBPduNFdyyVDnkQ6gmyzq8Q2 br1TslWD0u6s8rVL/sojaVqNbnCGxpt1GWbN3EPRyV7BUdad+zkUYqPp 8eD3mQ==
+; resign=20460416024207
+5r.subtree2. 86400 IN NSEC 60r.subtree2. A RRSIG NSEC
+5r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . JJ2BbHVqRR5P15+oc6/RgTEjYkaTj3mjETcBuMqI6ivYQa7ygDxyBayU S+a7CiYPrX+J/GH24GDShf+WmvT9HJoFNj7tHrJrP7jpWqkzU/v8Z9Y7 NgKLENUDc9IeSk+1Zs9Bhsce/MPIiTCYZrOX/kbUqKeXZqlZVjdcC7r9 aPdOGzmXy2PsWMGh30N16nJJ1ZWzpC9ln7onxGzf3XTphncnkWsccAVq yt4U3RWVqnq1Xey37h40E/aADjleyDI1S33UO0UR9DKR0kt3N+I+FBUq SoTK9bomhqrnEggo1jDQk7EP0uk9ZOP/C4hEdB9eBzXCPCGs8Iao/2vi eBA/Fg==
+; resign=20460416024207
+60r.subtree2. 86400 IN A 192.0.2.1
+60r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ZIoajgpZHkY1FbYMvAGK2dq0XRHaR6/PZuBM6Gz6gpF/1HxlcawMSdXx eyOf2+4v93XdalmCVWx0EFu9nW2o2cHymCyHmDuj56Sl8N5yC+a8JFoL US7CfweDgjHFs+4tc8V4IPo4Nwm4CjNI6D6ysfgIu6VI4rbul02YEFRT zdppfu5xxUuPunD4TtditBKA63+TVRU4W3I4ssPDgjBdFyPBqf31z5v0 itqVYFGpS9Rk/DEIqqQz+Gm+8sbvgGetWMbNUrVY4YS75cjdjqPu6B8v jVyAj52DtRIUHCYPKyC4MEKP8ZzCd1+2ke1yYdWQ9C35JV16SqkjLR8i QumDJQ==
+; resign=20460416024207
+60r.subtree2. 86400 IN NSEC 61r.subtree2. A RRSIG NSEC
+60r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . cefA/9TxZqQrYqdigonYLptj6AoACehR0l7LrglA/R+SYCDFIsEyRPfD igNgfaMetvxhfSK++eJGu3BAEgxOrSBMbgGkbNL7Pkebm4kF3YuRMcWi /aRTHpNNWuFwosI4gACavC7F/GnzhVk5JGRPW0kJ1sx6VJVejsIjnq1+ frCadVNUseK81rXuuVKfiT9X7Ol4Kplc/4soFLdB8HHAEWndmb2Y4INP w/FjXbvIDohkH7FlQc6VDCATHMki1IHIzt++wwa3J1K4A9eQxnzBXlQm GSZh2rGXXxSxk6ND5rXRQCNUoRPtiJd+1N39eo4xjzNfaVo7uqv3NTKx DWYPuQ==
+; resign=20460416024207
+61r.subtree2. 86400 IN A 192.0.2.1
+61r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . bHuRkXOHEvh4eGGHdXXzPYKjnLUG8eDGua0cGq7KU36z7GuaC2BKlSfM d6fRgrYFVA7p8nV+cGWzv6qFEc0wofoLhmaqWx5FsuxJZhPHfhERQ3zr PRqjeUrrpREHzEhb0NXF0jO2FKhtjILms1BRZJxtxhfffzB4tkl6yyGH MUVYAD3WaQO4Xt6NFR8M3J7VnTZn1CSk98grL6NvkFBJNifw8FEZaexw QSkApB1IZC0ZPl3mZNHQWhE3OKPdhrj1XT+OdBrKk8ogJUcRlw9YxbwR BhxB7r0m1LNxrjMSaL6AM89lsW6lVo1WUP0a2sldZA9QyDA5pK+qAkpw GqU3lQ==
+; resign=20460416024207
+61r.subtree2. 86400 IN NSEC 62r.subtree2. A RRSIG NSEC
+61r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . mnnAcqIn6fCsxbB7ZvqqNr59KzVVW+CA5FW3ZDZYCW94EZGw/2lUmQOv pgQk/XGkGWHWUvADK4yrSBo9AyklTfZMErsatVVgAkireKIYgigc/kvr MpMgYfu97LJgKJMJV9DQPKWW6E5/y6I8PL2KwnrTq7lCxPe2/SUe9ZIW pqyibjL+HFzLKLWlNm1FLEk+7gMTGWWYu+AmUQmBeTeKJeEizQ10yWdJ zdMMGjIRZv6eJy+g2K3pTuacpAZRECLQi1uqGvDhFi+Q9HVUDRLnkq0M wX//4OwUoUQR704UgyBqd5+88piLo4vk/V+O7ZgjN8eGs/YBWOYZRd8C tQXA/w==
+; resign=20460416024207
+62r.subtree2. 86400 IN A 192.0.2.1
+62r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . k/Oal0kT94HTQvOgFAcIgRt32IUiRqq0fxlHd8IWbx3f5IB5I/LM9Q9S eeIFFb4R4c2lhSTndjPmz0a/34bQkboNSdfRixHRLygN1ZRobTb8KOWH MZ1CKyq8KKZ0qWU5YfTq+ZrYRkXrcK4Z5V+yKyCppxi40vObZivcaN8r TRIYEBC0Ta6EuhHINZMvfGgxuls9K0tHdUUn90vPce5DQxVVp0CbPD5C 5Typj77qova2FQlJEw42C6D9TcFDHTeG7/3X99LOkij2CKqpbdmjqsCU 1RWXsmjTu6P/fdZLqMs34UgwQvEIuMfQcon7NyyydEj9vV5IX8hCTVw7 Oa/PMw==
+; resign=20460416024207
+62r.subtree2. 86400 IN NSEC 63r.subtree2. A RRSIG NSEC
+62r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . hZwtS+mGS0gICbQES7yt48A8h1eSbGTtfz3vj0HYUnnT732vPSjYjAqI FbKNlOdaP2thAhEE9twezzwaTJioYx1NJnBHuELBOFQsjWCQwt1hS/df SwAXkBk3Hfmy4oN4hr7UraXNQjGtrFoPUJjPypD2dfrF4CBiELY0joWp yTZM+aEkDRaotwgeIgEF3df5g46liP2V+GKIfdXpb5PuadG6XlAKyktv hi2jCZ2SSmrcirtjV4u2nZSLycRClgOj5MbXNVxKgMKvtfcVbsaaODcJ Y7pWKIkf0bUivmCy0nkY+ZeT0mnIp11PyaZoIYPSmCDSMWFh5mbibJV6 WKlviw==
+; resign=20460416024207
+63r.subtree2. 86400 IN A 192.0.2.1
+63r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . plZHoXjk91BPxauhgzc90PTZcJHKeIkZwB5PM2DcKDv6nnArJ29oMqek TExb13XrVtx44Ss/jHs9PdSlWIDy8NgLxfOnL2xYTj3/FRJ+/QAbCisD xINtFoFD4ozO+TJrbVAJ5lL1gTGFS/Gu/PBG0iN6TNkjCQ0tcjA7qAMp OVreYmVZSzbSE1W/oGQ50KnEm7ARPr/p0HqFxZNnV18MXbAkhcLA0tmt zush+sXq+d7RbhB4wXtrxbKntYH/WV/d7iIwprRoqE9LXGjZZMlN8hs4 CvgTdiPkP1cs0Aoucyv2SWYsS8HAwJx2pxhgFchOdoWwIeOch/ZHypL1 gULPqA==
+; resign=20460416024207
+63r.subtree2. 86400 IN NSEC 64r.subtree2. A RRSIG NSEC
+63r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . IlmuIR96rM8hkRBdmhMCTpVOkLz4Q6BAmlmWeY6AkFFnrfry4HjrpYXN nj+62rKC+l+micWTtCPzHVm3fshRKUG4klDtcMeNN2WJcMhApYoLCa1S HhmDut/Py7iGrkSYJbkJtUfVH5PobpcWjLvNBueBSped3RPFQcp+YHAF 1OgH4AKJNc/U8lO1xAHBp17IZsCOYzoUCpVH1VPQFA9eH821gBhIyScA l8tKLcb//OutsLlsG4cbC4unldVNj2EWllh4hz+KYXa038NsUhs3FoAk 626GPzzqE68+JCTl6kKvUAaz4OSSE7LLGEY9IUn4QvFFw8OeGV+luQ7f fvip5A==
+; resign=20460416024207
+64r.subtree2. 86400 IN A 192.0.2.1
+64r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . Z5DJ50ICaKG42eq93NThIuvUx6DmnQKuxxv8q9RxlNjqmoP+T9Bt15t8 QQHJFOacyxkhoPgsqIHFHyoB73Fl/9txgkyCTAeSTqB1YzoPzeZoMhrr PbLvCu0Z1Vj8r0Zytkz2V/QHxROfUV5gZzXqcgl18UNgb9WB0E82HBcy M1n6Z2ruawDuK6eBMf4K7t+ZsbdhCcOMx6D9foOzudWXR4Mn6FgNliQ2 JzhpY9t4X8rGU4HiRJuKDo6Rhk2WNLbyLDC2Jisb/FaK4q9gsao7bxYx VhYpvXNxaJeBDCa+WrnZ0QiPlKKWvPS3HYTyhX4KFl3gkWAumRelvNZA wWLfUw==
+; resign=20460416024207
+64r.subtree2. 86400 IN NSEC 65r.subtree2. A RRSIG NSEC
+64r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ZfAJzw5oh8Z3bbkLC8yfBHa02Fd9T2xEnbejczgGFT/rmOqSDtXifrfh HG+/8vRjhCD5IFIp4RpRQ79GY4vkyQnlFfxPFhaWe3zvmiVwWELutyKN yTNoxeiIeWjdLAMe6pSTcnhlB0vADYTN6edEoTZuQZHsJX+VuiU4nbgf SvPFocHH2KNiZx+Bk5+MmL/ProrfurhRKxD9mQQ4+2NZxwmgtJQQS2hK hKXivLssOkNS23NmPAtiID/iZQpBM7RtYjMm7CgCFigubIRuB8MYav1G cxLw3KqbegoDAStgCXsuvXQ4uQOtHxs4gI32N8GVaxPOUh/STxaMYsdz 0sA50w==
+; resign=20460416024207
+65r.subtree2. 86400 IN A 192.0.2.1
+65r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . JsGyJzO9z96GwXyROW5ZuSHeAfgqPWonnJU7Fm+4kOagLkGHeliGc8R0 UbN+kERWF1uC7zyQr357oyY8tVcTXCsnFQ9lEJcPQz1SqcCD2AtVQBb4 MRMHDa51mpMUJNdzpGBKbgsuOoQGQm8/xmzQncu7IicGl66yuamENzZF vIcsZMrom5pEnqI0FICjvSUoKNdMvVZ4NIbSuX7PR4ttt3eJ5ZhSrRU3 24h/sFh3ThNT5LcGnAF34UO9+Kd5YN+n65koVkBbfkkiN2M6JRczWZTa 5ldsygCg67x9ac1+5FZ3PrCjkOXBzY5GihT4ZW08+WX9uBF8MEzdSJSY lnjD3g==
+; resign=20460416024207
+65r.subtree2. 86400 IN NSEC 66r.subtree2. A RRSIG NSEC
+65r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . FUXNUkNiHFjR2SghJr+q7OniASnAoQ7XQP641A+PDXJY85ehtQb1B8N2 ukt5TPp1K2YHaYfa5aLOHSyUVTVGaR0p+QPODoiwWtOnwIO6mA/W2CYQ lN+eRBFQaUzHnjrUqEUb1sn9ZHCN6acQKKCAeG0dSaJECzVtndDcAb36 JvAydLEnu9fPgLyDlbTsBJG9sg+Yssfqa57dmTc8UWGMTOlZbQMuehnl 3pgWPxxgOLwnNu/OBNV95OJaxR1QEK5GH8ZLy5A21w/V8guIrwop7Zxz j22Qfhno3KqUU8DwKmZOOVuyz8NZU6dJk21xR9LOGiR+hRBaFvvZd/m5 gGfowA==
+; resign=20460416024207
+66r.subtree2. 86400 IN A 192.0.2.1
+66r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . dH+wUC3mViU6hwRUsvRCawDXwb1qwxmsW3pTVi37882NXx5P05OehWdU chwiVN9tpOg3hibUiVa/xw2FwfO5gNVVinTAy53zbfmw3CUDx/njvMMr WaslVmRj6GLsZDaFZJW7lZHi6RKlzrDeKF9YiWPivbe5k5JqmWmEamVs YlxZCijW8TkCH6CX2x5xgqngLG4J9KsbHOasVj/MDWr5dV2HSAdsXoY5 oz12xzKAVdPBXD41Y3CfPW5Oje+G8KGUfcXDdit9Cofh0DnYS4WsGOCO ZwgTRZZ43JwkY2hk//N7ziNtcsefKV2NRbu9TtOAaUxg4AbOW2qS5YIB foLswg==
+; resign=20460416024207
+66r.subtree2. 86400 IN NSEC 67r.subtree2. A RRSIG NSEC
+66r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . FHLMEHAlqjdwmV2LFNE8V1zIQYPHTnMYsJyBBDg/x2V9USEK4wU61Hax dGXkgu3VoBhakpo959qpjr0tt9kGxCrifPbzpaE1UlABFyX3CaiPXQlO 3jIKgsnPUncvQXMgXV+Iz3T17PIVdv8HwYqeyc0aedH2wHXMkKJngbuI Tndi4n/flkoRsbK0F4U/DDtu9UK3EsxOz90yaELJryj5sxiIeDX6l5Z0 IJSjZLafZuhU5nunGdzmL/9GcKGT4sAN2PrYuK6S3Ipoq/QKSYPQNise ZTxcP58dTMX7qy+ylF8Lr8nxdJch2CuakEkudQsiVX0SEeuqwU8O8nkC M71ipA==
+; resign=20460416024207
+67r.subtree2. 86400 IN A 192.0.2.1
+67r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . dw6mT5uSGLyaGRn6Oks18wYldst+JVfz9T8/d0f8jSz84d18Wy1POhQ6 +dVL24qYWh/PAzepU29SU7IbWmidOk6/zPXQnxub04/tIwDbUyNFEU56 IwnCTS98Uvbfkceb8+bnZvJWezuNYEg1kzUWmxxZ5Ye6sUPiNutTRIkn 0kSfUZbjpymAs10qocTnBmKduktn27FbQyVMcJuQF19DK52VVNrQ88OC WV0cB6T1XAyzElVzzpHdtNR0H83EZlZ4xDjeJ+RsOMpAoEJvd7AKMnSr Cqar59tIBHyH/5tV+3IFdrs8HI86OS8WcTrqJZFbaGjnDwCHKAOOq72c kXATww==
+; resign=20460416024207
+67r.subtree2. 86400 IN NSEC 68r.subtree2. A RRSIG NSEC
+67r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . kMqkEJCyfCgq43uNAsv6evzEdAcNt9PTSRklcVm60lDRnyu9+bSfeGVy x0SKa5Wn+agDBxGX997L6kUh8748VFojBIVso+maRkXEONXwWBLdgWCm avRPmTL43nOX6xIKvW7U5fftK+mAFooqIdYbuuWSnfbuYQS+uyrTwmp3 CfHeFryjYtDwzJOovOZuYGyU/Ca3Gx6+GvQn8CHxERlXAqdw0JcKQM6M tRg3FRt/LvqnHQwS5KUncAtGZPriM0xGMvi47ofkHqAwKc4mzsbJbFtM 2Fwemo6x3RQedMkBj1d/YbPxvtxEyfJfl4lug5PrvIMRGxUjUbKW5nRd KsKwxQ==
+; resign=20460416024207
+68r.subtree2. 86400 IN A 192.0.2.1
+68r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . qcyNaFw7+PzaNI/3KRPc6SMT628TgH/hsC17lgRaCO1SbqC3y3WRDHu6 ZrH4CcK9EzG63tU89Vg5N8USl+V5wVYnrqWveZXTG6Kr78bSW9HKXoW1 Jjeb9BVgQtTaRKt9MlpuLVLr2TEHD8irJFGR4GQmsfpk4R+LEssjp/8Y 8ofOuWiUECvajXZnct0UkYrxHTFVhtxvDtaFA8+gzVHU5s7dGfdY52pO sEN7z8Dmgrb8V6JuYi0fVWRcFaMxSLiuIiy5+T6PwLFS6lyRVaicV/qb Fd+ZAspcRvOjFZMr2tagang6sU3LJhCO+QC8Ojj345QFFEMtv3dt91wJ 5m5tIQ==
+; resign=20460416024207
+68r.subtree2. 86400 IN NSEC 69r.subtree2. A RRSIG NSEC
+68r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Px6K2z0p8+vmVihpeEomljipxs082D62eaX6iZ1UKWN301wf3C/OKJJW oURhL4BPoJzOGsjpo+//c+88b97uNAzmoFyHJAbc30tfkOHpgnR8Wsmb qfdkGXDiQMP4RVtTWwu1+PaUI+aHxiG9Ax4ldKi0ONTNzJXrqaYrgPJC EgYamDXS/kzx5C7vWTqUA9mE4zCW3R0MPm2P6pdU4AE6olcMAe56eKzo Z0TzF0l5VpXA1zSBKXuAdw5gNcAiNKA8oktpc/52xLbj2gB1pdJZvrSf /RqEdthnElT4HSwqgDH7Rp14fmzzpeEZ7SWpaWXUgk9LDV1MiEkt0TmA pWfezw==
+; resign=20460416024207
+31r.subtree2. 86400 IN A 192.0.2.1
+31r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . xASfmo7eeAQlDcnqUC2871BhPrLZo1RttsCBprJuqyzqI/U7IUwOLubB 9GUQBFC2h336R7goYUOvfkxItQdrKMHhtsE/5W/fkLIKfGQgKcWZ+o5M 0z94Oa+0dll+1B072+6qLTPJquFUGQcFTpc3To47tNvGCZrXw7rgIDv/ vpYPi8Di/djm/5nLKKgJjg9U42GnEG9sUyzGB4GOYlgixCAa/rGKKIpm AFYtLLBR4mjVzNywJ6EYVcAS9idC8hN1Dta4oni0t+3KMT5enk0ISmou xL8RqBQW01jc7WulRZEtMeoxedS4Dpnne4uiMkLxwE9KzNRgRewHMe9K MrzPZQ==
+; resign=20460416024207
+31r.subtree2. 86400 IN NSEC 32r.subtree2. A RRSIG NSEC
+31r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . f+5sionbddem8iXvDtoIt7I9805BerL/9diWmg2iqzoSgPFyhhtfXDps ++M+Y0iRvP6LRkF+zAA5OIOy+pSJbhj74ILZC3tvnnQuRk4jfT2mNqmt HmZJ5RcLCH17ifYj0bzfyp1+fUQ2yk1FEhT+zbOoVmNFyaAxzVOD8TpY kbW85FL4+Bic8w5o+j96Yf7sxqe2TLddzmEinKqE4JHNEEWdCfhZdq/7 nIm5b3pkRV+spJyRLMwRn5GVLQuMtJijljX3qzzgdCVXgs0WQAALoOzL AC6837pes52KTWOIoH3zKLKiiluZR9zBQLXMHacVapN9uUgmBWN6z+YB lMAMcg==
+; resign=20460416024207
+6r.subtree2. 86400 IN A 192.0.2.1
+6r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . DaE9dYZ9HhGJv/Sfw87tTCmdnWqfug3U/JEj/Q/7impFY6OPX3OYbwxu ga90xmO3xws75gGLMLUmlZSUWZeawsLQBmwlxSAD1GCsRgFEu/7GlCwD 23mHWYuHWtZ+8WyQrSKHmwpyjqaaFixIl+D0jzZ5sZYe2Wxpy3/IDDeo uWv3Du2W4ItR3PF5TV11sJTdta7Tl+5uS2iBxl5ohwtxM0t7Hk8tKr8x Gkfp4keCSNhs6j3KYh3Pjl5rObq4kt20icrm35nHyH6hElynADB+ZOxN Ei1XP1mj/WlH0GRejURI5/CmmdRT5oXYDY5R7k9Vwf0jz7+FJ5YxAdQm 75MQKw==
+; resign=20460416024207
+6r.subtree2. 86400 IN NSEC 70r.subtree2. A RRSIG NSEC
+6r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . L8pFJzoT7VeRfRgptXm62L1G7SGV0Kqrg6Ajx8j7HyN8EJia8hra/Xxe IqetVbiP6i65IdTciTeeKTcFQXJ+ZFYM1YmGFx9/7z5Y3Nu9KYHaNaBy nDJIuHrQAokLeqGetQmsZJWpLcTzNJjFMvXJ3gPxFpR9/NMi/Jd+XhXr xsVPiS5hlBw0lLtQ21kDWE84uthPXx4kFMbbnw337Cl5js2+tkSKV0bV NwySaZrR/cJdXvTY/xUlYm0MltX5j1BWL2x/2k44VUhcVxi6IJu6Byu1 AjD6kJaCtV6vGEbqmTk1GIfmrSsUgRhEt4mxc6jaP14ojBNI/Q1lcRRt ZkpvCg==
+; resign=20460416024207
+44r.subtree2. 86400 IN A 192.0.2.1
+44r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . qZMpq6KZ0gtpfR4vLChBPhzwwCJSCGNXkxKCrfdY/3ntM13NmxTL3pZd Eb2zyTPtqiE7EntPAXkRzr/Pg1+sFxSiPuF5rJhsh5AdT609Th3s0hf/ HFq0YfgoqWdfugIpox9HS85PfaHBouBGoV1kS9Mamch2D66CGMKCi/nE BlS/hnjaKeLHw+mKGuXLgUvwwZ7fCdVo/Rpq2UL/eNkN1wkslKU7LvgE sRRQMehCS6pjksYMSZBDm9nsjSuS9s3Sqn4XE47K4rS21Q1FHoiX7YHf lkAv3LS9Isip5HNFWw2QHXPNVNB6z7WUsdD+EHoV7lQjun0V290dC8JW nxDgsw==
+; resign=20460416024207
+44r.subtree2. 86400 IN NSEC 45r.subtree2. A RRSIG NSEC
+44r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . AztM2Ln9mLK2VNSlJbNxd2YUb9JzTDCj4uvJf9nf5HygpuJWd6tRvanr 9n0NAWeAP8ZIXLCerxYEBhtzDkz+WMinWE/vq4T4I9+9XtRXeqmKiNBe DAM8e4hMkZpZNb5CiLBHohm+rzDSg8wWKOxFB3bEgdomYtF0ajlkxKEf LIr5hccs04V2MT7IH04+mIw1I/a9PpOEz508Pn17KfKPyRxdCfirL/wk otbvIR2R0sLxgmN8FcooLQe5Afr7kviQDCHhTP/JCqxgRX/mZmXLkpUB 0T23BsrD+DclKwNEsMIX2wY9NXSHv0VKyn4/gMs9g/F3Kv25wOznk36E lGooww==
+; resign=20460416024207
+71r.subtree2. 86400 IN A 192.0.2.1
+71r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . xhl5CzYNam2ntB7v+jVddKUM3mgmZiuLR8M0cKHFt47F1bN/geVwDBXj F8VZisvz5YR0QeaG/EZGAW5wQYgCXbkNosRKnnamb4HSLlLn1vNkhJhk 8W+ZprTET/8mOimRW1K/VDPMSaRk2pObPGHt//wvvCp51xc60euq8zO0 olDmOrVnEV4LNTYS0IqQ/Zb4FKfcj2217laks4y8jpIA2RqsaMI9w+0O X3Umpw9KEKjWRz10UEW4gqv9uEm5E78e4ymClA/94y1tybxN6mymhrRv kMmDXpgTmVVRCU3UoqGTzNShXuWOY6j1GdZSav/ovIHrFmma+ijx0qnR GJHzKQ==
+; resign=20460416024207
+71r.subtree2. 86400 IN NSEC 72r.subtree2. A RRSIG NSEC
+71r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ZdQ6w2ryMuWp7iTgw2JNo4ZCCVW0tYCNz90B7wJzU1Ncr/rU13TmS7Up b44C8q89ExonsWOhUkcokaSFbrt45R41O6k3X3Ic9sQhwvyj2vpD2os6 w3rBiI9EEiGW1DdVEl7CmiRcWlzMeoAq1NyphkhUQCUytU7FGVM7I+ex GTTUNUmcB4wkT6z1T0mJXkmHk4nLfiEfe0aPLu3QS+UxLl4SriymzVms yYXn57+KFnpTS6ElCYIOPhJOAfSxEbipg5rPNLjG1nc3V7XuuE8PvvZX xTObbcBmhdK5U2OGc6G6RuKgQ2po2IGSA20O1bEbT1k2fbciKBuGLkZZ /iSC9A==
+; resign=20460416024207
+72r.subtree2. 86400 IN A 192.0.2.1
+72r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . weoG03ur8jEy+A8T8jx4Z3gqrrTc6TEd+uoqmL1rRC2pYBrnz29T/Q7X PL9XxXXpFIhoRdqnaLfaNaqZ8lGP5iSpEzWyvRSDtMcFtzKMzBcLRll5 IzjuiRHAGF1D2rPOR1oxJ8UbhlthyNGO3nXqc5JZ+tIFmf2Uda0KWtB5 rR/Qog5l5EgSxSgEshzN70D8sZFbS2IuelXjXW2aLR3eMIaH2zxuqkxP iIVuTh9CobtBzPewz7ZW9zImW6W6CgI8u7AkHHwfJkxPVUVAIZSUuux3 wdkjxDx3c/a/7OqjKBnlOPu0JNSpRLiKRQUKrjs2YyUPyWQC72XiG4e0 YZpYtg==
+; resign=20460416024207
+72r.subtree2. 86400 IN NSEC 73r.subtree2. A RRSIG NSEC
+72r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . sZWRAR4iaXT01LPWoixa3v/X02/ABRcP74eqs/4Pd9RiQdPYJPZxQR54 fA3pTBlaiG9r/Q7tkO7aHW4Ll+Ei08Ry/A9l/+JrJKJePdANItCsLoTu juGr1UCLKS5uTAef//sh5tY0o7tZIh8xqlH304ypIiAFPsXEATZu8opG a03DA78/e0hjr8jHar9VZEms6Rd8P4ietnFm4bot54E5toeSLXWVa/tV v69xQD6JmpBeNIf0A9/x8tqnnG0v3cEvbka74xWNwXQ53NbNbV39Lt6q Uwx9I3/2qlaDCwpxQMRosI4emPnF+xQSQRoPvIwU+2o0uo6kqMtzj4mG nL07zw==
+; resign=20460416024207
+73r.subtree2. 86400 IN A 192.0.2.1
+73r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ilUC40xFRAoBBb75GrehoJ9wqHjkwURrXN1RcqhoDE2/cVQPvvryXdK7 CUHTgTFznp14vLyqp055fNZgpDcXZJZ3ZhteIe/OZITv4p8yP1uxfjQ/ OCcP/0E8Qv3QF2p4BW8VeZ7ICjpB1LPtHpjsaVHex4KCYiVCpTPR0D9M 9/1TuerGWnS4AdOJA/MA7ZVxoulfTehHZ8Wkn8lhu0yMSU/UvkYDhDK6 jnYARY88vA+bw5FY4MOdK216/JWFZ064+qHnXw7BjxyTMWc20fCu0Y5a z9vo0mE46SHJ0zkS2IL4Ou8hzqDxf6O8UIGXASZh4gnZcUFM8WaxHIMj cznDJw==
+; resign=20460416024207
+73r.subtree2. 86400 IN NSEC 74r.subtree2. A RRSIG NSEC
+73r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . SKF5jm8SM52pUZ3i/U3U4ms8jLMWBbBLBAagxGcsFWZd5LGj0tRMO8P+ ZE7R9YSSgfYsNq4r9mJHqd1xwqzVin28OMuEw5USEfT8rSVm2sWnvk3a oDleBSjcL1V04JU0Rd/5fs2gwASqsAXZoP0ddzR8Fw+nKOw4wK4ah7D2 V/8hBzFoO8MTieiCE4iXB1Vr3+VZvduJdyoM1N1357axaQIIDg0G7PbA KUaiySfvkXNEHq8YNI6vNnVV/JUzZkKrjAsxyBtzHu7QiyO2q9y39qd3 l3BAyB4WZh2fsAo9KTqPLGoExS5JaPZ9LpHfzDlG7V+QxzbBRBLELr+Q Ipcnaw==
+; resign=20460416024207
+74r.subtree2. 86400 IN A 192.0.2.1
+74r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . hzCzNeHbVBD1gmpdxG1P2nVGK3YyoALpaoQ+az6+JRQBbal2E6cgwpgD Mw4jMTnBryhktpViSxcSjl8FXHovonJJFlQORxVEFAjhW40shnNHU4Ew ofPjNaeNn8Sxkpevncyv+/tRLqHJY9007GmVUyzPkW9nsFtmESNKiWVD 2IBZ3DEX9G++ExvxgC4pPxgeZY7vhj12sKaOuL+k3skcv+HOFBevfB2j dVfJMzQfHyYg+zoANGKnj7Eb0puXGL/lUIKvDES1voXhMMp+1zABItx/ vIRfMGAKd3fY6VO7/9XTq8uLNuSDCoBu+QvtbxB/sh0q9N7VWBhz/wQr jCGl7w==
+; resign=20460416024207
+74r.subtree2. 86400 IN NSEC 75r.subtree2. A RRSIG NSEC
+74r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . pSqVYdU0CflPEDRj0Mv6chdpOyxRdou1R0ltTBGAwH/HhkxUyH9bsVjT H3Pl93bDAQUU67PFMYcOHLzESUBgULyNnTH/ZIQhyanVCWxJdQXhumsQ bwaBjAbVvrVVLI3RwkcU1KOu5o7VCsX2pzJv/hbNiD9ZbSElpVUuKpWT rgr08bKQSSqgFEkWt7nlXBx94ufa2ryIiUd346q+U3FbGRcwW/YFaPsH nYykgmak+GJBWd5BouXIatxaDMXNUYpSY+z6X3fCItVO8JqoktmMeHK/ lXysyW56N5JgKtBqLUB/xIeGXC8kQj/K+YX48ZGCjlF2Wnuo6ZcnQd98 5qfaTQ==
+; resign=20460416024207
+75r.subtree2. 86400 IN A 192.0.2.1
+75r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ungkHYJHjOJnWwKjnx1qYF6FBcwyGqaVUPUiJcwYXb+VT2pQsjcKzAVJ wka4Ur4uAT2JJycfAYgsYhLU8TWXerh2H+KPUDOVjtGBtfcyJ+V3/GAA 56dGGnisckMgJGzOKbDln70wRHVea65RAv3RHQEgnXe0pMtw3PzZiwEZ n1kEAF5HYgV1CkNefcSv2eA91OZxRS/o14Tode6/xvzroa+C10dPqjXM m2mFyvfJyQxBaVxuXGc4BrJOtDd3pgXJIVE9ka9oLczx9NoyfVKRFfHU gCUnSSb+e/XWrJsHnJe9NKO0KunDbmdpGvQ8T3qK5qunboIp1unl795p LyrDxA==
+; resign=20460416024207
+75r.subtree2. 86400 IN NSEC 76r.subtree2. A RRSIG NSEC
+75r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . MQ+mzRMD/whzQsiJFOZnyW7O96lhum/QQ5/7xKJKNGpktBX7jNvgdSzK swAA6tgI7umIV2u1l+tI5wpyn0GzCRlw/MxVv0BY8TQU/befsN4u17tp QZNqhnzSsauhvxpypW5QeG/Nnm8nqMGcNzRDfQ3adDnG0hiYZYdvtBCG 0Ygk7LbuG2Ra04betRCerxKcSs7sCYC5EE3daJWf8bR43hWoJaC9Fa6n NtsCL2bc4Z5UQlzjZgH14V6RdkmhIKsNG0f9tbza19A2nE7YgDr1GvMM F/DUTONHV42taRDfXizCEJDax4h+ZcJ4wO7/DBEtrpSqeja3bJ9DbSA9 3udeCQ==
+; resign=20460416024207
+76r.subtree2. 86400 IN A 192.0.2.1
+76r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ifVnLFh/AL7XQnKzcxulNoe/CZgMEKm2UW8tb7JR89U8gUtuu45HKlOd SWFGE3VGylwEfSlt2VWL0dyAilSUZVcKXRqFRrDOrIjEIFkvdwgaTOGd u2foSS7ZdWGjRoNEo42kQnvis8eZ2CkHh2Oa53SYjD1GfX6Y0C+OCgwk zEyLleAhzUwdJbDpCfgKB8gKjXFjfLYltrHJkHlBStRt8Ec2+iX5oS6z FOccZSd1wb+Ch3EiYVxUAISyxv+riGh7U828SvgndU3B6xhthrdwiv2i W58xKvo0UQ/5hZSO5FFKzeWbruSE3BVYHsNb+uu8Rksa43FelX7R2Vzv vIGz2Q==
+; resign=20460416024207
+76r.subtree2. 86400 IN NSEC 77r.subtree2. A RRSIG NSEC
+76r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ingv1Z39NH7Glaak5WrMae8DZAw5SOqHtz8ugsdTlLWgfNAMYFCkJyAu m1tEPPek3Dn6Mj8UUSscFdUHXZ6ykompfaCMZj0msfw8CIJxQWakzP73 kYzufEJH7+KjiUQf2nE1SKlmeP2Ul1NaYB+bvYUttvXZvr9LTy0iH8Ij YdDrwhc3CslU6cL2S+uoL/qvyiXvLIqj/OAnCjylRzlvkLYWf+ISRnPk dv0f18ATAFt8ZavXCfP5Epyc7+OpToLByGu1yXO8/SqVcy+KvtCqPg+k uST0RpolF1INiI8GdS9eet4DdtekUYheoerQS4yVWV2Zv49gna1hulCO yHx13w==
+; resign=20460416024207
+36r.subtree2. 86400 IN A 192.0.2.1
+36r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . fEpn/LasYKUd8XzHc/ZM7/89uBNlhB8sncsnT8sGohJQQ/JFDNFqiIag rRvs4QQQNcyFa2fe5LHFBderzEybjn38GNMCnQYm7z4RkATEZussmPQA ha8rETZxLa12iZvILktns9Z7wnMwIX9WsfrsK/NLmAL/Z2+2st+Cszb5 CVr0ZfKBfRod3Srl/1FsnA/Ku8M/u1d3t+b5Z9ZY9m+2/VAhoLIyEc7C zK+0asVM36vN+LiOV1TnNQfAcmDnBT8s3+nepf+6j1S5LCNhm8+gfLAR 8iHb4LrdhCeBxfR1cs6pT1DzcwLTCs4PXLdmElsSxuqyiCas3pyXKoGk Kuns8Q==
+; resign=20460416024207
+36r.subtree2. 86400 IN NSEC 37r.subtree2. A RRSIG NSEC
+36r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . r+J2IoJGE1EUUQ/xifkSKsK49QyQifBfeXX8L140XxpRw1PwiaPSc82g stB1h4lgb9UoNomh9ISjEYkKmVKFdNxYmgXZRUOX+3gRvTNIQgX+VNyr xeELj2I9unlYar9JUKvRMgoWi7KuuLeu9YtT91Pd/WgUcFO0ZaVawIbJ 6BrnvHq0cth/ADQIncTm0F7DUSJdjRBlnXqEhE7rWAcYBfiDnUt4/BCP bIVaNKkzHuvzGubzjk1NPuNHKoFwoXNtYtT3z2GOuPmPr7ux0rVtE8Dp x1AmjzxaNGPBrsJhAS+dwREXGHA7QrtmxAojKmw0rJXZ46HPsd/e6BBv JvL7OA==
+; resign=20460416024207
+78r.subtree2. 86400 IN A 192.0.2.1
+78r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . OrFFiWwbBW147VMD+1Azi9/2dDFdww1gAwef4WECpBZx4lB29o2XoSnf jW2BDFz6U9jScXuSUhyAb6E+5dfITo4Qhnpx4PT6cNqyaqhRbSGbQT89 BpBPzFnnNvu0k6R2eGr0RSqZjKalFi7Lvzr7utZiThvh3FqNJoA7C6fd WyOcBnF8HahJYdnigYP5lwfN0aj3/LbiVHmXvqq0l7BUVDRZ6gLUhGMa pr92um8V7mquo4/0ilniteNLFx5/66lu0AqU+71SXB6MFpxjkqgdsX45 a5dcODTv65oLvW5AbAXqRl3TP9MbCgTfEkVVBlDVLmHwNP3LGI7e46pR xa0nbw==
+; resign=20460416024207
+78r.subtree2. 86400 IN NSEC 79r.subtree2. A RRSIG NSEC
+78r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . va/PGpGKLsXut42f7ZlV+nRB1xkwno589u+AmiKY5Du9CYP/G/M29GUn TnY2MGdgcu0GifwTQb0x7EyqBdkdm///9zfPvt1iI1mc2V+1CbiZoR3+ Jpft0+FKTDffHBkCnSeebEbEblvLPKnGr5nZ1ZMTZV044gYL5LhUzLvW AwSyZix+dVAYJkIS/suBWjuez3q+DbNdnayKR8rnz4nXSD0hKkvB8dT2 9/MrJGHxoiE944u4U47lUxAuEim1ab/qgKGZ0mc0hGCQQDvM6/QufbX8 YEh9BEDVui8puMru2UqST+ADGM0QcfRXmF6rNmlLjCoBXMi/RXLOcYaP KLSVUQ==
+; resign=20460416024207
+79r.subtree2. 86400 IN A 192.0.2.1
+79r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . JDBZnOKR5vFA9Zj6h9wIKhM5/cmOAcHDhYeG2upJGRhuz2grB3OZAaaH fp395ebYHAatSV/Fyov1y1EhShVeIcnuhUqm70CMgXZu3cjBn5n5pvjd 6dKxMbLMiAmngcX6497JYCMisSBqoyEzqq6jfe5Wsz8GSFEoSTVXpM4W JPg2Qy80k4okw6IAlHHxap4wVZPgcTZ7nT1DDwCB3nSY4oUXG26UYbRy vM6feasqo217jAtz2SXiH5Z/XroSGdb/V81QN+g91uZeUI9nl879lYE2 aqGrSV8oC6C3Ww5HpPXk3iBWR/ngBbsPEgMI2nGWOKsyu0kf5lmrSvqv zdJbsA==
+; resign=20460416024207
+79r.subtree2. 86400 IN NSEC 7r.subtree2. A RRSIG NSEC
+79r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . XAPiDw+8tcohl5AAzbSvQxLi3IiLkmAOjlDrbuGEtdCBo5+JCUSSQP/l tHF+skvmVdk1j1h0G08i4J//FwkCNlUfhYxfeX3132hv3Q3Nv9iHTK8U Hrp86DG5cKG3Z8H1j0cQCXLaE0qk2FzCgt0H6MCnl9ClTWtWQyEjCCZA 2bZ6HKLHxAUMUMnhAVoH9QoBjJoDi4N1deYz3FDbTAN/IH0K1vSgmGoP ztbNyz3WI/3KuJ4AIyISfk8nS/RRYL/WRE6w/WnxCH0/Mu3xDup3RYgX 9EQZuJQAhw+IcN7xgcxfb7kbTXym5xrzp21WUGZL5XobXU6mVGmsxYnL EutNVA==
+; resign=20460416024207
+7r.subtree2. 86400 IN A 192.0.2.1
+7r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . gvDi/djf1jvbm4oPF4vZvf6mOVOC1bnhH9rLA7s7Zyr+m6iq9DN1cUVv 6aHZ1U8tqYVkZTVu1Pt4iIAZ3x719yKSpe3FY5yBMjNWKqxXXO2P21tz X9Fiwb38WDsSgICZ3VHt33BFOhAXQYFTIZ/7pwsnZupnQgzagSqpos+K s4MsG44CbVRmUvVEoBYUe9TuYV+vJRLQcI80UTUxAIlki13rw18ZoWb+ CEBPoxV2D2Al9vFeq28Cdopi3hsN1D+0+imdx7Gz5kKtib1gnkb+xciZ TIVoIBnhDnOWm/y/rfuvXbq2G3JSGIPl5o73KMq+F/Q7hF/WQSnfnzXS KcP+Mw==
+; resign=20460416024207
+7r.subtree2. 86400 IN NSEC 80r.subtree2. A RRSIG NSEC
+7r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . ve8kD8ZLjqZM0Ke1iTOLQ1GSrNPCXcfRwD1dkZjXYT/SXV8fLVeiyBXN fi1hqjlUD+Mhi5elUkduTKzfcrLPQ2BnDQexSc3wuFgGSZtir/vfODDN FrfKdo7c5alC8vIkHz7IEVpd5I0aiSSgrBIC8ooWNRod/sRIBV8Fc774 o9b/Ef0KbvI40aIRANVNihrT0aPhA2/RHtZLbTUUbWIRXOp9+yWqJYu6 z7ox44cEfHWGkSNRb2IPRm+l8QN2HMA949b7FlxY2Pn5i6mTaWD6Kil7 RotIOwm1fkR7Jgsii6Ra8OBGpAo4Pg0Fkx76wTZh/DF+CoqcPjoA0w3K 9bc24Q==
+; resign=20460416024207
+80r.subtree2. 86400 IN A 192.0.2.1
+80r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . biJy4joHJcSotexQhJB7Mb7u5fevXebKBeDlrMsj2d7VdBRzSExwmO9i c+yM5Ou1P2i/3EkBKT3mCwRT7OjdHrwObXizdRutel87JwXDkk1xlti9 wTp4OJBEQqBPbx8wuY/ykmU40qkhY0vtluldOtTU5ytTr5gBRR00vEws 0FhhSuW5Mlsr5DeX6o8MDHaEcQm4s1vLsmI9XV+kDLuwuSHSuhTELJA3 v02uGCJswKj+r/LZ5m43sTKm9f2/IimBjhpdDqq9yv+9910aUUaW3hft xqWkWkPzKfpwmRhyu0K9VhQjgdvHwHe2jJ4UHt9Pja1dblLZm/qtLzz5 mTd9+Q==
+; resign=20460416024207
+80r.subtree2. 86400 IN NSEC 81r.subtree2. A RRSIG NSEC
+80r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . inmrH5256TzB5VtNY3N+E9K2wmIWbW+9fNaR7wXgTzSQD067JHMgUqLz 5d/QY9IjZFjlRMtv4Cup4tZ9KEGcg84l2pXNUcDr7LX31Ak0RsVkKvNx FVFY5tXMMbW2XWNbZZDsnLHp6VOQD16lcO8/gYb0tkwRGm8fPpMJ4Njk S4lWiAQNUj4RAhmAJkMSaZWTSjI4GXfC8k4oOynt0pGB2P65ASbU3ZnH 6cZFEH+lbDIrS184PBuvtucjMdYXw1q0p5giKKoxiIhJ4lwGiA7JlY5z 0UfS77lpnqv2lECMrOtXZDMirgLrPHMP9eVUP2quVkVlQCjW3ixJWgR+ eZvdwA==
+; resign=20460416024207
+81r.subtree2. 86400 IN A 192.0.2.1
+81r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . eZ4kaFChRslI+Biw9JbT75zlO/1sxWMtjNZl+fbV6lK6pl4PtoqqejnL ig4m0aQscpUMPzgIHO6kRhAeUmOBeEymvYquQyWk95X9XmUjWHyvUanT PFrpX9fPuPCKpWDRvodO3G9Y/77rexa9QYi2ocNjpHmlKtSO0zqmzoc6 nKice7Vpt/Caw9d3LNR6pUelveEtGNPgUgWMO6lWkx/uFTg1KPoHbCa1 KmVOPpAywEH4hKKH2vhvaocwxs3VfOV+x3/L/aGVQvPbB3vlsNDxkP53 tVMwq432ypK3effOvHvmiXobMErRqrz2fxLMB2fluwFXGn5TDAEtV5X9 3VO9jQ==
+; resign=20460416024207
+81r.subtree2. 86400 IN NSEC 82r.subtree2. A RRSIG NSEC
+81r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Tw3m1/C/lkX3o7qMo3jVb0NqBl9T3eISc3pRa5qZT47r/bG5UaUIj41u ZdVSFf1583ecSjAQCRhQFj+Obum3jkM9ZSqPQri8v4blXW4QNr3n6EVt rXNI2sYUPFdRT+lQw0L+LU9EWOCDVtXU3jjpujj2wKzD9FWbLXgi9S4a Zqi2UD7IwPVFmm/oGpqAEbhANJfH9pcxd6IQNsoELWw7f568BbR6qYsa EY5HWY135vtLBmALeDGhYYgxXa85J2jXqzOdsbSVexvVTuP4XiCTO+4w 8RN89br4vsHhkKXGompQ7Z7HQ07V05gLdxdj99TFbz4ILsshB01y2qWx Jr2zNg==
+; resign=20460416024207
+82r.subtree2. 86400 IN A 192.0.2.1
+82r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . U73fXbsssOg2Du/DqzXEL3GWHU79ZQ2RuWMuI5q9Dr1ymdN/8ZfI/M+H mgzet0FwhdwOtYPybBpzklJwUj1vduj7/lq3eSiApVTKbyURNX9FD70J QLb0tIujmQLAzewJHnXZTu0+gcTzw4BwPZv3od2Bfdv9LjdWtqFKzO5C 0ZlVkiM94X9rmLOlR7tZBvts9+AuC5M6LA9FCFBWfgUZk6f9sS6q9fuL /ApF9xG11D5z/zQ/UvQBO/rcFrkYNW8iery2MQbdqkh32iDSlMQe/N85 uUnHS59iLIzIHGLryWuPTNW4CPIJX7UyZ7OznP+NY+xZFXfE+BoAb+n7 KjgC7A==
+; resign=20460416024207
+82r.subtree2. 86400 IN NSEC 83r.subtree2. A RRSIG NSEC
+82r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . q+82SmeIWsbPXp34ti37GrX6R2ALUmf5LPwb7xtYuD3RtxtNnlXq5BAg LCIqW6lCsaIIu+mafCPj2unp7+sivYvn3Ts75oG+FMxHWL53S0zGKcnm p+m6uir8FvtsUrs3hBldo7NDJ3bJ8s/FzBXMVNgMHbvpqn5joNq8f33o 9nuOQ8yGH1+/m4oQDJMCnOmo/3ueS/PpfPdcnB0k3PFslkrQ6y3WflFH 2GXxbsF7ZugWrS/VVlxl3FLIiMbdxvkpLkVvzCefmuIhrQ48k6eG7713 dTWh24GetY96qacyLkZGtKd2ht9xDX2LM12kMGoytBs7F0dBuqcnIm5N PbnTkQ==
+; resign=20460416024207
+83r.subtree2. 86400 IN A 192.0.2.1
+83r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . V1hihHtRb0zh4uCzifwDQ9j9TfnG0RUuD8LU1VzRm4qBXri+aHMuMycH sT74f1xfClAAsfY4ws1NA+rkcoExm4yIrgfiOVBhoZNekG96JJRJRkQr Zid5VDAF2xUWFJhLImA5MDOmdYsaTfAMnvXqHX5LgHJT5D28fHZ8fFPy PfWgiBCf88dOgbjlx5JKs1Ou9GYsWQCqKbnwp2nFvAwpnCqK4Waa/n/E wJanXLd14GlyGvnRYh5ITkJTFf69FerBSS7zTTQ638jicwAo17hhdzol Q2642gt9iNpo4cr3IcQ4GBBXCFKv97/+YImANV7ahwOy+uVsqFiACt3N mYyVZQ==
+; resign=20460416024207
+83r.subtree2. 86400 IN NSEC 84r.subtree2. A RRSIG NSEC
+83r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . MSS+aIIC3ZvkhSN7PhPvDmLgXUMuViCgyXxkhW9KUkSkkpx4Eby38thp ZwhktiNXapsUO0URG2qZGs0S0lW3dTJdn2GauvXNkfzGKY1iWgZsVQvZ xnKU6Id5CSMgjXAPrfKuMqU6hYi1awsLqmYsNzdVxi4SVLPm8hvkx2jG OfeLeWGLWtfHr289viu7cWO1Yeh3AEqWoUteJNU6eugdsTvrWurnfqpB QQfidnh6OTAO18cyXcTsLCYCT3e4XVrBzpVUnaEacVG7J9MXcggOeKOB 6KvG3PlDpRjgU1HElR1XZdMWTU/c7vv8v30+q+i61cEauiyYRFAwBFvB BcCBHg==
+; resign=20460416024207
+84r.subtree2. 86400 IN A 192.0.2.1
+84r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ICS4/eZuAoeN0YbfAnYLeOTHjqYgOi0CFeMkzO1bC669gbyyMA7tcM6j LvgDnLwyJ1UT9Nm+sNYmh86fEaDM1j2DerRoqA9QVHAWcnPIUtBDOY7x QGMoHxnqSmSWOp18s60xNaoJm2f0Xyd8v4oJvnP2D4Rl9oHtxOJ2226p WIducgaPGzvLeZK+Ug0N5DisRHOWiE/2YvKV5raAMzo+t1yhIyjC0+mR FNWes3UyVOV2mahCKUM60g91UUjkB8tHdcswnapnYc7X+KLhmXZMGbXx FSia2cpobZPl7PLKbI2DEDFOVwjyYELQI4g3dPZ4aY0mMpAEUkRSwp59 a5/fOA==
+; resign=20460416024207
+84r.subtree2. 86400 IN NSEC 85r.subtree2. A RRSIG NSEC
+84r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . i1COi8BjDNgls+2f/p9xV58rS/3HQVZAPEO7t74bm40MlsFz3zwFwwP6 5hZxQBuH37erv/MMNbhcJo4IrFzdxxCiAKTm9vy4+Od8mOsNM3wBPhgh 4q1wdZQgMVHt0ujSib0M4VvQoh+foFRFFJ9Yi8UHJp9yJUuL7nwtmNhr 4YxihkEcRv1uS2M9ycXY6Igmr8diND1ZLT7AzLtPHw2Kkk7k140KjkI1 G5UcWLI/emknTRlGrLIRb4yUHtFa9HxEX7vHmGNbffKOYZOUrQ6Weh18 +NTH7SdOt96KH7bO9DdjySEUx2k3uusYDAq69U65ZeUWJHh5CV/GIfrv i5DJjA==
+; resign=20460416024207
+70r.subtree2. 86400 IN A 192.0.2.1
+70r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . An/Ib6WiyjTOKXYc1z2GOhDsve5WSEstyynUiKtPzFzskx4DAUz3oJXu WmTWj0fjh8lcS6hc9v7OuxaIVjzgeYy/4I3aiioC3m0hvKoCXX/a89Dg RGU/pMValMERnOPHQFJNfnn6bnLJK2OcncKeG+9xI8zCZ12vXvRCDFHj Wb5LB+B7az/Zpei5QAxv/RKXwTW8Z+Czrd5Rf8ArNGjvI/n+iJDRR/BS zGMd72Sdyxfq3YrNrTbSgSfDE84PW9oAXd1JGvOqvBeVx3at8YRKA+OW +mDxxeWqyaYHrwfBT7K+PoEDnianVszY9xgsDSqutQ9Add9AuVXPAHss B4Ec/A==
+; resign=20460416024207
+70r.subtree2. 86400 IN NSEC 71r.subtree2. A RRSIG NSEC
+70r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Aa1xQzeSjo2ddP5isYJnFrdxGIB41u5BfLPUcvC+C61s0XJBbh9qxs+G BwMfWWasjOD7eCE8Blpp8s02RFoLqcOZR2jfog/acgYBmke+NxPJb4Ga eJJltzGBD6pxrT8bwa2seocz/mZHRNEutTtRT6BBumsh9GHe4/j/7f6H 0JkKe2yrFmfeoAgiL+0QdYzvr228Drl5/Nd70xsLX/6Bp0ad9jVF9wqG 4+fdxgbVu5cYBl7TSBNfqGZZF2zH2s5XqS1W7kzy6k58NE0bHFmPsvt6 J4LBwzlJ9LuvVGUTt6DeEEKelpJi6rZDNOG4iBu9R4Upr/rxjD5uVOSu QEnv0Q==
+; resign=20460416024207
+45r.subtree2. 86400 IN A 192.0.2.1
+45r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . q5s21YBzlfcLpOiXVDIGCwm7vswz0+SKDguv3QQODUK7suHTq/nzPWgW E5qoFeZlsR2WmdNxT3DEWDW/b1LqnXqGvMIMKmtA5mJhU99qeaJ4Eqz0 5VXA5GCxc43iSofbOAHT4Cbx6pxJKY6uzetkMAz9j+8wZpG4doviPbw+ fHGr2X3Y8Cnikpw9sT9FD94evVVy/oDaIfqp12N3P91q5KkPNk7CroA7 y2RKP1L+xjYknTeiO37RBfkOoIHuIn/kj2T7s2tBiLBWdyqPMOHekP6V nNJLk+anPQF2ZSIgMgjVJsei/F+2RllGH+Gg4kNLZ8EXhg2XXgPnrU57 nb2Ndw==
+; resign=20460416024207
+45r.subtree2. 86400 IN NSEC 46r.subtree2. A RRSIG NSEC
+45r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . w/pT9IPEFwTRvgktRLWSCLiSVHQluPR+94ErsFy77t9CIVZ11Gf0m+mG 2QUtOg2yY1yBTPNIo999r+NiofSj82QGUQLl7BFi0jgfZgYHLMYwaAXr CkoVletffW8GFpA3Bw8bpqZugQdRGOQiNl2DvXqtlqexOS7af+3Ogcgt CDviGDcTFeO0AFAA/hD4dF7W/F6uPAQUQBj2wnxop8rAGnFl9XwFEq38 yvv0kl9ad2L3lzNZraNUq6Z/AsiSFsmDP5M8zVQda1okwPOZdzQivfmY 6k1WxCpxWqtVmiQ88Jp+2S7UhFgFsAWpjSPbYce7bkld0SU30z5nPfkZ W5DtjQ==
+; resign=20460416024207
+86r.subtree2. 86400 IN A 192.0.2.1
+86r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FiG0vw+/0qkfx3ZkNHTrxsNQB6rfb7xyNV5Gj6N9foOgpvvdIrenSD+t wGhgf0YhwYhCbO+1MZ1E7ShoFoVG4AOtU5sOrLuVUKJUr32qwgjYZtPc ePnVGVXSbSfcOWmsdQaGRtADIHysR3wVMdy8z1GI2r1EuG7WLOhFSZYJ vedJ0c+Zd+LOlZAyQA2RO/R3sBf/fMKiaLKXBaKsk/4v8rkP0nEr2pKN CM4ylF2oEk4REvAHskmQBmUilFha6lqcE5AwSFigeqeJGNw0TVG3rnSn 5cIfAj0NxGNwK4MR1C8WLbLsaS5nq9lPfKuxDr5FRDaWzvmozdIXSeJW OBxF7A==
+; resign=20460416024207
+86r.subtree2. 86400 IN NSEC 87r.subtree2. A RRSIG NSEC
+86r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . W2aE201SZaLS1WZeR5va8Ql5bAWmmkgd+vRO0a1Tlg5keuCHo2e3lk60 oVP6KP5N7oVEnfAZAJNTTdfvnt0RVpZbsBmvh0o+m4oVTRWgMp6TIK2D K4vTjckBK1fGT4BHCV3QPepn0cFjfRz5N3HStg3scJRYczYvv/igkR2P MNj1tnx2Q4Kd8xThuc/b8AnvuUcVxYhR1SRlCHBTHqxsYlbRD/CrJXVb xDqLrN8cBLwDKwMPOQfUOnYq4LdjzbJJLrJNjRkB/ptYmSE2YdwTCY3i XEyW71hJUcya9QrSrSDLXfeO47yuCDxzuRe7ia+cWMh63YnVabuQLd0P hzUebQ==
+; resign=20460416024207
+88r.subtree2. 86400 IN A 192.0.2.1
+88r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . ECdyI5EKeFIu4Jj3tL4d7By5WXstfWxgjZ3Oa1V/8rkOa6KRE3yForzO +F4Ao6t5CjKM5ZsKoeC1ixWLPJ2pE7c0jEgRx2Pey5TJAzrf8wVQKoud fVe6XFU4by1wPNqTXU+0Asu/uRLSaqz1gOIH6/7Ozm7P3/3xPVDbcVLA 0EbxnMZnfcSOOa9sewySVp+4eVbF7mvOQ1FwoIcJlXc1HdDUcO1UPEHE BqEfE2l6NFp71oDWXsaSXetWx4yJtLrIg9EH0N70l74Kboxy4nWfydQE ywuA2v8pjIBu4pz+ZBxTFiPs3cydNUltOAnAvHkL4wI+PKL7SpHM+sx6 YTNtmA==
+; resign=20460416024207
+88r.subtree2. 86400 IN NSEC 89r.subtree2. A RRSIG NSEC
+88r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . XHdir6VyCMaQ1x+1rLuON1wW1B1AXXMynTL3O3z/nDSTGLCiL+AVTm2u Y/3B+go+MK2dWrsZq+l8esBlps8CXDqltYReyuEIKUgnLk/MCa4rBO8L lWS1SJ/xdB8VJ/830aR7a4YifkKaKncQT2qb99iESz/1EqUZbdCNdGTl aoKFGPQd2xI1J5x/RJwad9YGhpHZQ61Uvxv05VIuQIV21j0EnlWHbYPo KCtbgDZfpFx8XVd4qp6qai+lxozgqviYcsCqHJa31iPMTdpaUBtjEOiV 3Cz9PSJHqbWrTGGVEVX+k45qXWPtcABjob4Eb9mM6yDN+ON3QEfR8Vhn aU4V0g==
+; resign=20460416024207
+89r.subtree2. 86400 IN A 192.0.2.1
+89r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . LtLaFzg/aJ2onaZKr5khKLgVpJUJL12P0WD95k6o500NUZbnlmweGC0I JbIqQIqhUlrrQkCYsMCXhs499SNmmUZ0BsOypN/BDfMePbd09F+PZz0o JRHvkf/yGjkBK0kEtZ6Crorl5coSuASum8NwR/nw8h6skZX5cH0+aHgI SwWW9IAnjnQ/RNLnTtLN8Mr5WuhmGGSkje1UPe9oBOlVbWjMAyhX2lVw AU77h15R4bR/Rf18Cz+zC6n1SsTkCRs52j/IS55U/cuJnHv5IfmajHTc bZWcFkbIwX5PMcxkyjS/4oT7e8FRoJPxletIXWxLWwSW+8G+OYwBKlGx ATOMTg==
+; resign=20460416024207
+89r.subtree2. 86400 IN NSEC 8r.subtree2. A RRSIG NSEC
+89r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . IJAqvknmXRDy3ZD8gN2zhCLYkhvHljU3UISARe7YeDf4WBkRLwK+07u7 B6JjiAJWf/qJ7qcQl4bIYxPJ5iLmZuT7bXGyt5aPWcxwk1LluU+At8cE HFEEna0yZshXPWHQiSULtXj+KAjz+rDDD4I9sjoYIY0R925+OXibYpgp 3vDoPD6G+mS7VCl1zoMOU3gpPFpbFwjF3s0kzcJOfW739JFyg4Sxe15H LewmsStzmC5Dy7KWErFPzio1i+e8xbpMvGVjFVapzd/gAaL5Fsw63SYJ onDpQje2zIK7ysaiEXtuezF1mFm/d/HJR5cPu3F80xf35F+CrViR2zZZ zJjWEg==
+; resign=20460416024207
+8r.subtree2. 86400 IN A 192.0.2.1
+8r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . S9i6MWPelsjhrORy81ifXaJw+PpOHNWxa4UXaIa9cFskybm2wx5pGQhN 9Csz9v3P05lOwDXasBEHybrlodlSY7+49M3g5yqLEnLVWiqlgRdulLcP zgNkLAgW98FixbbR4WdweG0HYbYuq6yJoNERdLAJGRhz6MHPgjYTL8uI jvQ2UcBPdeeB/eJ5uL208LbFXrOgCPvCjSxz3DGXgrpH6JHRFm1i6yCs sjou+AsF4gX/AwcM9XUmxkh5lD7HN+Ey4RDtid/DNL+2WrnLoBwjh/YB DdiT123+lNg62J+TlQPgvr4Wdemz+MTzcWkSfkjemdyYZ6W06IU0altW /Ufr/Q==
+; resign=20460416024207
+8r.subtree2. 86400 IN NSEC 90r.subtree2. A RRSIG NSEC
+8r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . droP8BigHxGz/QAEBLOgYzSmdABokPM5BRluOKY/zr9Nh/z2dBiBW1EO 1cV5GPK8lcx8toqEfu0Zq9+aWzdcV3ofvRhZ5q+JndY05X8GutT/0P3W gRI+96ilRFksLKU7dS2uj6dpsopssqkSqGRl685YowNnn7O9bB4LyVHP hOlbhd1sXQ40+bvxYKMui0SU3FrotvjNuU3u1CtGormUj+vjFSbI5FoE XkWDg3C1QsTD3mEjXctQIfL5mv2TTWkJqrAbPoG11wTtAa8Dm0/RjoUK jvnJPMloW/gIiKTyif6PV9MLIyDHSXDdvxOwV4pAQhpUm3up0ZkAv3Pe HMSZFA==
+; resign=20460416024207
+90r.subtree2. 86400 IN A 192.0.2.1
+90r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . sguhfNWje/190bkCeJ3v/75CjfdICbh/bno4hsC2ApZbC6Nq7XVTA/VQ U35g7ln+fjiD03UwTAf/k4RzA8REfQanK02LNhj56zFNNhhdnzIsJPrg sDcyzQITp+SMy1boseShRjukdgSMMU8E9igqs3S456YC+dlp2Zn4WrbN p9noTXUxsVt3/TJH0RaZBy5azXgEU1+PiLO7YNamHrrvTdcj57RjyVxe M811Ge269l6RWfuLR6ssV0LNO8ZrLRHNnxvtg8RvkbJXSmOjom7dbqEJ Gg+GA4/vHqDl37nJdFuHkhwUnVSIV0ntcGDbaNBliEj4t663iLB69Fz1 FYbK/g==
+; resign=20460416024207
+90r.subtree2. 86400 IN NSEC 91r.subtree2. A RRSIG NSEC
+90r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . O6+LuRnl9i6Rt+pAl8e7rtkF3aACthkrEbTQ3eMHyiYeoTJsVpVyPArX PXFTtXJxYQjqwPHVGl4jukLnJtDJy+x/OXwhLea2UWmpo/APCO8K4xrp DjCiBBe/S2uYnkVRN30wu4t2UqdojBGnstmsBmaIF9+oyVRf67OSiHet QOzttz+g8DGbfJ8KUlzYhPCPvIUR3Dh3jEfeAfO8CVZ64gXZwTwGZblk HYNZzKQs2zgga+r/R9Dpp4mHxyDVH1x5USVXx51Y9sXZ+kNJeclyEibd zZMli/iDXH9PHBTCXbyHBgwMmZOtNMU17k8/oA8uwPZKN1owUmfWO7Ar psAbxw==
+; resign=20460416024207
+91r.subtree2. 86400 IN A 192.0.2.1
+91r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . dOrt/iu9d/CPmDnkLgGlI7bd1RDgVosMf0+Q3y1qg53EzrqtBCuIKZ+D /7a2RtYWTkZp3j+hyrhz+WHBTCfOr/KUtlHCsrOIExY7cpSTaYFSYA5c UR7qAkYsUNSBXBsnA/ucSyllfXoiFeBDlmO4wNnwnjI0F1hMJCxnn2Rf i5qliDwwlEojWPYMygYwzba0Rc1GMljsd1HLysjnRu2rslGNRx8dACRu QkqFNLOU/iqrJuWIAYCRmkvJTqdwNbV921oGOTxNJAtw3KWDNWMQ24hn sH1IDkyAq81FKDtI19BdnAlp9uDhBTWOGMJ32cooTWTig/ut3oMA/zqd gGp6zA==
+; resign=20460416024207
+91r.subtree2. 86400 IN NSEC 92r.subtree2. A RRSIG NSEC
+91r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . RRwaMaalChpe0eMu1K7oa/VKqUbOeXwhN4GwZLP8kANBVr2H+VEHkme7 2eTZfBZamuheOlACtuW8kwm/F4ZAtY1XTr0bFKTXV/J/X/eISDCj2JTW +a5iNuq1JHok+vCZmF6t6u4PVKM/hD/AttXrcjHHK2TfOdgO7rZ5gGaV 0cBjT7SeWbzB3RFYWH8imLOE7v469TGO7W7bGxDwdgyvEQ9ZCZV6z82e uQoKddHC2x3nwyrkJJ70Z2HzBJOiByQjXs6hCIetC128HEtLXoQ9ILKA s2gB1EEzT6+PxcARpK7TNNuOTNpIriDkw3vItpfp9AvTqaR6xRX5YwYC ufT4tg==
+; resign=20460416024207
+92r.subtree2. 86400 IN A 192.0.2.1
+92r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . elD3rVSaGOQbDjXtl1rMX3XtseJPSbIyVkORcBJffjom9JPkT2MenM5K 6d5ksiAlTgnSCcOf2gMI8AtSpJkSdmU6CBCADUc9p7rdF1x2K4tQ6WdY G4koQmAXGiojxihftE8z/N/xIt2r7oNMfwdfk7cxgKUBuJs/gabcJFLi 5UVgZcVqlWVlLmxKWk1zNbAY+LuCZrmnbP4nA1qQKqA/i/yMPnPcgdWq DZLGtpjFiSucVDxgaIoQQkobMGG6mMwwrWo5F11V1G1zmxx6NXvaKt+A 4u8pclsNxpDCQQcCPgg0znXPtLcyaQl8bkkg+lK5lXr7Ct9gPaWITzJE Z63S9g==
+; resign=20460416024207
+92r.subtree2. 86400 IN NSEC 93r.subtree2. A RRSIG NSEC
+92r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . IuwQACPEsgjR9yO0WItcJ7eyepFUEh9Dg44S7hIS4BmZPgcQrO4FeboD GzGTjbR2x/gP8nYarvTVmxK79kqQ9UATg2pPojyJMKWkoE6Cv03qJzKx nxHkAnyGFGYjIa6zFNJbPQfz+G7SX+o14ooHyVXZcKmAqwKbxxuAbYK3 oSWSMM6j+lk+Ak+GoPgm5zxrbh+aivtGqaZm7Xhg9A877a+w7TjVtOn+ WagIQB9FjG2Ilk3pGIiAskveIMrUM7NPr7bTeyvb0rmewheDfd6uJ8Ou ZirvOhuwv59rDoZ+Us6JaBQ5Y/SrsMQRe3bLwNjx6nuCQlUKedK4db4p N454kg==
+; resign=20460416024207
+93r.subtree2. 86400 IN A 192.0.2.1
+93r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . FfE3JvfkfdE56yTYiqu2CCMxMo2BUwBrjXf1XU9OZ2amya2aSGl8yAl1 yGXbm/FwLKgK6WIfZZTQHBr9Hm3UgwWNg79Z5OQj+RvIHfuIgpX0YgFR TlnLtuFWaufrxu+g+7AVRSB4RkVsN0b0i00o3RGS2pjAtAQ6S/h5R/Qo FlE/scsykd3dCPGyPICxRZdDzouk91aw9YZtxJFOESUHYdKFpqsnu0yf Sd1VQ2kFh3kgkk4Dw59L8hOmYXT8Uxi2gHOQsbb94NwpEJ45NyRAe+xI Vwu2vp+4P1talaWlrlEPqH7/eQ+2AiaAqklwZhKKkN4H9KMx8/T2lpDc ObhuJQ==
+; resign=20460416024207
+93r.subtree2. 86400 IN NSEC 94r.subtree2. A RRSIG NSEC
+93r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . DmsCcR5kIlhw7uLX5C9MiQUya6CWs5q7zTZg5n/r7XiEyV/gB9c2eFLp nTgJdbczL2ShCSEtkb0aYPRRjAvtqGF48JmGJf1NYFgWCmQj2Rei4Mfe besJvJrn36MIY6iOeGFlVGcSffsQxySISEf2+c9UcKOkqsV/O/GCPFHx dU1yIDu+bit8Szhrt7x3RhB4Uj7RWZW6pzeVA/KxBXHHFDSwl/51dygf j0KB7D1zZK5nfXqHtecwzFy9JuL9SkcvI65oPaAjcjS+AbqWZgiHpeXN djbNKhzqUDl4jP+R5wdxs3h1ONQ9WFQOT70fy8gB1Z4fTJLutbEOiMSZ n99gFg==
+; resign=20460416024207
+94r.subtree2. 86400 IN A 192.0.2.1
+94r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . CmTajnWkJnUVcGZzcokeDSqy/NnLUc4Odgi+zon5TrgSaAOEL66pm/zN BSkfK5nciTwptVSg9gpYPu2GMmFamytxizc2cu5YkC0zpcuuWDybGSAz YNw4iZBhLP8iowD1Ea/s7pDflgogShGzX24mNpnYJxVimqO2Hyhp045t xQTumsHzZQVWUlk1Hsa1OtcAOqY4JA9A1zn+gP4Fw59ytXTRPs1C2I3y F8WIOfTrWviN6NvcwFrii2tiQwC8BpI6OI/1wbdkIy2KbHT5IgXxsUh8 QgQjFg2W5pwcbUaAGBnljuAaH7ZL9EAQ70rIybJYNomSc1nEImShb7FZ 39TOSw==
+; resign=20460416024207
+94r.subtree2. 86400 IN NSEC 95r.subtree2. A RRSIG NSEC
+94r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . RcJXpMi5SCMM2tlHDYUwRpRf/BpCyhs3ZUx6hDWJZ9FkMZTJFG4/MWqA Amib2FehoHrhMHcWdsyo/nFQjni3EX28TKt4OlHUYMgo7Kw8p//R1IRA xM5YBsF29k+QZwup8RO/op4a3q2/1lsL+VedNeoJo0giTdfahq8phN1U MNszlAQZ3SuA1T3QM0EnEldKJ9I4HPXYLR/0tLKnjTkCOX6xSI976lAT 2fRGhRuDrhimzjhOWKFMzu+Is5vvAHEqNvqBO9jSIWihK/qoa5Lk+1Tm 96MWrzFMPkMgiJIm5Y6w0VIFFguN/8l+dLbyrzE7j/ytxoPgvWwItjmc gPI8Gg==
+; resign=20460416024207
+95r.subtree2. 86400 IN A 192.0.2.1
+95r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . dEvcbOlBNJYt35F+19pG6fuqzLKwxV8EKNYst0AiKnHxThDTBe6t5zTy j3BQqVaODnEl5nexcjfWigd3u1Fyp5V4U6kPDhe3Jy31FJxDZQPRPLlI hVf1iftkzse4iVx+NpYn6xDbacz/t84RNBcKQGAb2M0MXEbdTBSMI/dA AloyXIedaJAkMMMVTBCQ+b9+YJrCpFfq/uN4hI4kjWvh6hVuVj69CrHL mGD03WXdrURhmhl2mw+AXjUoIopCLGch5h0j1qEc3sAPIkTkmpcaZTvV ZaQTPypvkoK3exrlfgeOhfvHlbk7olrfLcoV6QgSJKRXO/MqRwOVagl8 xIF2Ww==
+; resign=20460416024207
+95r.subtree2. 86400 IN NSEC 96r.subtree2. A RRSIG NSEC
+95r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . E9OK+kEC4PuVEN+P7c+4KzNCAbR3Hsttzi8URw5yjixiGRPbUdS4BdV9 z618OxJzmJJ7M8mz0a5Ofp3fqeXGRmuWecSEMtzFcHJuY6e6Rr2GTXEg sXM3TZnSkEnVtzuCLa+n6nAB0pgEZVnaln3gOpMGZp3kUjfl/0dHE68e lUQ1mqyefoMMx6wsFELXdRXw8g863N8rnu13jx2RkTwufU9Ri7uxSzIw BZvLnGaGQhweCxme8mG7kXU4pZCD/CDYsUUB2trhvsr2f/6ZZkxrlgLe iDBAFB8aw0eQGlf6wPNrumye/LI9CLGwoe1Y3rR5+1O8qCnJYSIy4Bs2 q28nEQ==
+; resign=20460416024207
+96r.subtree2. 86400 IN A 192.0.2.1
+96r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . u1+d0AAtw7KTRsYrFqDlgNIWZUHG3DGvnS/OVOlLiaFrHjlJ8YMKGRoQ WB0cxJ061Hw783mmVZcIJXCXSpfZV6q/afki+b9ZwIO+0yK9jD5cY9vD H5CteywVmduCLoDs0uDYBWE4UbWcB3L8wqSgnDaY4dTB3lmj4oEA1qde a04/Y/R19ntlmDM3lS7tDjY5xRlzK9Q+e8EsxRfriI326px6HBRqdYew dn7RRxVjWbZR4H04nedDQ5jeBh0t0Jl9UUdv5rsYRczE58DR4hkeMrMD RsQv9aEMCCHZIblHS7Jo4O7T7mAjit+0n2n6HOqs8eistzURsErvZogl WvCZEA==
+; resign=20460416024207
+96r.subtree2. 86400 IN NSEC 97r.subtree2. A RRSIG NSEC
+96r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . A1rrt+oKmhhenoj0rBMlott7i5hJ4sKX6yTBi2sQWSCiJuZtPOVaZ6v/ AoOib4IgvSqYc6iIDxn600Xt116j/TWYM/skU/59bd430WbGhoDCCVpC IoNilVWmaDltDkqRsOOJygeJyt8MubUXry5v33MHjxRYywHUZk/nZz9T 05dYDB5IkPzQkUR1kpWBSmxbKhpTDMtOhVtg1oAjb9dOlIDcC6TWno8E hpW5NRJjSAihwOUScPKrubIG3pN4Wzj6yADVao0IBQ+sukm2l3BDdXnU tOI9Xsk2/f2JObVQNs7QqFgtnD6Ht0+9ZfnBCJJtPPcz3flaYIaNeGQ9 bA41rQ==
+; resign=20460416024207
+97r.subtree2. 86400 IN A 192.0.2.1
+97r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . gLTmSp8fhdoLKIOQVjqsqT08v7KC6sfuAPIUrLiauhLvDO06Tx0hIt8V i6An7KsOsaH0ELt2piRbGf6MS/aH2jHSuluFgGp2VIal74kT41A+YsL8 2CkgAcLSvD17HCtWAzxz76eplPJbA3+2nLnSooM04+qR/uckUNdP3Tnm MbZExofXSj9OG4b6U/kRZJ9H/gEbdktDCdtvnCBeJUoV6K9bQc8/RVhE cf/blPcGNm/KVf8XQZgoBTsr4t3S2f68WwpT2pyeoaqYxKr5m6k9jfWC FDRWjGXjtU5IEeKubH2EAjpgBWPLFaRUDAerTjFTqsnJneCMds2k9+gf cULClg==
+; resign=20460416024207
+97r.subtree2. 86400 IN NSEC 98r.subtree2. A RRSIG NSEC
+97r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . JenfHs6Shb7IxIUJK+cojyruYCbm9sBk6sdB7UzHDy/ezOnm3Y5tThi4 YKfvUKxT5/J0rapIIq+eIjs8SabokeG14EsVFDtOHD80xhqdaAukAy6B naSvzFhe9hod0MWx9lM1pOosoVvrA+Wv0/8DOcGxC0myK9TvOSrl3PKM Xr4y8Y9pthV960TM5g7y2GE+aXfpX2gOZiMGe0WOTqUheXQ8wC+etOdz DW5aRLV63qtwXnlY7UWC7balJ36EzELY8al2to5KVhwxwfjjdvak4rWv KH8dDTUY1gL1jX+GPMmY8uLRWMKTHtx6HA8s9AHVxLLjra0lERkbs0/m Y59/mg==
+; resign=20460416024207
+98r.subtree2. 86400 IN A 192.0.2.1
+98r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . uC88oukw3HlsaDfwK2REa9YSQ2wNlwkw3aQ/MBTHUdiOxhZ8cCjdAbLH Vrbn2zCUYcwcAyfYTNOQ51sG2xQ9IsQaclrKO6wUZTikr9vQdwk+/yYS 08o1K3dc0NUSacllCkJ277lekmq8WRhX/qLUKaLScOV2mzZpEVdfqR/7 eZYI1fjjsMiccOjjdKvQdrK63lZrR4JW8KkIlojguz7lNjIuPqV3srD5 psHZDPL8LfoMcFFV3BKr0X875JKdbCTWEeVcAlMmgfp+AZ9s++SggHix ALCBjBhrNJT5zeCTGwU/Y3dBfYYbzTX93fOMfRP60ER9LH+Ns2khIOCj 4siS1Q==
+; resign=20460416024207
+98r.subtree2. 86400 IN NSEC 99r.subtree2. A RRSIG NSEC
+98r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . JzxL54WUwM8cA11scDj5GOKD8AW9VgQT/XtJXKGhwejRMuNH9exj0uLx 6OPLNNN6gp3dgz/mu1LhcE0SiTr42AhmJlYzBJsmJuMZOJ1OGcWYX2yi Yl5udmTp4SJrGQPvH48KzSY4/te2ik4Y6lKdiKz1bd7aaH8cqExIVHMa DJEzXwjJqOQQCph2rq4d1XFfst19ghyoU2GJ1zX8iMZ1TDR/GppRaxd4 ZecfR9C9dzItIjnPo7Ky8JUeGTQY0+PvIriAIcNVTRkEm4x5+zNmLOXe fWTTnoeVhTXVJYki7sLjtKWvoe5jKdiVgJWqLN6Ps4JTJJgu3BHGJzbH fqKqxQ==
+; resign=20460416024207
+99r.subtree2. 86400 IN A 192.0.2.1
+99r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . NO/jA3/Fq6aB+mNCs5aejoZdHdH8MZ5waNKWmW6ba8Tsp58jiyMOAiIj aLiLBZ/A7vXj67NJcCgp0ffs0eE1xwtgcg8DwtsOFHey1I+zmQSjmmUI Gqi0Yi4F0G5V2URkNcZPfc0Ey6zXmpCrfVrKS0DktP8g/kjLkXilTd5F xfLZZzb4XvQA6r/UzNdWaQwe+IU1pYS9h7i4hezxDINj5RoemRykPbBa hm76bG51GX/iLcQ32SuJHM8AaQ6PnnKXLvB+LSkpMBTm9KshBLu0/Sju GeletOuG2sveR5BP6BpQR5STJ7upc5jZH0kOjBUwrV2UI0GPTg0aMl8Q jToNww==
+; resign=20460416024207
+99r.subtree2. 86400 IN NSEC 9r.subtree2. A RRSIG NSEC
+99r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . QHgZNdQItRkchoW2V8xGFn1CFeAwWEH3WlRNv5PTDopVoaOF5bZocWUC OTp+5SFJSqVZ2VZBSfCaqjurC+pwpGYviQQvxV7C31aiQFwoX+092XvU 7EM+YbOLDzg3u95z7YESo58o/ZVIti7p4+JPAp8IKPjGiPhUKUH3hArO TvoZja6adGCNpLkxbE5VFA6esI4ZTMv6IEhALBi9/IdI8ZRpv6WMG+Bn By1w9My++QCtPFQIFC1tLTqPKaIUknL/H5gqKMHmR5YteXpQ9jHtuwEN Lys7P3I/E2TVr2Jt35YyYkUUaxR/W/AMRYOu/cyzuR10nLgiQBLhc3xs 89k7Lw==
+; resign=20460416024207
+69r.subtree2. 86400 IN A 192.0.2.1
+69r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . H9e3SuKHH/ds/qxJExQwItamzQmLsoYnh4u8aAAIOHg1+LT2P0Sn/KGw ISMl/McYEeOXlFt2SGwC+3X96MlXC9GpsMxtPLy86Jt6p/O8gVdSpr7P y2bFB2Ioz1I64mGKTN+AAJtl85zeV5DbEeQcB1uCtHGdh3aGhgS/CT82 b0eFOAuLiIf45Wy5h5DFZz0n9XAUJGOCejUUqZlEdfSJ/9Y9KCjFPOf6 XDr1Ts6eJhukd2FnGImAVRsCeYuCtkm3GjSkxYG3QgqJNDUDpk++Xzw+ 5kNiOZgQ20y8d+VBfC5IJ7mZlEuUCO2Bw71EQC/BbHVkQdgDbKe1oFHn 39Gd6w==
+; resign=20460416024207
+69r.subtree2. 86400 IN NSEC 6r.subtree2. A RRSIG NSEC
+69r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . HUFhcEIpOw13gVwxCTqn4SkpOHHks/x+5qXR7zbMzeCWh3xH2e5V6xyd vh0gD4+qpb6BV0VtHTCWad0IvZADLo434fIAf1skmmkwXHK1P3s8hqeh K7iKTcKSwqE0Fx2vCpmmkutyKgNxokyzBmyhhMHk3kIbh5SqOl3Bl9wh G5WEuICCh+SxFaM+8eHOZl0iWIZAAD4EW0kVdU4Mi0GUI0rvufYL+Jv3 z2Scw+zxA3ZOBPLgHFWLrVd51zap6Sc+CpIE/E+le0TRcam/wV++Tpr/ 5tE7FN634aoVZlf0z5R9VE5rlVvrkzxeo/OYmLWaJ0JbirTMc2nUIOQF 7lFbdw==
+; resign=20460416024207
+9r.subtree2. 86400 IN A 192.0.2.1
+9r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . w3ZdS3SbdNZCYHTVb0iyxEhH6CsCcS3dzopqkxQLH1NhLlEC6zCD8vaZ 5iN3eBV55Kl751tEkmiHeCajXLRhDd6xZC71gZRLp9B8oouEX6DwofZ9 m00N7njjtUd5+ZAuqN5r4oZm4N9zVMA9xQjXszDGM5r91EjxzChohzUE lZ+b4aeOd2jA3z2nmygUq4bNYGFonmzCDhuxvhhrjw/dt/eXWGMWBxFN Rz6Z5ZTgcAgU6pjANijSQ/7Z/D1SZ9f1gDL9O/yK/et08jR5Vlt49McZ mTWcv6ZPOuzrcWSFzO14TR4ZWQEYBKWHcflACH0QRJeMoIrR7D97pPGl /nycqA==
+; resign=20460416024207
+9r.subtree2. 86400 IN NSEC . A RRSIG NSEC
+9r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . pMO6DJZKqSfiPHQjd3nRdxcwHgyDVAtSeTlLVZ5q3RBOQ1hxKa+b1DF/ lPNU/0YDsfEznEhkFPQj3ceWZtABhVLqj/tQNQy2TBkLBFVDTQuLHNWh wjSAwmg5SRUI/NG3t3c4142Sb3yc418XrRWTMu9SS147hUzqZsNls/19 6vkIX7GQLIyqhOULjedZplNFgOxpRmFa5zORTZ30ghBZj4yuH/DXKJyb POLV/h3OrXi3d6KzKkFH+ItRija8dlm3cNNvf5ibD9DsXoEDDm+xu4YM tJDHS5pcPschc99l4YzQPw3AUmOFmoDknzS3v4KSXcbb2olM24pqVb9P eAENJg==
+; resign=20460416024207
+85r.subtree2. 86400 IN A 192.0.2.1
+85r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . f3ogUidaXG4YfcQ1pk360EY2gIoH9khcvUAnb+suATWZgyBzhgzzFLcW r8hB+tF4uW7fpECLic2w+BmZUFX4yGWVDYldS0Nywvj0vApNWAnrn+3P RdNJ7zkI5/ZO1IZDGTGaRZlG1wkIv8KL4lXjRHjw/VU6vMgepld5cCNJ 5tYkhBdHKvltkBqsD3owQ1nCuvfDfIQp0z6H5gCx5EpoNsqKz2O9Z8Mr hjBvl/yVhDrvXRbN0d4o3pHdg1ozm2azNiwRllgCD/vxV48Uw7ZOkFUk Z/e0kgTOsKHCCGCV976OjH8ryFPiVwD2r7tAbzzrEctDtL3pgKocTknh aUSfsA==
+; resign=20460416024207
+85r.subtree2. 86400 IN NSEC 86r.subtree2. A RRSIG NSEC
+85r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . Nc2ArE889yWghwl7jvS5+GSHcYV7Of1aX+YDPThgr+DxZS68jKXwXhrQ NBVMGe/tPdCML/Ka7rggAUmgQJmrx4JY/s1tJMbm1GUJr/rVd23Qt2/3 5Y0OQF76JeNfKtyUfdr2K2nIKtvte0zPq4RlNa0NWm4Lmm53PES4PnO3 w5cnnxwePHMN8aQp5GucyFCdw+Ah8pRGZLRVbPJgj+iL+YhT4k4mQeI8 yoMuC46icqMRzg0lGHaBGeSE8uD1GVjbwOpngqWjB8vdYDmBX1vWNt4P dVII2ybrBIwfnqnGZptNqhs0roKaLGz2GA/0b05bwl1jHL9AhYZ5Zin2 Xz1JVw==
+; resign=20460416024207
+77r.subtree2. 86400 IN A 192.0.2.1
+77r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . QAc9wnWC//xMkla4VISWRiYme7K9/K1PJc+oLaJ+slJJSjbqqbqcqUCx EjDCw7V+a9FvnSTBs3tGpBvnQfGBT8LwbVzgrLA3p7Ad0JX2O5XlaYU7 tcbDatZdRoQkB/rxpUcPzEKe8t3mT4nzavyK+QTF6gbfrcGLRRrdADwh lUOmada+Hz/339BkLYHEvFHCdnmovmtBUOQPjgVpj2pFWPZ+cAOw5VJV 7/mLFwijuoe22Yq9E9WaI6YWJIeobu+Dht13FlUkiAgQeqyDAOCnmLjp Hf2VRjXvRktliYOHoM1gBG9c7qUHIHtOS4oXRT+0ElN9CBBovTzdIoo6 WML2DQ==
+; resign=20460416024207
+77r.subtree2. 86400 IN NSEC 78r.subtree2. A RRSIG NSEC
+77r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . jp+KYsenqjl2t7tBVdmg1VVQoq1eHgqnnGjLeVU3oGNLtwwvQWEl0fV0 MNMuBUzH5jhMw8L8VvmFihIa9RQga3PQDI+CnrvRH1YNM8OAXzaWWWrA Fs4cS/cCUo3dbfgL7bxxTYmYYG96N4QbTvBE6M/N8bvquMikTdWjsMBy rz3ZsMCoVC8CXr+i06vevOKZaqfJ08hYg/hbW8+h/XtgXaQZjLEGJ68X osO+20PkOHvRD7X3RTHVlDrT2ZZOjCokNEgl4/alp5t5xXi6A3hD0HfF T/tzTiBi8gVw/PXJRgPV8rE3BXCf7Cqwwtei/28Knkf13zHD8fOmrV08 PuQfKQ==
+; resign=20460416024207
+87r.subtree2. 86400 IN A 192.0.2.1
+87r.subtree2. 86400 IN RRSIG A 8 2 86400 20460416024207 20180815062314 48409 . fPXLpsy1AqCF8HpnyeICWkuMtVpa87akqhqWJdKTG6pKKVQR0swtNaYE e3aLriYm92PGuYzdIIrQLyBlRqfdsyNIbhcGSufBplGt0hUR6CCFP6eX wuOBqw2lhrNReC/GM+E1wjvZTfZ1qwc+JOA462jXX0t9PSrTTlFM5CEj /91mOZXJ8DTYHeDPy3jvPEjhR/137fC0WhHHrMNKI7wT0dNSObq49DXu IzqrtNpzEWtiDYQdhk37hraueb0BNFmVONLqve2Q51D320qi953XR3pz olGQ9UrI7Eeo4kbwSGLo8U7mIH2wS65RHzrO8+4U+W/pwHXLoGnQxiRw e1NfdQ==
+; resign=20460416024207
+87r.subtree2. 86400 IN NSEC 88r.subtree2. A RRSIG NSEC
+87r.subtree2. 86400 IN RRSIG NSEC 8 2 86400 20460416024207 20180815062314 48409 . JyZPoj5NUaz3hnlq+EoPq2mozjKk1VsVmD9Ipc7M8chTIxjiYLnyRVcx ZeE5p+M19oXK71RPwSdYH2UjiFCOa4cZntkIny972BYEDtezGdSYzz/z VMDf9r6XOBeECHiIAaF6q+ptR0K47188Q6+eTlcPZMNSDrckRFBfI/qT 9R99kCvDJ5WyBTY+2aT2qCP0+INJi/XQfOj3RuuJhxLh7UBbicdR43fb QW//otvC9Hb6RwxZFkrM283ddAcjA/0/L4bPmV5AQo1lGeO6e/5jmBay /uJD4HVAHwW3T7rbQyMOWjoIGyJreEaSiE2ykBptwscbgrBOygyOs2wX zZ8EEg==
+; resign=20460416024207
diff --git a/daemon/cache.test/testroot.zone.unsigned b/daemon/cache.test/testroot.zone.unsigned
new file mode 100644
index 0000000..65be9b6
--- /dev/null
+++ b/daemon/cache.test/testroot.zone.unsigned
@@ -0,0 +1,215 @@
+. 86400 SOA rootns. you.test. 2017071101 1800 900 604800 86400
+. 86400 NS rootns.
+rootns. 86400 A 198.41.0.4
+
+subtree1. 86400 TXT "txt exists"
+subtree1. 86400 A 192.0.2.1
+b.subtree1. 86400 TXT "txt exists"
+b.subtree1. 86400 A 192.0.2.2
+a.b.subtree1. 86400 TXT "txt exists"
+a.b.subtree1. 86400 A 192.0.2.3
+a.b.subtree1. 86400 AAAA 2001:db8::
+
+; subtree2. is empty non-terminal
+1r.subtree2. 86400 AAAA 2001:db8::
+2r.subtree2. 86400 AAAA 2001:db8::1
+2r.subtree2. 86400 AAAA 2001:db8::2
+3r.subtree2. 86400 AAAA 2001:db8::
+4r.subtree2. 86400 A 192.0.2.1
+5r.subtree2. 86400 A 192.0.2.1
+6r.subtree2. 86400 A 192.0.2.1
+7r.subtree2. 86400 A 192.0.2.1
+8r.subtree2. 86400 A 192.0.2.1
+9r.subtree2. 86400 A 192.0.2.1
+10r.subtree2. 86400 A 192.0.2.1
+11r.subtree2. 86400 A 192.0.2.1
+12r.subtree2. 86400 A 192.0.2.1
+13r.subtree2. 86400 A 192.0.2.1
+14r.subtree2. 86400 A 192.0.2.1
+15r.subtree2. 86400 A 192.0.2.1
+16r.subtree2. 86400 A 192.0.2.1
+17r.subtree2. 86400 A 192.0.2.1
+18r.subtree2. 86400 A 192.0.2.1
+19r.subtree2. 86400 A 192.0.2.1
+20r.subtree2. 86400 A 192.0.2.1
+21r.subtree2. 86400 A 192.0.2.1
+22r.subtree2. 86400 A 192.0.2.1
+23r.subtree2. 86400 A 192.0.2.1
+24r.subtree2. 86400 A 192.0.2.1
+25r.subtree2. 86400 A 192.0.2.1
+26r.subtree2. 86400 A 192.0.2.1
+27r.subtree2. 86400 A 192.0.2.1
+28r.subtree2. 86400 A 192.0.2.1
+29r.subtree2. 86400 A 192.0.2.1
+30r.subtree2. 86400 A 192.0.2.1
+31r.subtree2. 86400 A 192.0.2.1
+32r.subtree2. 86400 A 192.0.2.1
+33r.subtree2. 86400 A 192.0.2.1
+34r.subtree2. 86400 A 192.0.2.1
+35r.subtree2. 86400 A 192.0.2.1
+36r.subtree2. 86400 A 192.0.2.1
+37r.subtree2. 86400 A 192.0.2.1
+38r.subtree2. 86400 A 192.0.2.1
+39r.subtree2. 86400 A 192.0.2.1
+40r.subtree2. 86400 A 192.0.2.1
+41r.subtree2. 86400 A 192.0.2.1
+42r.subtree2. 86400 A 192.0.2.1
+43r.subtree2. 86400 A 192.0.2.1
+44r.subtree2. 86400 A 192.0.2.1
+45r.subtree2. 86400 A 192.0.2.1
+46r.subtree2. 86400 A 192.0.2.1
+47r.subtree2. 86400 A 192.0.2.1
+48r.subtree2. 86400 A 192.0.2.1
+49r.subtree2. 86400 A 192.0.2.1
+50r.subtree2. 86400 A 192.0.2.1
+51r.subtree2. 86400 A 192.0.2.1
+52r.subtree2. 86400 A 192.0.2.1
+53r.subtree2. 86400 A 192.0.2.1
+54r.subtree2. 86400 A 192.0.2.1
+55r.subtree2. 86400 A 192.0.2.1
+56r.subtree2. 86400 A 192.0.2.1
+57r.subtree2. 86400 A 192.0.2.1
+58r.subtree2. 86400 A 192.0.2.1
+59r.subtree2. 86400 A 192.0.2.1
+60r.subtree2. 86400 A 192.0.2.1
+61r.subtree2. 86400 A 192.0.2.1
+62r.subtree2. 86400 A 192.0.2.1
+63r.subtree2. 86400 A 192.0.2.1
+64r.subtree2. 86400 A 192.0.2.1
+65r.subtree2. 86400 A 192.0.2.1
+66r.subtree2. 86400 A 192.0.2.1
+67r.subtree2. 86400 A 192.0.2.1
+68r.subtree2. 86400 A 192.0.2.1
+69r.subtree2. 86400 A 192.0.2.1
+70r.subtree2. 86400 A 192.0.2.1
+71r.subtree2. 86400 A 192.0.2.1
+72r.subtree2. 86400 A 192.0.2.1
+73r.subtree2. 86400 A 192.0.2.1
+74r.subtree2. 86400 A 192.0.2.1
+75r.subtree2. 86400 A 192.0.2.1
+76r.subtree2. 86400 A 192.0.2.1
+77r.subtree2. 86400 A 192.0.2.1
+78r.subtree2. 86400 A 192.0.2.1
+79r.subtree2. 86400 A 192.0.2.1
+80r.subtree2. 86400 A 192.0.2.1
+81r.subtree2. 86400 A 192.0.2.1
+82r.subtree2. 86400 A 192.0.2.1
+83r.subtree2. 86400 A 192.0.2.1
+84r.subtree2. 86400 A 192.0.2.1
+85r.subtree2. 86400 A 192.0.2.1
+86r.subtree2. 86400 A 192.0.2.1
+87r.subtree2. 86400 A 192.0.2.1
+88r.subtree2. 86400 A 192.0.2.1
+89r.subtree2. 86400 A 192.0.2.1
+90r.subtree2. 86400 A 192.0.2.1
+91r.subtree2. 86400 A 192.0.2.1
+92r.subtree2. 86400 A 192.0.2.1
+93r.subtree2. 86400 A 192.0.2.1
+94r.subtree2. 86400 A 192.0.2.1
+95r.subtree2. 86400 A 192.0.2.1
+96r.subtree2. 86400 A 192.0.2.1
+97r.subtree2. 86400 A 192.0.2.1
+98r.subtree2. 86400 A 192.0.2.1
+99r.subtree2. 86400 A 192.0.2.1
+100r.subtree2. 86400 A 192.0.2.1
+101r.subtree2. 86400 A 192.0.2.1
+102r.subtree2. 86400 A 192.0.2.1
+103r.subtree2. 86400 A 192.0.2.1
+104r.subtree2. 86400 A 192.0.2.1
+105r.subtree2. 86400 A 192.0.2.1
+106r.subtree2. 86400 A 192.0.2.1
+107r.subtree2. 86400 A 192.0.2.1
+108r.subtree2. 86400 A 192.0.2.1
+109r.subtree2. 86400 A 192.0.2.1
+110r.subtree2. 86400 A 192.0.2.1
+111r.subtree2. 86400 A 192.0.2.1
+112r.subtree2. 86400 A 192.0.2.1
+113r.subtree2. 86400 A 192.0.2.1
+114r.subtree2. 86400 A 192.0.2.1
+115r.subtree2. 86400 A 192.0.2.1
+116r.subtree2. 86400 A 192.0.2.1
+117r.subtree2. 86400 A 192.0.2.1
+118r.subtree2. 86400 A 192.0.2.1
+119r.subtree2. 86400 A 192.0.2.1
+120r.subtree2. 86400 A 192.0.2.1
+121r.subtree2. 86400 A 192.0.2.1
+122r.subtree2. 86400 A 192.0.2.1
+123r.subtree2. 86400 A 192.0.2.1
+124r.subtree2. 86400 A 192.0.2.1
+125r.subtree2. 86400 A 192.0.2.1
+126r.subtree2. 86400 A 192.0.2.1
+127r.subtree2. 86400 A 192.0.2.1
+128r.subtree2. 86400 A 192.0.2.1
+129r.subtree2. 86400 A 192.0.2.1
+130r.subtree2. 86400 A 192.0.2.1
+131r.subtree2. 86400 A 192.0.2.1
+132r.subtree2. 86400 A 192.0.2.1
+133r.subtree2. 86400 A 192.0.2.1
+134r.subtree2. 86400 A 192.0.2.1
+135r.subtree2. 86400 A 192.0.2.1
+136r.subtree2. 86400 A 192.0.2.1
+137r.subtree2. 86400 A 192.0.2.1
+138r.subtree2. 86400 A 192.0.2.1
+139r.subtree2. 86400 A 192.0.2.1
+140r.subtree2. 86400 A 192.0.2.1
+141r.subtree2. 86400 A 192.0.2.1
+142r.subtree2. 86400 A 192.0.2.1
+143r.subtree2. 86400 A 192.0.2.1
+144r.subtree2. 86400 A 192.0.2.1
+145r.subtree2. 86400 A 192.0.2.1
+146r.subtree2. 86400 A 192.0.2.1
+147r.subtree2. 86400 A 192.0.2.1
+148r.subtree2. 86400 A 192.0.2.1
+149r.subtree2. 86400 A 192.0.2.1
+150r.subtree2. 86400 A 192.0.2.1
+151r.subtree2. 86400 A 192.0.2.1
+152r.subtree2. 86400 A 192.0.2.1
+153r.subtree2. 86400 A 192.0.2.1
+154r.subtree2. 86400 A 192.0.2.1
+155r.subtree2. 86400 A 192.0.2.1
+156r.subtree2. 86400 A 192.0.2.1
+157r.subtree2. 86400 A 192.0.2.1
+158r.subtree2. 86400 A 192.0.2.1
+159r.subtree2. 86400 A 192.0.2.1
+160r.subtree2. 86400 A 192.0.2.1
+161r.subtree2. 86400 A 192.0.2.1
+162r.subtree2. 86400 A 192.0.2.1
+163r.subtree2. 86400 A 192.0.2.1
+164r.subtree2. 86400 A 192.0.2.1
+165r.subtree2. 86400 A 192.0.2.1
+166r.subtree2. 86400 A 192.0.2.1
+167r.subtree2. 86400 A 192.0.2.1
+168r.subtree2. 86400 A 192.0.2.1
+169r.subtree2. 86400 A 192.0.2.1
+170r.subtree2. 86400 A 192.0.2.1
+171r.subtree2. 86400 A 192.0.2.1
+172r.subtree2. 86400 A 192.0.2.1
+173r.subtree2. 86400 A 192.0.2.1
+174r.subtree2. 86400 A 192.0.2.1
+175r.subtree2. 86400 A 192.0.2.1
+176r.subtree2. 86400 A 192.0.2.1
+177r.subtree2. 86400 A 192.0.2.1
+178r.subtree2. 86400 A 192.0.2.1
+179r.subtree2. 86400 A 192.0.2.1
+180r.subtree2. 86400 A 192.0.2.1
+181r.subtree2. 86400 A 192.0.2.1
+182r.subtree2. 86400 A 192.0.2.1
+183r.subtree2. 86400 A 192.0.2.1
+184r.subtree2. 86400 A 192.0.2.1
+185r.subtree2. 86400 A 192.0.2.1
+186r.subtree2. 86400 A 192.0.2.1
+187r.subtree2. 86400 A 192.0.2.1
+188r.subtree2. 86400 A 192.0.2.1
+189r.subtree2. 86400 A 192.0.2.1
+190r.subtree2. 86400 A 192.0.2.1
+191r.subtree2. 86400 A 192.0.2.1
+192r.subtree2. 86400 A 192.0.2.1
+193r.subtree2. 86400 A 192.0.2.1
+194r.subtree2. 86400 A 192.0.2.1
+195r.subtree2. 86400 A 192.0.2.1
+196r.subtree2. 86400 A 192.0.2.1
+197r.subtree2. 86400 A 192.0.2.1
+198r.subtree2. 86400 A 192.0.2.1
+199r.subtree2. 86400 A 192.0.2.1
+200r.subtree2. 86400 A 192.0.2.1
+201r.subtree2. 86400 A 192.0.2.1
diff --git a/daemon/daemon.mk b/daemon/daemon.mk
new file mode 100644
index 0000000..e0d0000
--- /dev/null
+++ b/daemon/daemon.mk
@@ -0,0 +1,79 @@
+kresd_SOURCES := \
+ daemon/io.c \
+ daemon/network.c \
+ daemon/engine.c \
+ daemon/worker.c \
+ daemon/bindings.c \
+ daemon/ffimodule.c \
+ daemon/tls.c \
+ daemon/tls_ephemeral_credentials.c \
+ daemon/tls_session_ticket-srv.c \
+ daemon/zimport.c \
+ daemon/session.c \
+ daemon/main.c
+
+kresd_DIST := daemon/lua/kres.lua daemon/lua/kres-gen.lua \
+ daemon/lua/trust_anchors.lua daemon/lua/zonefile.lua
+
+# Embedded resources
+%.inc: %.lua
+ @$(call quiet,XXD_LUA,$<) $< > $@
+ifeq ($(AMALG), yes)
+kresd.amalg.c: daemon/lua/sandbox.inc daemon/lua/config.inc
+else
+daemon/engine.o: daemon/lua/sandbox.inc daemon/lua/config.inc
+kresd-lint: daemon/lua/sandbox.inc daemon/lua/config.inc
+endif
+
+# Installed FFI bindings
+bindings-install: $(kresd_DIST) $(DESTDIR)$(MODULEDIR)
+ $(INSTALL) -m 0644 $(kresd_DIST) $(DESTDIR)$(MODULEDIR)
+
+LUA_HAS_SETFUNCS := \
+ $(shell pkg-config luajit --atleast-version=2.1.0-beta3 && echo 1 || echo 0)
+
+kresd_CFLAGS := -fPIE \
+ -Dlibknot_SONAME=\"$(libknot_SONAME)\" \
+ -Dlibzscanner_SONAME=\"$(libzscanner_SONAME)\" \
+ -DROOTHINTS=\"$(ROOTHINTS)\" \
+ -DLIBEXT=\"$(LIBEXT)\" \
+ -DLUA_HAS_SETFUNCS="$(LUA_HAS_SETFUNCS)"
+kresd_DEPEND := $(libkres) $(contrib)
+kresd_LIBS := $(libkres_TARGET) $(contrib_TARGET) $(libknot_LIBS) \
+ $(libzscanner_LIBS) $(libdnssec_LIBS) $(libuv_LIBS) $(lua_LIBS) \
+ $(gnutls_LIBS)
+
+# Enable systemd
+ifeq ($(HAS_libsystemd), yes)
+kresd_CFLAGS += -DHAS_SYSTEMD
+kresd_LIBS += $(libsystemd_LIBS)
+endif
+
+# Make binary
+$(eval $(call make_sbin,kresd,daemon,yes))
+
+# Targets
+date := $(shell head -n1 < NEWS | sed 's/.*(\(.*\)).*/\1/' | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$$' || date -u -r NEWS +%F)
+daemon: $(kresd) $(kresd_DIST)
+daemon-install: kresd-install bindings-install
+ifneq ($(SED),)
+ $(SED) -e "s/@VERSION@/$(VERSION)/" -e "s/@DATE@/$(date)/" \
+ -e "s|@MODULEDIR@|$(MODULEDIR)|" \
+ -e "s|@KEYFILE_DEFAULT@|$(KEYFILE_DEFAULT)|" \
+ doc/kresd.8.in > doc/kresd.8
+ $(INSTALL) -d -m 0755 $(DESTDIR)$(MANDIR)/man8/
+ $(INSTALL) -m 0644 doc/kresd.8 $(DESTDIR)$(MANDIR)/man8/
+endif
+daemon-clean: kresd-clean
+ @$(RM) daemon/lua/*.inc daemon/lua/trust_anchors.lua
+
+daemon/lua/trust_anchors.lua: daemon/lua/trust_anchors.lua.in
+ @$(call quiet,SED,$<) -e "s|@ETCDIR@|$(ETCDIR)|g;s|@KEYFILE_DEFAULT@|$(KEYFILE_DEFAULT)|g" $< > $@
+
+daemon/lua/kres-gen.lua: | $(libkres)
+ @echo "WARNING: regenerating $@"
+ @# the sed saves some space(s)
+ daemon/lua/kres-gen.sh | sed 's/ /\t/g' > $@
+.DELETE_ON_ERROR: daemon/lua/kres-gen.lua
+
+.PHONY: daemon daemon-install daemon-clean
diff --git a/daemon/engine.c b/daemon/engine.c
new file mode 100644
index 0000000..5eebe3d
--- /dev/null
+++ b/daemon/engine.c
@@ -0,0 +1,1055 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <contrib/cleanup.h>
+#include <ccan/json/json.h>
+#include <ccan/asprintf/asprintf.h>
+#include <uv.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/param.h>
+#include <libzscanner/scanner.h>
+
+#include "daemon/engine.h"
+#include "daemon/bindings.h"
+#include "daemon/ffimodule.h"
+#include "lib/nsrep.h"
+#include "lib/cache/api.h"
+#include "lib/defines.h"
+#include "lib/cache/cdb_lmdb.h"
+#include "lib/dnssec/ta.h"
+
+/* Magic defaults for the engine. */
+#ifndef LRU_RTT_SIZE
+#define LRU_RTT_SIZE 65536 /**< NS RTT cache size */
+#endif
+#ifndef LRU_REP_SIZE
+#define LRU_REP_SIZE (LRU_RTT_SIZE / 4) /**< NS reputation cache size */
+#endif
+#ifndef LRU_COOKIES_SIZE
+ #ifdef ENABLE_COOKIES
+ #define LRU_COOKIES_SIZE LRU_RTT_SIZE /**< DNS cookies cache size. */
+ #else
+ #define LRU_COOKIES_SIZE LRU_ASSOC /* simpler than guards everywhere */
+ #endif
+#endif
+
+/** @internal Compatibility wrapper for Lua < 5.2 */
+#if LUA_VERSION_NUM < 502
+#define lua_rawlen(L, obj) lua_objlen((L), (obj))
+#endif
+
+/**@internal Maximum number of incomplete TCP connections in queue.
+* Default is from Redis and Apache. */
+#ifndef TCP_BACKLOG_DEFAULT
+#define TCP_BACKLOG_DEFAULT 511
+#endif
+
+/** @internal Annotate for static checkers. */
+KR_NORETURN int lua_error (lua_State *L);
+
+/* Cleanup engine state every 5 minutes */
+const size_t CLEANUP_TIMER = 5*60*1000;
+
+/* Execute byte code */
+#define l_dobytecode(L, arr, len, name) \
+ (luaL_loadbuffer((L), (arr), (len), (name)) || lua_pcall((L), 0, LUA_MULTRET, 0))
+/** Load file in a sandbox environment. */
+#define l_dosandboxfile(L, filename) \
+ (luaL_loadfile((L), (filename)) || engine_pcall((L), 0))
+
+/*
+ * Global bindings.
+ */
+
+/** Register module callback into Lua world. */
+#define REGISTER_MODULE_CALL(L, module, cb, name) do { \
+ lua_pushlightuserdata((L), (module)); \
+ lua_pushlightuserdata((L), (cb)); \
+ lua_pushcclosure((L), l_trampoline, 2); \
+ lua_setfield((L), -2, (name)); \
+ } while (0)
+
+/** Print help and available commands. */
+static int l_help(lua_State *L)
+{
+ static const char *help_str =
+ "help()\n show this help\n"
+ "quit()\n quit\n"
+ "hostname()\n hostname\n"
+ "package_version()\n return package version\n"
+ "user(name[, group])\n change process user (and group)\n"
+ "verbose(true|false)\n toggle verbose mode\n"
+ "option(opt[, new_val])\n get/set server option\n"
+ "mode(strict|normal|permissive)\n set resolver strictness level\n"
+ "reorder_RR([true|false])\n set/get reordering of RRs within RRsets\n"
+ "resolve(name, type[, class, flags, callback])\n resolve query, callback when it's finished\n"
+ "todname(name)\n convert name to wire format\n"
+ "tojson(val)\n convert value to JSON\n"
+ "map(expr)\n run expression on all workers\n"
+ "net\n network configuration\n"
+ "cache\n network configuration\n"
+ "modules\n modules configuration\n"
+ "kres\n resolver services\n"
+ "trust_anchors\n configure trust anchors\n"
+ ;
+ lua_pushstring(L, help_str);
+ return 1;
+}
+
+static bool update_privileges(int uid, int gid)
+{
+ if ((gid_t)gid != getgid()) {
+ if (setregid(gid, gid) < 0) {
+ return false;
+ }
+ }
+ if ((uid_t)uid != getuid()) {
+ if (setreuid(uid, uid) < 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/** Set process user/group. */
+static int l_setuser(lua_State *L)
+{
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isstring(L, 1)) {
+ lua_pushliteral(L, "user(user[, group)");
+ lua_error(L);
+ }
+ /* Fetch UID/GID based on string identifiers. */
+ struct passwd *user_pw = getpwnam(lua_tostring(L, 1));
+ if (!user_pw) {
+ lua_pushliteral(L, "invalid user name");
+ lua_error(L);
+ }
+ int uid = user_pw->pw_uid;
+ int gid = getgid();
+ if (n > 1 && lua_isstring(L, 2)) {
+ struct group *group_pw = getgrnam(lua_tostring(L, 2));
+ if (!group_pw) {
+ lua_pushliteral(L, "invalid group name");
+ lua_error(L);
+ }
+ gid = group_pw->gr_gid;
+ }
+ /* Drop privileges */
+ bool ret = update_privileges(uid, gid);
+ if (!ret) {
+ lua_pushstring(L, strerror(errno));
+ lua_error(L);
+ }
+ lua_pushboolean(L, ret);
+ return 1;
+}
+
+/** Quit current executable. */
+static int l_quit(lua_State *L)
+{
+ engine_stop(engine_luaget(L));
+ return 0;
+}
+
+/** Toggle verbose mode. */
+static int l_verbose(lua_State *L)
+{
+ if (lua_isboolean(L, 1) || lua_isnumber(L, 1)) {
+ kr_verbose_set(lua_toboolean(L, 1));
+ }
+ lua_pushboolean(L, kr_verbose_status);
+ return 1;
+}
+
+char *engine_get_hostname(struct engine *engine) {
+ static char hostname_str[KNOT_DNAME_MAXLEN];
+ if (!engine) {
+ return NULL;
+ }
+
+ if (!engine->hostname) {
+ if (gethostname(hostname_str, sizeof(hostname_str)) != 0)
+ return NULL;
+ return hostname_str;
+ }
+ return engine->hostname;
+}
+
+int engine_set_hostname(struct engine *engine, const char *hostname) {
+ if (!engine || !hostname) {
+ return kr_error(EINVAL);
+ }
+
+ char *new_hostname = strdup(hostname);
+ if (!new_hostname) {
+ return kr_error(ENOMEM);
+ }
+ if (engine->hostname) {
+ free(engine->hostname);
+ }
+ engine->hostname = new_hostname;
+ network_new_hostname(&engine->net, engine);
+
+ return 0;
+}
+
+/** Return hostname. */
+static int l_hostname(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (lua_gettop(L) == 0) {
+ lua_pushstring(L, engine_get_hostname(engine));
+ return 1;
+ }
+ if ((lua_gettop(L) != 1) || !lua_isstring(L, 1)) {
+ lua_pushstring(L, "hostname takes at most one parameter: (\"fqdn\")");
+ lua_error(L);
+ }
+
+ if (engine_set_hostname(engine, lua_tostring(L, 1)) != 0) {
+ lua_pushstring(L, "setting hostname failed");
+ lua_error(L);
+ }
+
+ lua_pushstring(L, engine_get_hostname(engine));
+ return 1;
+}
+
+/** Return server package version. */
+static int l_package_version(lua_State *L)
+{
+ lua_pushliteral(L, PACKAGE_VERSION);
+ return 1;
+}
+
+char *engine_get_moduledir(struct engine *engine) {
+ return engine->moduledir;
+}
+
+int engine_set_moduledir(struct engine *engine, const char *moduledir) {
+ if (!engine || !moduledir) {
+ return kr_error(EINVAL);
+ }
+
+ char *new_moduledir = strdup(moduledir);
+ if (!new_moduledir) {
+ return kr_error(ENOMEM);
+ }
+ if (engine->moduledir) {
+ free(engine->moduledir);
+ }
+ engine->moduledir = new_moduledir;
+
+ /* Use module path for including Lua scripts */
+ char l_paths[MAXPATHLEN] = { 0 };
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wformat" /* %1$ is not in C standard */
+ /* Save original package.path to package._path */
+ snprintf(l_paths, MAXPATHLEN - 1,
+ "if package._path == nil then package._path = package.path end\n"
+ "package.path = '%1$s/?.lua;%1$s/?/init.lua;'..package._path\n"
+ "if package._cpath == nil then package._cpath = package.cpath end\n"
+ "package.cpath = '%1$s/?%2$s;'..package._cpath\n",
+ new_moduledir, LIBEXT);
+ #pragma GCC diagnostic pop
+
+ int ret = l_dobytecode(engine->L, l_paths, strlen(l_paths), "");
+ if (ret != 0) {
+ lua_pop(engine->L, 1);
+ return ret;
+ }
+ return 0;
+}
+
+/** Return hostname. */
+static int l_moduledir(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ if (lua_gettop(L) == 0) {
+ lua_pushstring(L, engine_get_moduledir(engine));
+ return 1;
+ }
+ if ((lua_gettop(L) != 1) || !lua_isstring(L, 1)) {
+ lua_pushstring(L, "moduledir takes at most one parameter: (\"directory\")");
+ lua_error(L);
+ }
+
+ if (engine_set_moduledir(engine, lua_tostring(L, 1)) != 0) {
+ lua_pushstring(L, "setting moduledir failed");
+ lua_error(L);
+ }
+
+ lua_pushstring(L, engine_get_moduledir(engine));
+ return 1;
+}
+
+/** @internal for l_trustanchor: */
+static void ta_add(zs_scanner_t *zs)
+{
+ map_t *ta = zs->process.data;
+ if (!ta)
+ return;
+ if (kr_ta_add(ta, zs->r_owner, zs->r_type, zs->r_ttl, zs->r_data, zs->r_data_length))
+ zs->process.data = NULL; /* error signalling */
+}
+/** Enable/disable trust anchor. */
+static int l_trustanchor(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ const char *anchor = lua_tostring(L, 1);
+ bool enable = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true;
+ if (!anchor || strlen(anchor) == 0) {
+ return 0;
+ }
+ /* If disabling, parse the owner string only. */
+ if (!enable) {
+ knot_dname_t *owner = knot_dname_from_str(NULL, anchor, KNOT_DNAME_MAXLEN);
+ if (!owner) {
+ lua_pushstring(L, "invalid trust anchor owner");
+ lua_error(L);
+ }
+ lua_pushboolean(L, kr_ta_del(&engine->resolver.trust_anchors, owner) == 0);
+ free(owner);
+ return 1;
+ }
+
+ /* Parse the record */
+ zs_scanner_t *zs = malloc(sizeof(*zs));
+ if (!zs || zs_init(zs, ".", 1, 0) != 0) {
+ free(zs);
+ lua_pushstring(L, "not enough memory");
+ lua_error(L);
+ }
+ zs_set_processing(zs, ta_add, NULL, &engine->resolver.trust_anchors);
+ bool ok = zs_set_input_string(zs, anchor, strlen(anchor)) == 0
+ && zs_parse_all(zs) == 0;
+ ok = ok && zs->process.data; /* reset to NULL on error in ta_add */
+
+ zs_deinit(zs);
+ free(zs);
+ /* Report errors */
+ if (!ok) {
+ lua_pushstring(L, "failed to process trust anchor RR");
+ lua_error(L);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+/** Load root hints from zonefile. */
+static int l_hint_root_file(lua_State *L)
+{
+ struct engine *engine = engine_luaget(L);
+ struct kr_context *ctx = &engine->resolver;
+ const char *file = lua_tostring(L, 1);
+
+ const char *err = engine_hint_root_file(ctx, file);
+ if (err) {
+ if (!file) {
+ file = ROOTHINTS;
+ }
+ lua_push_printf(L, "error when opening '%s': %s", file, err);
+ lua_error(L);
+ } else {
+ lua_pushboolean(L, true);
+ return 1;
+ }
+}
+
+/** @internal for engine_hint_root_file */
+static void roothints_add(zs_scanner_t *zs)
+{
+ struct kr_zonecut *hints = zs->process.data;
+ if (!hints) {
+ return;
+ }
+ if (zs->r_type == KNOT_RRTYPE_A || zs->r_type == KNOT_RRTYPE_AAAA) {
+ kr_zonecut_add(hints, zs->r_owner, zs->r_data, zs->r_data_length);
+ }
+}
+const char* engine_hint_root_file(struct kr_context *ctx, const char *file)
+{
+ if (!file) {
+ file = ROOTHINTS;
+ }
+ if (strlen(file) == 0 || !ctx) {
+ return "invalid parameters";
+ }
+ struct kr_zonecut *root_hints = &ctx->root_hints;
+
+ zs_scanner_t zs;
+ if (zs_init(&zs, ".", 1, 0) != 0) {
+ return "not enough memory";
+ }
+ if (zs_set_input_file(&zs, file) != 0) {
+ zs_deinit(&zs);
+ return "failed to open root hints file";
+ }
+
+ kr_zonecut_set(root_hints, (const uint8_t *)"");
+ zs_set_processing(&zs, roothints_add, NULL, root_hints);
+ zs_parse_all(&zs);
+ zs_deinit(&zs);
+ return NULL;
+}
+
+/** Unpack JSON object to table */
+static void l_unpack_json(lua_State *L, JsonNode *table)
+{
+ /* Unpack POD */
+ switch(table->tag) {
+ case JSON_STRING: lua_pushstring(L, table->string_); return;
+ case JSON_NUMBER: lua_pushnumber(L, table->number_); return;
+ case JSON_BOOL: lua_pushboolean(L, table->bool_); return;
+ default: break;
+ }
+ /* Unpack object or array into table */
+ lua_newtable(L);
+ JsonNode *node = NULL;
+ json_foreach(node, table) {
+ /* Push node value */
+ switch(node->tag) {
+ case JSON_OBJECT: /* as array */
+ case JSON_ARRAY: l_unpack_json(L, node); break;
+ case JSON_STRING: lua_pushstring(L, node->string_); break;
+ case JSON_NUMBER: lua_pushnumber(L, node->number_); break;
+ case JSON_BOOL: lua_pushboolean(L, node->bool_); break;
+ default: continue;
+ }
+ /* Set table key */
+ if (node->key) {
+ lua_setfield(L, -2, node->key);
+ } else {
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ }
+ }
+}
+
+/** @internal Recursive Lua/JSON serialization. */
+static JsonNode *l_pack_elem(lua_State *L, int top)
+{
+ switch(lua_type(L, top)) {
+ case LUA_TSTRING: return json_mkstring(lua_tostring(L, top));
+ case LUA_TNUMBER: return json_mknumber(lua_tonumber(L, top));
+ case LUA_TBOOLEAN: return json_mkbool(lua_toboolean(L, top));
+ case LUA_TTABLE: break; /* Table, iterate it. */
+ default: return json_mknull();
+ }
+ /* Use absolute indexes here, as the table may be nested. */
+ JsonNode *node = NULL;
+ lua_pushnil(L);
+ while(lua_next(L, top) != 0) {
+ bool is_array = false;
+ if (!node) {
+ is_array = (lua_type(L, top + 1) == LUA_TNUMBER);
+ node = is_array ? json_mkarray() : json_mkobject();
+ if (!node) {
+ return NULL;
+ }
+ } else {
+ is_array = node->tag == JSON_ARRAY;
+ }
+
+ /* Insert to array/table. */
+ JsonNode *val = l_pack_elem(L, top + 2);
+ if (is_array) {
+ json_append_element(node, val);
+ } else {
+ const char *key = lua_tostring(L, top + 1);
+ json_append_member(node, key, val);
+ }
+ lua_pop(L, 1);
+ }
+ /* Return empty object for empty tables. */
+ return node ? node : json_mkobject();
+}
+
+/** @internal Serialize to string */
+static char *l_pack_json(lua_State *L, int top)
+{
+ JsonNode *root = l_pack_elem(L, top);
+ if (!root) {
+ return NULL;
+ }
+ char *result = json_encode(root);
+ json_delete(root);
+ return result;
+}
+
+static int l_tojson(lua_State *L)
+{
+ auto_free char *json_str = l_pack_json(L, lua_gettop(L));
+ if (!json_str) {
+ return 0;
+ }
+ lua_pushstring(L, json_str);
+ return 1;
+}
+
+static int l_fromjson(lua_State *L)
+{
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ lua_pushliteral(L, "a JSON string is required");
+ lua_error(L);
+ }
+
+ const char *json_str = lua_tostring(L, 1);
+ JsonNode *root_node = json_decode(json_str);
+
+ if (!root_node) {
+ lua_pushliteral(L, "invalid JSON string");
+ lua_error(L);
+ }
+ l_unpack_json(L, root_node);
+ json_delete(root_node);
+
+ return 1;
+}
+
+/** @internal Throw Lua error if expr is false */
+#define expr_checked(expr) \
+ if (!(expr)) { lua_pushboolean(L, false); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); continue; }
+
+static int l_map(lua_State *L)
+{
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+ lua_pushliteral(L, "map('string with a lua expression')");
+ lua_error(L);
+ }
+
+ struct engine *engine = engine_luaget(L);
+ const char *cmd = lua_tostring(L, 1);
+ uint32_t len = strlen(cmd);
+ lua_newtable(L);
+
+ /* Execute on leader instance */
+ int ntop = lua_gettop(L);
+ engine_cmd(L, cmd, true);
+ lua_settop(L, ntop + 1); /* Push only one return value to table */
+ lua_rawseti(L, -2, 1);
+
+ for (size_t i = 0; i < engine->ipc_set.len; ++i) {
+ int fd = engine->ipc_set.at[i];
+ /* Send command */
+ expr_checked(write(fd, &len, sizeof(len)) == sizeof(len));
+ expr_checked(write(fd, cmd, len) == len);
+ /* Read response */
+ uint32_t rlen = 0;
+ if (read(fd, &rlen, sizeof(rlen)) == sizeof(rlen)) {
+ expr_checked(rlen < UINT32_MAX);
+ auto_free char *rbuf = malloc(rlen + 1);
+ expr_checked(rbuf != NULL);
+ expr_checked(read(fd, rbuf, rlen) == rlen);
+ rbuf[rlen] = '\0';
+ /* Unpack from JSON */
+ JsonNode *root_node = json_decode(rbuf);
+ if (root_node) {
+ l_unpack_json(L, root_node);
+ } else {
+ lua_pushlstring(L, rbuf, rlen);
+ }
+ json_delete(root_node);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ continue;
+ }
+ /* Didn't respond */
+ lua_pushboolean(L, false);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ }
+ return 1;
+}
+
+#undef expr_checked
+
+
+/** Trampoline function for module properties. */
+static int l_trampoline(lua_State *L)
+{
+ struct kr_module *module = lua_touserdata(L, lua_upvalueindex(1));
+ void* callback = lua_touserdata(L, lua_upvalueindex(2));
+ struct engine *engine = engine_luaget(L);
+ if (!module) {
+ lua_pushstring(L, "module closure missing upvalue");
+ lua_error(L);
+ }
+
+ /* Now we only have property callback or config,
+ * if we expand the callables, we might need a callback_type.
+ */
+ const char *args = NULL;
+ auto_free char *cleanup_args = NULL;
+ if (lua_gettop(L) > 0) {
+ if (lua_istable(L, 1) || lua_isboolean(L, 1)) {
+ cleanup_args = l_pack_json(L, 1);
+ args = cleanup_args;
+ } else {
+ args = lua_tostring(L, 1);
+ }
+ }
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wpedantic" /* void* vs. function pointer */
+ if (callback == module->config) {
+ module->config(module, args);
+ } else {
+ kr_prop_cb *prop = (kr_prop_cb *)callback;
+ auto_free char *ret = prop(engine, module, args);
+ if (!ret) { /* No results */
+ return 0;
+ }
+ JsonNode *root_node = json_decode(ret);
+ if (root_node) {
+ l_unpack_json(L, root_node);
+ } else {
+ lua_pushstring(L, ret);
+ }
+ json_delete(root_node);
+ return 1;
+ }
+ #pragma GCC diagnostic pop
+
+ /* No results */
+ return 0;
+}
+
+/*
+ * Engine API.
+ */
+
+static int init_resolver(struct engine *engine)
+{
+ /* Note: it had been zored by engine_init(). */
+ /* Open resolution context */
+ engine->resolver.trust_anchors = map_make(NULL);
+ engine->resolver.negative_anchors = map_make(NULL);
+ engine->resolver.pool = engine->pool;
+ engine->resolver.modules = &engine->modules;
+ engine->resolver.cache_rtt_tout_retry_interval = KR_NS_TIMEOUT_RETRY_INTERVAL;
+ /* Create OPT RR */
+ engine->resolver.opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t));
+ if (!engine->resolver.opt_rr) {
+ return kr_error(ENOMEM);
+ }
+ knot_edns_init(engine->resolver.opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool);
+ /* Use default TLS padding */
+ engine->resolver.tls_padding = -1;
+ /* Empty init; filled via ./lua/config.lua */
+ kr_zonecut_init(&engine->resolver.root_hints, (const uint8_t *)"", engine->pool);
+ /* Open NS rtt + reputation cache */
+ lru_create(&engine->resolver.cache_rtt, LRU_RTT_SIZE, engine->pool, NULL);
+ lru_create(&engine->resolver.cache_rep, LRU_REP_SIZE, engine->pool, NULL);
+ lru_create(&engine->resolver.cache_cookie, LRU_COOKIES_SIZE, engine->pool, NULL);
+
+ /* Load basic modules */
+ engine_register(engine, "iterate", NULL, NULL);
+ engine_register(engine, "validate", NULL, NULL);
+ engine_register(engine, "cache", NULL, NULL);
+
+ return array_push(engine->backends, kr_cdb_lmdb());
+}
+
+static int init_state(struct engine *engine)
+{
+ /* Initialize Lua state */
+ engine->L = luaL_newstate();
+ if (engine->L == NULL) {
+ return kr_error(ENOMEM);
+ }
+ /* Initialize used libraries. */
+ lua_gc(engine->L, LUA_GCSTOP, 0);
+ luaL_openlibs(engine->L);
+ /* Global functions */
+ lua_pushcfunction(engine->L, l_help);
+ lua_setglobal(engine->L, "help");
+ lua_pushcfunction(engine->L, l_quit);
+ lua_setglobal(engine->L, "quit");
+ lua_pushcfunction(engine->L, l_hostname);
+ lua_setglobal(engine->L, "hostname");
+ lua_pushcfunction(engine->L, l_package_version);
+ lua_setglobal(engine->L, "package_version");
+ lua_pushcfunction(engine->L, l_moduledir);
+ lua_setglobal(engine->L, "moduledir");
+ lua_pushcfunction(engine->L, l_verbose);
+ lua_setglobal(engine->L, "verbose");
+ lua_pushcfunction(engine->L, l_setuser);
+ lua_setglobal(engine->L, "user");
+ lua_pushcfunction(engine->L, l_trustanchor);
+ lua_setglobal(engine->L, "trustanchor");
+ lua_pushcfunction(engine->L, l_hint_root_file);
+ lua_setglobal(engine->L, "_hint_root_file");
+ lua_pushliteral(engine->L, libknot_SONAME);
+ lua_setglobal(engine->L, "libknot_SONAME");
+ lua_pushliteral(engine->L, libzscanner_SONAME);
+ lua_setglobal(engine->L, "libzscanner_SONAME");
+ lua_pushcfunction(engine->L, l_tojson);
+ lua_setglobal(engine->L, "tojson");
+ lua_pushcfunction(engine->L, l_fromjson);
+ lua_setglobal(engine->L, "fromjson");
+ lua_pushcfunction(engine->L, l_map);
+ lua_setglobal(engine->L, "map");
+ lua_pushlightuserdata(engine->L, engine);
+ lua_setglobal(engine->L, "__engine");
+ return kr_ok();
+}
+
+/**
+ * Start luacov measurement and store results to file specified by
+ * KRESD_COVERAGE_STATS environment variable.
+ * Do nothing if the variable is not set.
+ */
+static void init_measurement(struct engine *engine)
+{
+ const char * const statspath = getenv("KRESD_COVERAGE_STATS");
+ if (!statspath)
+ return;
+
+ char * snippet = NULL;
+ int ret = asprintf(&snippet,
+ "_luacov_runner = require('luacov.runner')\n"
+ "_luacov_runner.init({\n"
+ " statsfile = '%s',\n"
+ " exclude = {'test', 'tapered', 'lua/5.1'},\n"
+ "})\n"
+ "jit.off()\n", statspath
+ );
+ assert(ret > 0);
+
+ ret = luaL_loadstring(engine->L, snippet);
+ assert(ret == 0);
+ lua_call(engine->L, 0, 0);
+ free(snippet);
+}
+
+int engine_init(struct engine *engine, knot_mm_t *pool)
+{
+ if (engine == NULL) {
+ return kr_error(EINVAL);
+ }
+
+ memset(engine, 0, sizeof(*engine));
+ engine->pool = pool;
+
+ /* Initialize state */
+ int ret = init_state(engine);
+ if (ret != 0) {
+ engine_deinit(engine);
+ return ret;
+ }
+ init_measurement(engine);
+ /* Initialize resolver */
+ ret = init_resolver(engine);
+ if (ret != 0) {
+ engine_deinit(engine);
+ return ret;
+ }
+ /* Initialize network */
+ network_init(&engine->net, uv_default_loop(), TCP_BACKLOG_DEFAULT);
+
+ return ret;
+}
+
+static void engine_unload(struct engine *engine, struct kr_module *module)
+{
+ /* Unregister module */
+ auto_free char *name = strdup(module->name);
+ kr_module_unload(module);
+ /* Clear in Lua world, but not for embedded modules ('cache' in particular). */
+ if (name && !kr_module_embedded(name)) {
+ lua_pushnil(engine->L);
+ lua_setglobal(engine->L, name);
+ }
+ free(module);
+}
+
+void engine_deinit(struct engine *engine)
+{
+ if (engine == NULL) {
+ return;
+ }
+
+ /* Only close sockets and services,
+ * no need to clean up mempool. */
+ network_deinit(&engine->net);
+ kr_zonecut_deinit(&engine->resolver.root_hints);
+ kr_cache_close(&engine->resolver.cache);
+
+ /* The lru keys are currently malloc-ated and need to be freed. */
+ lru_free(engine->resolver.cache_rtt);
+ lru_free(engine->resolver.cache_rep);
+ lru_free(engine->resolver.cache_cookie);
+
+ /* Clear IPC pipes */
+ for (size_t i = 0; i < engine->ipc_set.len; ++i) {
+ close(engine->ipc_set.at[i]);
+ }
+
+ /* Unload modules and engine. */
+ for (size_t i = 0; i < engine->modules.len; ++i) {
+ engine_unload(engine, engine->modules.at[i]);
+ }
+ if (engine->L) {
+ lua_close(engine->L);
+ }
+
+ /* Free data structures */
+ array_clear(engine->modules);
+ array_clear(engine->backends);
+ array_clear(engine->ipc_set);
+ kr_ta_clear(&engine->resolver.trust_anchors);
+ kr_ta_clear(&engine->resolver.negative_anchors);
+ free(engine->hostname);
+ free(engine->moduledir);
+}
+
+int engine_pcall(lua_State *L, int argc)
+{
+#if LUA_VERSION_NUM >= 502
+ lua_getglobal(L, "_SANDBOX");
+ lua_setupvalue(L, -(2 + argc), 1);
+#endif
+ return lua_pcall(L, argc, LUA_MULTRET, 0);
+}
+
+int engine_cmd(lua_State *L, const char *str, bool raw)
+{
+ if (L == NULL) {
+ return kr_error(ENOEXEC);
+ }
+
+ /* Evaluate results */
+ lua_getglobal(L, "eval_cmd");
+ lua_pushstring(L, str);
+ lua_pushboolean(L, raw);
+
+ /* Check result. */
+ return engine_pcall(L, 2);
+}
+
+int engine_ipc(struct engine *engine, const char *expr)
+{
+ if (engine == NULL || engine->L == NULL) {
+ return kr_error(ENOEXEC);
+ }
+
+ /* Run expression and serialize response. */
+ engine_cmd(engine->L, expr, true);
+ if (lua_gettop(engine->L) > 0) {
+ l_tojson(engine->L);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int engine_load_sandbox(struct engine *engine)
+{
+ /* Init environment */
+ static const char sandbox_bytecode[] = {
+ #include "daemon/lua/sandbox.inc"
+ };
+ if (l_dobytecode(engine->L, sandbox_bytecode, sizeof(sandbox_bytecode), "init") != 0) {
+ fprintf(stderr, "[system] error %s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ return kr_error(ENOEXEC);
+ }
+ return kr_ok();
+}
+
+int engine_loadconf(struct engine *engine, const char *config_path)
+{
+ assert(config_path != NULL);
+ int ret = l_dosandboxfile(engine->L, config_path);
+ if (ret != 0) {
+ fprintf(stderr, "%s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ }
+ return ret;
+}
+
+int engine_load_defaults(struct engine *engine)
+{
+ /* Load defaults */
+ static const char config_bytecode[] = {
+ #include "daemon/lua/config.inc"
+ };
+ int ret = l_dobytecode(engine->L, config_bytecode, sizeof(config_bytecode), "config");
+ if (ret != 0) {
+ fprintf(stderr, "%s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ }
+ return ret;
+}
+
+int engine_start(struct engine *engine)
+{
+ /* Clean up stack and restart GC */
+ lua_settop(engine->L, 0);
+ lua_gc(engine->L, LUA_GCCOLLECT, 0);
+ lua_gc(engine->L, LUA_GCSETSTEPMUL, 50);
+ lua_gc(engine->L, LUA_GCSETPAUSE, 400);
+ lua_gc(engine->L, LUA_GCRESTART, 0);
+
+ return kr_ok();
+}
+
+void engine_stop(struct engine *engine)
+{
+ if (!engine) {
+ return;
+ }
+ uv_stop(uv_default_loop());
+}
+
+/** Register module properties in Lua environment, if any. */
+static int register_properties(struct engine *engine, struct kr_module *module)
+{
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wpedantic" /* casts in lua_pushlightuserdata() */
+ if (!module->config && !module->props) {
+ return kr_ok();
+ }
+ lua_newtable(engine->L);
+ if (module->config != NULL) {
+ REGISTER_MODULE_CALL(engine->L, module, module->config, "config");
+ }
+
+ const struct kr_prop *p = module->props == NULL ? NULL : module->props();
+ for (; p && p->name; ++p) {
+ if (p->cb != NULL) {
+ REGISTER_MODULE_CALL(engine->L, module, p->cb, p->name);
+ }
+ }
+ lua_setglobal(engine->L, module->name);
+ #pragma GCC diagnostic pop
+
+ /* Register module in Lua env */
+ lua_getglobal(engine->L, "modules_register");
+ lua_getglobal(engine->L, module->name);
+ if (engine_pcall(engine->L, 1) != 0) {
+ lua_pop(engine->L, 1);
+ }
+
+ return kr_ok();
+}
+
+/** @internal Find matching module */
+static size_t module_find(module_array_t *mod_list, const char *name)
+{
+ size_t found = mod_list->len;
+ for (size_t i = 0; i < mod_list->len; ++i) {
+ struct kr_module *mod = mod_list->at[i];
+ if (strcmp(mod->name, name) == 0) {
+ found = i;
+ break;
+ }
+ }
+ return found;
+}
+
+int engine_register(struct engine *engine, const char *name, const char *precedence, const char* ref)
+{
+ if (engine == NULL || name == NULL) {
+ return kr_error(EINVAL);
+ }
+ /* Make sure module is unloaded */
+ (void) engine_unregister(engine, name);
+ /* Find the index of referenced module. */
+ module_array_t *mod_list = &engine->modules;
+ size_t ref_pos = mod_list->len;
+ if (precedence && ref) {
+ ref_pos = module_find(mod_list, ref);
+ if (ref_pos >= mod_list->len) {
+ return kr_error(EIDRM);
+ }
+ }
+ /* Attempt to load binary module */
+ struct kr_module *module = malloc(sizeof(*module));
+ if (!module) {
+ return kr_error(ENOMEM);
+ }
+ module->data = engine;
+ int ret = kr_module_load(module, name, engine->moduledir);
+ /* Load Lua module if not a binary */
+ if (ret == kr_error(ENOENT)) {
+ ret = ffimodule_register_lua(engine, module, name);
+ } else if (ret == kr_error(ENOTSUP)) {
+ /* Print a more helpful message when module is linked against an old resolver ABI. */
+ fprintf(stderr, "[system] module '%s' links to unsupported ABI, please rebuild it\n", name);
+ }
+ if (ret != 0) {
+ free(module);
+ return ret;
+ }
+ if (array_push(engine->modules, module) < 0) {
+ engine_unload(engine, module);
+ return kr_error(ENOMEM);
+ }
+ /* Evaluate precedence operator */
+ if (precedence) {
+ struct kr_module **arr = mod_list->at;
+ size_t emplacement = mod_list->len;
+ if (strcasecmp(precedence, ">") == 0) {
+ if (ref_pos + 1 < mod_list->len)
+ emplacement = ref_pos + 1; /* Insert after target */
+ }
+ if (strcasecmp(precedence, "<") == 0) {
+ emplacement = ref_pos; /* Insert at target */
+ }
+ /* Move the tail if it has some elements. */
+ if (emplacement + 1 < mod_list->len) {
+ memmove(&arr[emplacement + 1], &arr[emplacement], sizeof(*arr) * (mod_list->len - (emplacement + 1)));
+ arr[emplacement] = module;
+ }
+ }
+
+ return register_properties(engine, module);
+}
+
+int engine_unregister(struct engine *engine, const char *name)
+{
+ module_array_t *mod_list = &engine->modules;
+ size_t found = module_find(mod_list, name);
+ if (found < mod_list->len) {
+ engine_unload(engine, mod_list->at[found]);
+ array_del(*mod_list, found);
+ return kr_ok();
+ }
+
+ return kr_error(ENOENT);
+}
+
+void engine_lualib(struct engine *engine, const char *name, lua_CFunction lib_cb)
+{
+ if (engine != NULL) {
+#if LUA_VERSION_NUM >= 502
+ luaL_requiref(engine->L, name, lib_cb, 1);
+ lua_pop(engine->L, 1);
+#else
+ lib_cb(engine->L);
+#endif
+ }
+}
+
+struct engine *engine_luaget(lua_State *L)
+{
+ lua_getglobal(L, "__engine");
+ struct engine *engine = lua_touserdata(L, -1);
+ if (!engine) luaL_error(L, "internal error, empty engine pointer");
+ lua_pop(L, 1);
+ return engine;
+}
diff --git a/daemon/engine.h b/daemon/engine.h
new file mode 100644
index 0000000..b79991a
--- /dev/null
+++ b/daemon/engine.h
@@ -0,0 +1,89 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+/*
+ * @internal These are forward decls to allow building modules with engine but without Lua.
+ */
+struct lua_State;
+
+#include "lib/utils.h"
+#include "lib/resolve.h"
+#include "daemon/network.h"
+
+/* @internal Array of file descriptors shorthand. */
+typedef array_t(int) fd_array_t;
+
+struct engine {
+ struct kr_context resolver;
+ struct network net;
+ module_array_t modules;
+ array_t(const struct kr_cdb_api *) backends;
+ fd_array_t ipc_set;
+ knot_mm_t *pool;
+ char *hostname;
+ struct lua_State *L;
+ char *moduledir;
+};
+
+int engine_init(struct engine *engine, knot_mm_t *pool);
+void engine_deinit(struct engine *engine);
+
+/** Perform a lua command within the sandbox.
+ *
+ * @return zero on success.
+ * The result will be returned on the lua stack - an error message in case of failure.
+ * http://www.lua.org/manual/5.1/manual.html#lua_pcall */
+int engine_cmd(struct lua_State *L, const char *str, bool raw);
+
+/** Execute current chunk in the sandbox */
+int engine_pcall(struct lua_State *L, int argc);
+
+int engine_ipc(struct engine *engine, const char *expr);
+
+
+int engine_load_sandbox(struct engine *engine);
+int engine_loadconf(struct engine *engine, const char *config_path);
+int engine_load_defaults(struct engine *engine);
+
+/** Start the lua engine and execute the config. */
+int engine_start(struct engine *engine);
+void engine_stop(struct engine *engine);
+int engine_register(struct engine *engine, const char *name, const char *precedence, const char* ref);
+int engine_unregister(struct engine *engine, const char *name);
+void engine_lualib(struct engine *engine, const char *name, int (*lib_cb) (struct lua_State *));
+
+
+/** Return engine light userdata. */
+struct engine *engine_luaget(struct lua_State *L);
+
+/** Set/get the per engine hostname */
+char *engine_get_hostname(struct engine *engine);
+int engine_set_hostname(struct engine *engine, const char *hostname);
+
+/** Set/get the per engine moduledir */
+char *engine_get_moduledir(struct engine *engine);
+int engine_set_moduledir(struct engine *engine, const char *moduledir);
+
+/** Load root hints from a zonefile (or config-time default if NULL).
+ *
+ * @return error message or NULL (statically allocated)
+ * @note exported to be usable from the hints module.
+ */
+KR_EXPORT
+const char* engine_hint_root_file(struct kr_context *ctx, const char *file);
+
diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c
new file mode 100644
index 0000000..1cb6b71
--- /dev/null
+++ b/daemon/ffimodule.c
@@ -0,0 +1,289 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <uv.h>
+
+#include "daemon/engine.h"
+#include "daemon/ffimodule.h"
+#include "daemon/bindings.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+#if LUA_VERSION_NUM >= 502
+#define l_resume(L, argc) lua_resume((L), NULL, (argc))
+#else
+#define l_resume(L, argc) lua_resume((L), (argc))
+#endif
+
+/** @internal Slots for layer callbacks.
+ * Each slot ID corresponds to Lua reference in module API. */
+enum {
+ SLOT_begin = 0,
+ SLOT_reset,
+ SLOT_finish,
+ SLOT_consume,
+ SLOT_produce,
+ SLOT_checkout,
+ SLOT_answer_finalize,
+ SLOT_count /* dummy, must be the last */
+};
+#define SLOT_size sizeof(int)
+
+/** @internal Helper for retrieving the right function entrypoint. */
+static inline lua_State *l_ffi_preface(struct kr_module *module, const char *call) {
+ lua_State *L = module->lib;
+ lua_getglobal(L, module->name);
+ lua_getfield(L, -1, call);
+ lua_remove(L, -2);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return NULL;
+ }
+ lua_pushlightuserdata(L, module);
+ return L;
+}
+
+/** @internal Continue with coroutine. */
+static void l_ffi_resume_cb(uv_idle_t *check)
+{
+ lua_State *L = check->data;
+ int status = l_resume(L, 0);
+ if (status != LUA_YIELD) {
+ uv_idle_stop(check); /* Stop coroutine */
+ uv_close((uv_handle_t *)check, (uv_close_cb)free);
+ }
+ lua_pop(L, lua_gettop(L));
+}
+
+/** @internal Schedule deferred continuation. */
+static int l_ffi_defer(lua_State *L)
+{
+ uv_idle_t *check = malloc(sizeof(*check));
+ if (!check) {
+ return kr_error(ENOMEM);
+ }
+ uv_idle_init(uv_default_loop(), check);
+ check->data = L;
+ return uv_idle_start(check, l_ffi_resume_cb);
+}
+
+/** @internal Helper for calling the entrypoint. */
+static inline int l_ffi_call(lua_State *L, int argc)
+{
+ int status = lua_pcall(L, argc, 1, 0);
+ if (status != 0) {
+ fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ return kr_error(EIO);
+ }
+ if (lua_isnumber(L, -1)) { /* Return code */
+ status = lua_tonumber(L, -1);
+ } else if (lua_isthread(L, -1)) { /* Continuations */
+ status = l_ffi_defer(lua_tothread(L, -1));
+ }
+ lua_pop(L, 1);
+ return status;
+}
+
+static int l_ffi_init(struct kr_module *module)
+{
+ lua_State *L = l_ffi_preface(module, "init");
+ if (!L) {
+ return 0;
+ }
+ return l_ffi_call(L, 1);
+}
+
+static int l_ffi_deinit(struct kr_module *module)
+{
+ /* Deinit the module in Lua (if possible) */
+ int ret = 0;
+ lua_State *L = module->lib;
+ if (l_ffi_preface(module, "deinit")) {
+ ret = l_ffi_call(L, 1);
+ }
+ module->lib = NULL;
+ /* Free the layer API wrapper (unconst it) */
+ kr_layer_api_t* api = module->data;
+ if (!api) {
+ return ret;
+ }
+ /* Unregister layer callback references from registry. */
+ for (int si = 0; si < SLOT_count; ++si) {
+ if (api->cb_slots[si] > 0) {
+ luaL_unref(L, LUA_REGISTRYINDEX, api->cb_slots[si]);
+ }
+ }
+ free(api);
+ return ret;
+}
+
+/** @internal Helper for retrieving layer Lua function by name. */
+#define LAYER_FFI_CALL(ctx, slot_name) \
+ const int *cb_slot = (ctx)->api->cb_slots + SLOT_ ## slot_name; \
+ if (*cb_slot <= 0) { \
+ return ctx->state; \
+ } \
+ struct kr_module *module = (ctx)->api->data; \
+ lua_State *L = module->lib; \
+ lua_rawgeti(L, LUA_REGISTRYINDEX, *cb_slot); \
+ lua_pushnumber(L, ctx->state)
+
+static int l_ffi_layer_begin(kr_layer_t *ctx)
+{
+ LAYER_FFI_CALL(ctx, begin);
+ lua_pushlightuserdata(L, ctx->req);
+ return l_ffi_call(L, 2);
+}
+
+static int l_ffi_layer_reset(kr_layer_t *ctx)
+{
+ LAYER_FFI_CALL(ctx, reset);
+ lua_pushlightuserdata(L, ctx->req);
+ return l_ffi_call(L, 2);
+}
+
+static int l_ffi_layer_finish(kr_layer_t *ctx)
+{
+ struct kr_request *req = ctx->req;
+ LAYER_FFI_CALL(ctx, finish);
+ lua_pushlightuserdata(L, req);
+ lua_pushlightuserdata(L, req->answer);
+ return l_ffi_call(L, 3);
+}
+
+static int l_ffi_layer_consume(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ if (ctx->state & KR_STATE_FAIL) {
+ return ctx->state; /* Already failed, skip */
+ }
+ LAYER_FFI_CALL(ctx, consume);
+ lua_pushlightuserdata(L, ctx->req);
+ lua_pushlightuserdata(L, pkt);
+ return l_ffi_call(L, 3);
+}
+
+static int l_ffi_layer_produce(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ if (ctx->state & (KR_STATE_FAIL)) {
+ return ctx->state; /* Already failed or done, skip */
+ }
+ LAYER_FFI_CALL(ctx, produce);
+ lua_pushlightuserdata(L, ctx->req);
+ lua_pushlightuserdata(L, pkt);
+ return l_ffi_call(L, 3);
+}
+
+static int l_ffi_layer_checkout(kr_layer_t *ctx, knot_pkt_t *pkt, struct sockaddr *dst, int type)
+{
+ if (ctx->state & (KR_STATE_FAIL)) {
+ return ctx->state; /* Already failed or done, skip */
+ }
+ LAYER_FFI_CALL(ctx, checkout);
+ lua_pushlightuserdata(L, ctx->req);
+ lua_pushlightuserdata(L, pkt);
+ lua_pushlightuserdata(L, dst);
+ lua_pushboolean(L, type == SOCK_STREAM);
+ return l_ffi_call(L, 5);
+}
+
+static int l_ffi_layer_answer_finalize(kr_layer_t *ctx)
+{
+ LAYER_FFI_CALL(ctx, answer_finalize);
+ lua_pushlightuserdata(L, ctx->req);
+ return l_ffi_call(L, 2);
+}
+#undef LAYER_FFI_CALL
+
+/** @internal Conditionally register layer trampoline
+ * @warning Expects 'module.layer' to be on top of Lua stack. */
+#define LAYER_REGISTER(L, api, name) do { \
+ int *cb_slot = (api)->cb_slots + SLOT_ ## name; \
+ lua_getfield((L), -1, #name); \
+ if (!lua_isnil((L), -1)) { \
+ (api)->name = l_ffi_layer_ ## name; \
+ *cb_slot = luaL_ref((L), LUA_REGISTRYINDEX); \
+ } else { \
+ lua_pop((L), 1); \
+ } \
+} while(0)
+
+/** @internal Create C layer api wrapper. */
+static kr_layer_api_t *l_ffi_layer_create(lua_State *L, struct kr_module *module)
+{
+ /* Fabricate layer API wrapping the Lua functions
+ * reserve slots after it for references to Lua callbacks. */
+ const size_t api_length = offsetof(kr_layer_api_t, cb_slots)
+ + (SLOT_count * SLOT_size);
+ kr_layer_api_t *api = malloc(api_length);
+ if (api) {
+ memset(api, 0, api_length);
+ LAYER_REGISTER(L, api, begin);
+ LAYER_REGISTER(L, api, finish);
+ LAYER_REGISTER(L, api, consume);
+ LAYER_REGISTER(L, api, produce);
+ LAYER_REGISTER(L, api, checkout);
+ LAYER_REGISTER(L, api, answer_finalize);
+ LAYER_REGISTER(L, api, reset);
+ /* Begin is always set, as it initializes layer baton. */
+ api->begin = l_ffi_layer_begin;
+ api->data = module;
+ }
+ return api;
+}
+
+/** @internal Retrieve C layer api wrapper. */
+static const kr_layer_api_t *l_ffi_layer(struct kr_module *module)
+{
+ if (module) {
+ return (const kr_layer_api_t *)module->data;
+ }
+ return NULL;
+}
+#undef LAYER_REGISTER
+
+int ffimodule_register_lua(struct engine *engine, struct kr_module *module, const char *name)
+{
+ /* Register module in Lua */
+ lua_State *L = engine->L;
+ lua_getglobal(L, "require");
+ lua_pushstring(L, name);
+ if (lua_pcall(L, 1, LUA_MULTRET, 0) != 0) {
+ fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ return kr_error(ENOENT);
+ }
+ lua_setglobal(L, name);
+ lua_getglobal(L, name);
+
+ /* Create FFI module with trampolined functions. */
+ memset(module, 0, sizeof(*module));
+ module->name = strdup(name);
+ module->init = &l_ffi_init;
+ module->deinit = &l_ffi_deinit;
+ /* Bake layer API if defined in module */
+ lua_getfield(L, -1, "layer");
+ if (!lua_isnil(L, -1)) {
+ module->layer = &l_ffi_layer;
+ module->data = l_ffi_layer_create(L, module);
+ }
+ module->lib = L;
+ lua_pop(L, 2); /* Clear the layer + module global */
+ if (module->init) {
+ return module->init(module);
+ }
+ return kr_ok();
+}
diff --git a/daemon/ffimodule.h b/daemon/ffimodule.h
new file mode 100644
index 0000000..92298f5
--- /dev/null
+++ b/daemon/ffimodule.h
@@ -0,0 +1,33 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+/**
+ * Register Lua module as a FFI module.
+ * This fabricates a standard module interface,
+ * that trampolines to the Lua module methods.
+ *
+ * @note Lua module is loaded in it's own coroutine,
+ * so it's possible to yield and resume at arbitrary
+ * places except deinit()
+ *
+ * @param engine daemon engine
+ * @param module prepared module
+ * @param name module name
+ * @return 0 or an error
+ */
+int ffimodule_register_lua(struct engine *engine, struct kr_module *module, const char *name);
diff --git a/daemon/io.c b/daemon/io.c
new file mode 100644
index 0000000..f183d34
--- /dev/null
+++ b/daemon/io.c
@@ -0,0 +1,494 @@
+/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <libknot/errcode.h>
+#include <contrib/ucw/lib.h>
+#include <contrib/ucw/mempool.h>
+#include <assert.h>
+
+#include "daemon/io.h"
+#include "daemon/network.h"
+#include "daemon/worker.h"
+#include "daemon/tls.h"
+#include "daemon/session.h"
+
+#define negotiate_bufsize(func, handle, bufsize_want) do { \
+ int bufsize = 0; func(handle, &bufsize); \
+ if (bufsize < bufsize_want) { \
+ bufsize = bufsize_want; \
+ func(handle, &bufsize); \
+ } \
+} while (0)
+
+static void check_bufsize(uv_handle_t* handle)
+{
+ return; /* TODO: resurrect after https://github.com/libuv/libuv/issues/419 */
+ /* We want to buffer at least N waves in advance.
+ * This is magic presuming we can pull in a whole recvmmsg width in one wave.
+ * Linux will double this the bufsize wanted.
+ */
+ const int bufsize_want = 2 * sizeof( ((struct worker_ctx *)NULL)->wire_buf ) ;
+ negotiate_bufsize(uv_recv_buffer_size, handle, bufsize_want);
+ negotiate_bufsize(uv_send_buffer_size, handle, bufsize_want);
+}
+
+#undef negotiate_bufsize
+
+static void handle_getbuf(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
+{
+ /* UDP sessions use worker buffer for wire data,
+ * TCP sessions use session buffer for wire data
+ * (see session_set_handle()).
+ * TLS sessions use buffer from TLS context.
+ * The content of the worker buffer is
+ * guaranteed to be unchanged only for the duration of
+ * udp_read() and tcp_read().
+ */
+ struct session *s = handle->data;
+ if (!session_flags(s)->has_tls) {
+ buf->base = (char *) session_wirebuf_get_free_start(s);
+ buf->len = session_wirebuf_get_free_size(s);
+ } else {
+ struct tls_common_ctx *ctx = session_tls_get_common_ctx(s);
+ buf->base = (char *) ctx->recv_buf;
+ buf->len = sizeof(ctx->recv_buf);
+ }
+}
+
+void udp_recv(uv_udp_t *handle, ssize_t nread, const uv_buf_t *buf,
+ const struct sockaddr *addr, unsigned flags)
+{
+ uv_loop_t *loop = handle->loop;
+ struct worker_ctx *worker = loop->data;
+ struct session *s = handle->data;
+ if (session_flags(s)->closing) {
+ return;
+ }
+ if (nread <= 0) {
+ if (nread < 0) { /* Error response, notify resolver */
+ worker_submit(s, NULL);
+ } /* nread == 0 is for freeing buffers, we don't need to do this */
+ return;
+ }
+ if (addr->sa_family == AF_UNSPEC) {
+ return;
+ }
+ struct sockaddr *peer = session_get_peer(s);
+ if (session_flags(s)->outgoing) {
+ assert(peer->sa_family != AF_UNSPEC);
+ if (kr_sockaddr_cmp(peer, addr) != 0) {
+ return;
+ }
+ } else {
+ memcpy(peer, addr, kr_sockaddr_len(addr));
+ }
+ ssize_t consumed = session_wirebuf_consume(s, (const uint8_t *)buf->base,
+ nread);
+ assert(consumed == nread); (void)consumed;
+ session_wirebuf_process(s);
+ session_wirebuf_discard(s);
+ mp_flush(worker->pkt_pool.ctx);
+}
+
+static int udp_bind_finalize(uv_handle_t *handle)
+{
+ check_bufsize(handle);
+ /* Handle is already created, just create context. */
+ struct session *s = session_new(handle, false);
+ assert(s);
+ session_flags(s)->outgoing = false;
+ return io_start_read(handle);
+}
+
+int udp_bind(uv_udp_t *handle, struct sockaddr *addr)
+{
+ unsigned flags = UV_UDP_REUSEADDR;
+ if (addr->sa_family == AF_INET6) {
+ flags |= UV_UDP_IPV6ONLY;
+ }
+ int ret = uv_udp_bind(handle, addr, flags);
+ if (ret != 0) {
+ return ret;
+ }
+ return udp_bind_finalize((uv_handle_t *)handle);
+}
+
+int udp_bindfd(uv_udp_t *handle, int fd)
+{
+ if (!handle) {
+ return kr_error(EINVAL);
+ }
+
+ int ret = uv_udp_open(handle, (uv_os_sock_t) fd);
+ if (ret != 0) {
+ return ret;
+ }
+ return udp_bind_finalize((uv_handle_t *)handle);
+}
+
+void tcp_timeout_trigger(uv_timer_t *timer)
+{
+ struct session *s = timer->data;
+
+ assert(!session_flags(s)->closing);
+
+ struct worker_ctx *worker = timer->loop->data;
+
+ if (!session_tasklist_is_empty(s)) {
+ int finalized = session_tasklist_finalize_expired(s);
+ worker->stats.timeout += finalized;
+ /* session_tasklist_finalize_expired() may call worker_task_finalize().
+ * If session is a source session and there were IO errors,
+ * worker_task_finalize() can filnalize all tasks and close session. */
+ if (session_flags(s)->closing) {
+ return;
+ }
+
+ }
+ if (!session_tasklist_is_empty(s)) {
+ uv_timer_stop(timer);
+ session_timer_start(s, tcp_timeout_trigger,
+ KR_RESOLVE_TIME_LIMIT / 2,
+ KR_RESOLVE_TIME_LIMIT / 2);
+ } else {
+ /* Normally it should not happen,
+ * but better to check if there anything in this list. */
+ while (!session_waitinglist_is_empty(s)) {
+ struct qr_task *t = session_waitinglist_pop(s, false);
+ worker_task_finalize(t, KR_STATE_FAIL);
+ worker_task_unref(t);
+ worker->stats.timeout += 1;
+ if (session_flags(s)->closing) {
+ return;
+ }
+ }
+ const struct engine *engine = worker->engine;
+ const struct network *net = &engine->net;
+ uint64_t idle_in_timeout = net->tcp.in_idle_timeout;
+ uint64_t last_activity = session_last_activity(s);
+ uint64_t idle_time = kr_now() - last_activity;
+ if (idle_time < idle_in_timeout) {
+ idle_in_timeout -= idle_time;
+ uv_timer_stop(timer);
+ session_timer_start(s, tcp_timeout_trigger,
+ idle_in_timeout, idle_in_timeout);
+ } else {
+ struct sockaddr *peer = session_get_peer(s);
+ char *peer_str = kr_straddr(peer);
+ kr_log_verbose("[io] => closing connection to '%s'\n",
+ peer_str ? peer_str : "");
+ if (session_flags(s)->outgoing) {
+ worker_del_tcp_waiting(worker, peer);
+ worker_del_tcp_connected(worker, peer);
+ }
+ session_close(s);
+ }
+ }
+}
+
+static void tcp_recv(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf)
+{
+ struct session *s = handle->data;
+ assert(s && session_get_handle(s) == (uv_handle_t *)handle &&
+ handle->type == UV_TCP);
+
+ if (session_flags(s)->closing) {
+ return;
+ }
+
+ /* nread might be 0, which does not indicate an error or EOF.
+ * This is equivalent to EAGAIN or EWOULDBLOCK under read(2). */
+ if (nread == 0) {
+ return;
+ }
+
+ if (nread < 0 || !buf->base) {
+ if (kr_verbose_status) {
+ struct sockaddr *peer = session_get_peer(s);
+ char *peer_str = kr_straddr(peer);
+ kr_log_verbose("[io] => connection to '%s' closed by peer (%s)\n",
+ peer_str ? peer_str : "",
+ uv_strerror(nread));
+ }
+ worker_end_tcp(s);
+ return;
+ }
+
+ ssize_t consumed = 0;
+ const uint8_t *data = (const uint8_t *)buf->base;
+ ssize_t data_len = nread;
+ if (session_flags(s)->has_tls) {
+ /* buf->base points to start of the tls receive buffer.
+ Decode data free space in session wire buffer. */
+ consumed = tls_process_input_data(s, (const uint8_t *)buf->base, nread);
+ if (consumed < 0) {
+ if (kr_verbose_status) {
+ struct sockaddr *peer = session_get_peer(s);
+ char *peer_str = kr_straddr(peer);
+ kr_log_verbose("[io] => connection to '%s': "
+ "error processing TLS data, close\n",
+ peer_str ? peer_str : "");
+ }
+ worker_end_tcp(s);
+ return;
+ } else if (consumed == 0) {
+ return;
+ }
+ data = session_wirebuf_get_free_start(s);
+ data_len = consumed;
+ }
+
+ /* data points to start of the free space in session wire buffer.
+ Simple increase internal counter. */
+ consumed = session_wirebuf_consume(s, data, data_len);
+ assert(consumed == data_len);
+
+ int ret = session_wirebuf_process(s);
+ if (ret < 0) {
+ /* An error has occurred, close the session. */
+ worker_end_tcp(s);
+ }
+ session_wirebuf_compress(s);
+ struct worker_ctx *worker = handle->loop->data;
+ mp_flush(worker->pkt_pool.ctx);
+}
+
+static void _tcp_accept(uv_stream_t *master, int status, bool tls)
+{
+ if (status != 0) {
+ return;
+ }
+
+ struct worker_ctx *worker = (struct worker_ctx *)master->loop->data;
+ uv_tcp_t *client = malloc(sizeof(uv_tcp_t));
+ if (!client) {
+ return;
+ }
+ int res = io_create(master->loop, (uv_handle_t *)client,
+ SOCK_STREAM, AF_UNSPEC, tls);
+ if (res) {
+ if (res == UV_EMFILE) {
+ worker->too_many_open = true;
+ worker->rconcurrent_highwatermark = worker->stats.rconcurrent;
+ }
+ /* Since res isn't OK struct session wasn't allocated \ borrowed.
+ * We must release client handle only.
+ */
+ free(client);
+ return;
+ }
+
+ /* struct session was allocated \ borrowed from memory pool. */
+ struct session *s = client->data;
+ assert(session_flags(s)->outgoing == false);
+ assert(session_flags(s)->has_tls == tls);
+
+ if (uv_accept(master, (uv_stream_t *)client) != 0) {
+ /* close session, close underlying uv handles and
+ * deallocate (or return to memory pool) memory. */
+ session_close(s);
+ return;
+ }
+
+ /* Set deadlines for TCP connection and start reading.
+ * It will re-check every half of a request time limit if the connection
+ * is idle and should be terminated, this is an educated guess. */
+ struct sockaddr *peer = session_get_peer(s);
+ int peer_len = sizeof(union inaddr);
+ int ret = uv_tcp_getpeername(client, peer, &peer_len);
+ if (ret || peer->sa_family == AF_UNSPEC) {
+ session_close(s);
+ return;
+ }
+
+ const struct engine *engine = worker->engine;
+ const struct network *net = &engine->net;
+ uint64_t idle_in_timeout = net->tcp.in_idle_timeout;
+
+ uint64_t timeout = KR_CONN_RTT_MAX / 2;
+ if (tls) {
+ timeout += TLS_MAX_HANDSHAKE_TIME;
+ struct tls_ctx_t *ctx = session_tls_get_server_ctx(s);
+ if (!ctx) {
+ ctx = tls_new(worker);
+ if (!ctx) {
+ session_close(s);
+ return;
+ }
+ ctx->c.session = s;
+ ctx->c.handshake_state = TLS_HS_IN_PROGRESS;
+ session_tls_set_server_ctx(s, ctx);
+ }
+ }
+ session_timer_start(s, tcp_timeout_trigger, timeout, idle_in_timeout);
+ io_start_read((uv_handle_t *)client);
+}
+
+static void tcp_accept(uv_stream_t *master, int status)
+{
+ _tcp_accept(master, status, false);
+}
+
+static void tls_accept(uv_stream_t *master, int status)
+{
+ _tcp_accept(master, status, true);
+}
+
+static int set_tcp_option(uv_handle_t *handle, int option, int val)
+{
+ uv_os_fd_t fd = 0;
+ if (uv_fileno(handle, &fd) == 0) {
+ return setsockopt(fd, IPPROTO_TCP, option, &val, sizeof(val));
+ }
+ return 0; /* N/A */
+}
+
+static int tcp_bind_finalize(uv_handle_t *handle)
+{
+ /* TCP_FASTOPEN enables 1 RTT connection resumptions. */
+#ifdef TCP_FASTOPEN
+# ifdef __linux__
+ (void) set_tcp_option(handle, TCP_FASTOPEN, 16); /* Accepts queue length hint */
+# else
+ (void) set_tcp_option(handle, TCP_FASTOPEN, 1); /* Accepts on/off */
+# endif
+#endif
+
+ handle->data = NULL;
+ return 0;
+}
+
+static int _tcp_bind(uv_tcp_t *handle, struct sockaddr *addr, uv_connection_cb connection, int tcp_backlog)
+{
+ unsigned flags = 0;
+ if (addr->sa_family == AF_INET6) {
+ flags |= UV_TCP_IPV6ONLY;
+ }
+
+ int ret = uv_tcp_bind(handle, addr, flags);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* TCP_DEFER_ACCEPT delays accepting connections until there is readable data. */
+#ifdef TCP_DEFER_ACCEPT
+ if (set_tcp_option((uv_handle_t *)handle, TCP_DEFER_ACCEPT, KR_CONN_RTT_MAX/1000) != 0) {
+ kr_log_info("[ io ] tcp_bind (defer_accept): %s\n", strerror(errno));
+ }
+#endif
+
+ ret = uv_listen((uv_stream_t *)handle, tcp_backlog, connection);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return tcp_bind_finalize((uv_handle_t *)handle);
+}
+
+int tcp_bind(uv_tcp_t *handle, struct sockaddr *addr, int tcp_backlog)
+{
+ return _tcp_bind(handle, addr, tcp_accept, tcp_backlog);
+}
+
+int tcp_bind_tls(uv_tcp_t *handle, struct sockaddr *addr, int tcp_backlog)
+{
+ return _tcp_bind(handle, addr, tls_accept, tcp_backlog);
+}
+
+static int _tcp_bindfd(uv_tcp_t *handle, int fd, uv_connection_cb connection, int tcp_backlog)
+{
+ if (!handle) {
+ return kr_error(EINVAL);
+ }
+
+ int ret = uv_tcp_open(handle, (uv_os_sock_t) fd);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = uv_listen((uv_stream_t *)handle, tcp_backlog, connection);
+ if (ret != 0) {
+ return ret;
+ }
+ return tcp_bind_finalize((uv_handle_t *)handle);
+}
+
+int tcp_bindfd(uv_tcp_t *handle, int fd, int tcp_backlog)
+{
+ return _tcp_bindfd(handle, fd, tcp_accept, tcp_backlog);
+}
+
+int tcp_bindfd_tls(uv_tcp_t *handle, int fd, int tcp_backlog)
+{
+ return _tcp_bindfd(handle, fd, tls_accept, tcp_backlog);
+}
+
+int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, unsigned family, bool has_tls)
+{
+ int ret = -1;
+ if (type == SOCK_DGRAM) {
+ ret = uv_udp_init(loop, (uv_udp_t *)handle);
+ } else if (type == SOCK_STREAM) {
+ ret = uv_tcp_init_ex(loop, (uv_tcp_t *)handle, family);
+ uv_tcp_nodelay((uv_tcp_t *)handle, 1);
+ }
+ if (ret != 0) {
+ return ret;
+ }
+ struct session *s = session_new(handle, has_tls);
+ if (s == NULL) {
+ ret = -1;
+ }
+ return ret;
+}
+
+void io_deinit(uv_handle_t *handle)
+{
+ if (!handle) {
+ return;
+ }
+ session_free(handle->data);
+ handle->data = NULL;
+}
+
+void io_free(uv_handle_t *handle)
+{
+ io_deinit(handle);
+ free(handle);
+}
+
+int io_start_read(uv_handle_t *handle)
+{
+ switch (handle->type) {
+ case UV_UDP:
+ return uv_udp_recv_start((uv_udp_t *)handle, &handle_getbuf, &udp_recv);
+ case UV_TCP:
+ return uv_read_start((uv_stream_t *)handle, &handle_getbuf, &tcp_recv);
+ default:
+ assert(!EINVAL);
+ return kr_error(EINVAL);
+ }
+}
+
+int io_stop_read(uv_handle_t *handle)
+{
+ if (handle->type == UV_UDP) {
+ return uv_udp_recv_stop((uv_udp_t *)handle);
+ } else {
+ return uv_read_stop((uv_stream_t *)handle);
+ }
+}
diff --git a/daemon/io.h b/daemon/io.h
new file mode 100644
index 0000000..1d41569
--- /dev/null
+++ b/daemon/io.h
@@ -0,0 +1,46 @@
+/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <uv.h>
+#include <libknot/packet/pkt.h>
+#include <gnutls/gnutls.h>
+#include "lib/generic/array.h"
+#include "daemon/worker.h"
+
+struct tls_ctx_t;
+struct tls_client_ctx_t;
+
+int udp_bind(uv_udp_t *handle, struct sockaddr *addr);
+int udp_bindfd(uv_udp_t *handle, int fd);
+int tcp_bind(uv_tcp_t *handle, struct sockaddr *addr, int tcp_backlog);
+int tcp_bind_tls(uv_tcp_t *handle, struct sockaddr *addr, int tcp_backlog);
+int tcp_bindfd(uv_tcp_t *handle, int fd, int tcp_backlog);
+int tcp_bindfd_tls(uv_tcp_t *handle, int fd, int tcp_backlog);
+void tcp_timeout_trigger(uv_timer_t *timer);
+
+/** Initialize the handle, incl. ->data = struct session * instance.
+ * \param type = SOCK_*
+ * \param family = AF_*
+ * \param has_tls has meanings only when type is SOCK_STREAM */
+int io_create(uv_loop_t *loop, uv_handle_t *handle, int type,
+ unsigned family, bool has_tls);
+void io_deinit(uv_handle_t *handle);
+void io_free(uv_handle_t *handle);
+
+int io_start_read(uv_handle_t *handle);
+int io_stop_read(uv_handle_t *handle);
diff --git a/daemon/lua/config.lua b/daemon/lua/config.lua
new file mode 100644
index 0000000..448d6dd
--- /dev/null
+++ b/daemon/lua/config.lua
@@ -0,0 +1,35 @@
+-- Listen on localhost
+if not next(net.list()) and not env.KRESD_NO_LISTEN then
+ local ok, err = pcall(net.listen, '127.0.0.1')
+ if not ok then
+ error('bind to 127.0.0.1@53 '..err)
+ end
+ -- IPv6 loopback may fail
+ ok, err = pcall(net.listen, '::1')
+ if not ok and verbose() then
+ print('bind to ::1@53 '..err)
+ end
+ -- Exit when kresd isn't listening on any interfaces
+ if not next(net.list()) then
+ panic('not listening on any interface, exiting...')
+ end
+end
+-- Open cache if not set/disabled
+if not cache.current_size then
+ cache.size = 100 * MB
+end
+
+-- If no addresses for root servers are set, load them from the default file
+if require('ffi').C.kr_zonecut_is_empty(kres.context().root_hints) then
+ _hint_root_file()
+end
+
+if not trust_anchors.keysets['\0'] and trust_anchors.keyfile_default then
+ if io.open(trust_anchors.keyfile_default, 'r') then
+ trust_anchors.config(trust_anchors.keyfile_default, true)
+ else
+ panic("cannot open default trust anchor file:'%s'",
+ trust_anchors.keyfile_default
+ )
+ end
+end
diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
new file mode 100644
index 0000000..eeb8ff7
--- /dev/null
+++ b/daemon/lua/kres-gen.lua
@@ -0,0 +1,430 @@
+local ffi = require('ffi')
+--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[
+
+typedef struct knot_dump_style knot_dump_style_t;
+extern const knot_dump_style_t KNOT_DUMP_STYLE_DEFAULT;
+typedef void knot_db_t;
+struct kr_cdb_api {};
+struct lru {};
+
+typedef struct knot_mm {
+ void *ctx, *alloc, *free;
+} knot_mm_t;
+
+typedef void *(*map_alloc_f)(void *, size_t);
+typedef void (*map_free_f)(void *baton, void *ptr);
+typedef void (*trace_log_f) (const struct kr_query *, const char *, const char *);
+typedef void (*trace_callback_f)(struct kr_request *);
+typedef enum {KNOT_ANSWER, KNOT_AUTHORITY, KNOT_ADDITIONAL} knot_section_t;
+typedef struct {
+ uint16_t pos;
+ uint16_t flags;
+ uint16_t compress_ptr[16];
+} knot_rrinfo_t;
+typedef unsigned char knot_dname_t;
+typedef struct {
+ uint16_t len;
+ uint8_t data[];
+} knot_rdata_t;
+typedef struct {
+ uint16_t count;
+ knot_rdata_t *rdata;
+} knot_rdataset_t;
+typedef struct {
+ knot_dname_t *_owner;
+ uint32_t _ttl;
+ uint16_t type;
+ uint16_t rclass;
+ knot_rdataset_t rrs;
+ void *additional;
+} knot_rrset_t;
+typedef struct knot_pkt knot_pkt_t;
+typedef struct {
+ uint8_t *ptr[15];
+} knot_edns_options_t;
+typedef struct {
+ knot_pkt_t *pkt;
+ uint16_t pos;
+ uint16_t count;
+} knot_pktsection_t;
+struct knot_compr {
+ uint8_t *wire;
+ knot_rrinfo_t *rrinfo;
+ struct {
+ uint16_t pos;
+ uint8_t labels;
+ } suffix;
+};
+typedef struct knot_compr knot_compr_t;
+struct knot_pkt {
+ uint8_t *wire;
+ size_t size;
+ size_t max_size;
+ size_t parsed;
+ uint16_t reserved;
+ uint16_t qname_size;
+ uint16_t rrset_count;
+ uint16_t flags;
+ knot_rrset_t *opt_rr;
+ knot_rrset_t *tsig_rr;
+ knot_edns_options_t *edns_opts;
+ struct {
+ uint8_t *pos;
+ size_t len;
+ } tsig_wire;
+ knot_section_t current;
+ knot_pktsection_t sections[3];
+ size_t rrset_allocd;
+ knot_rrinfo_t *rr_info;
+ knot_rrset_t *rr;
+ knot_mm_t mm;
+ knot_compr_t compr;
+};
+typedef struct {
+ void *root;
+ struct knot_mm *pool;
+} map_t;
+struct kr_qflags {
+ _Bool NO_MINIMIZE : 1;
+ _Bool NO_THROTTLE : 1;
+ _Bool NO_IPV6 : 1;
+ _Bool NO_IPV4 : 1;
+ _Bool TCP : 1;
+ _Bool RESOLVED : 1;
+ _Bool AWAIT_IPV4 : 1;
+ _Bool AWAIT_IPV6 : 1;
+ _Bool AWAIT_CUT : 1;
+ _Bool SAFEMODE : 1;
+ _Bool CACHED : 1;
+ _Bool NO_CACHE : 1;
+ _Bool EXPIRING : 1;
+ _Bool ALLOW_LOCAL : 1;
+ _Bool DNSSEC_WANT : 1;
+ _Bool DNSSEC_BOGUS : 1;
+ _Bool DNSSEC_INSECURE : 1;
+ _Bool DNSSEC_CD : 1;
+ _Bool STUB : 1;
+ _Bool ALWAYS_CUT : 1;
+ _Bool DNSSEC_WEXPAND : 1;
+ _Bool PERMISSIVE : 1;
+ _Bool STRICT : 1;
+ _Bool BADCOOKIE_AGAIN : 1;
+ _Bool CNAME : 1;
+ _Bool REORDER_RR : 1;
+ _Bool TRACE : 1;
+ _Bool NO_0X20 : 1;
+ _Bool DNSSEC_NODS : 1;
+ _Bool DNSSEC_OPTOUT : 1;
+ _Bool NONAUTH : 1;
+ _Bool FORWARD : 1;
+ _Bool DNS64_MARK : 1;
+ _Bool CACHE_TRIED : 1;
+ _Bool NO_NS_FOUND : 1;
+};
+typedef struct {
+ knot_rrset_t **at;
+ size_t len;
+ size_t cap;
+} rr_array_t;
+struct ranked_rr_array_entry {
+ uint32_t qry_uid;
+ uint8_t rank;
+ uint8_t revalidation_cnt;
+ _Bool cached : 1;
+ _Bool yielded : 1;
+ _Bool to_wire : 1;
+ _Bool expiring : 1;
+ knot_rrset_t *rr;
+};
+typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
+typedef struct {
+ ranked_rr_array_entry_t **at;
+ size_t len;
+ size_t cap;
+} ranked_rr_array_t;
+typedef struct trie trie_t;
+struct kr_zonecut {
+ knot_dname_t *name;
+ knot_rrset_t *key;
+ knot_rrset_t *trust_anchor;
+ struct kr_zonecut *parent;
+ trie_t *nsset;
+ knot_mm_t *pool;
+};
+typedef struct {
+ struct kr_query **at;
+ size_t len;
+ size_t cap;
+} kr_qarray_t;
+struct kr_rplan {
+ kr_qarray_t pending;
+ kr_qarray_t resolved;
+ struct kr_request *request;
+ knot_mm_t *pool;
+ uint32_t next_uid;
+};
+struct kr_request_qsource_flags {
+ _Bool tcp : 1;
+ _Bool tls : 1;
+};
+struct kr_request {
+ struct kr_context *ctx;
+ knot_pkt_t *answer;
+ struct kr_query *current_query;
+ struct {
+ const struct sockaddr *addr;
+ const struct sockaddr *dst_addr;
+ const knot_pkt_t *packet;
+ struct kr_request_qsource_flags flags;
+ size_t size;
+ } qsource;
+ struct {
+ unsigned int rtt;
+ const struct sockaddr *addr;
+ } upstream;
+ struct kr_qflags options;
+ int state;
+ ranked_rr_array_t answ_selected;
+ ranked_rr_array_t auth_selected;
+ ranked_rr_array_t add_selected;
+ rr_array_t additional;
+ _Bool answ_validated;
+ _Bool auth_validated;
+ uint8_t rank;
+ struct kr_rplan rplan;
+ trace_log_f trace_log;
+ trace_callback_f trace_finish;
+ int vars_ref;
+ knot_mm_t pool;
+ unsigned int uid;
+ void *daemon_context;
+};
+enum kr_rank {KR_RANK_INITIAL, KR_RANK_OMIT, KR_RANK_TRY, KR_RANK_INDET = 4, KR_RANK_BOGUS, KR_RANK_MISMATCH, KR_RANK_MISSING, KR_RANK_INSECURE, KR_RANK_AUTH = 16, KR_RANK_SECURE = 32};
+struct kr_cache {
+ knot_db_t *db;
+ const struct kr_cdb_api *api;
+ struct {
+ uint32_t hit;
+ uint32_t miss;
+ uint32_t insert;
+ uint32_t delete;
+ } stats;
+ uint32_t ttl_min;
+ uint32_t ttl_max;
+ struct timeval checkpoint_walltime;
+ uint64_t checkpoint_monotime;
+};
+
+typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type,
+ const struct kr_query *qry);
+
+void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner,
+ uint16_t type, uint16_t rclass, uint32_t ttl);
+struct kr_nsrep {
+ unsigned int score;
+ unsigned int reputation;
+ const knot_dname_t *name;
+ struct kr_context *ctx;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+struct kr_query {
+ struct kr_query *parent;
+ knot_dname_t *sname;
+ uint16_t stype;
+ uint16_t sclass;
+ uint16_t id;
+ struct kr_qflags flags;
+ struct kr_qflags forward_flags;
+ uint32_t secret;
+ uint16_t fails;
+ uint16_t reorder;
+ uint64_t creation_time_mono;
+ uint64_t timestamp_mono;
+ struct timeval timestamp;
+ struct kr_zonecut zone_cut;
+ struct kr_layer_pickle *deferred;
+ uint32_t uid;
+ struct kr_query *cname_parent;
+ struct kr_request *request;
+ kr_stale_cb stale_cb;
+ struct kr_nsrep ns;
+};
+struct kr_context {
+ struct kr_qflags options;
+ knot_rrset_t *opt_rr;
+ map_t trust_anchors;
+ map_t negative_anchors;
+ struct kr_zonecut root_hints;
+ struct kr_cache cache;
+ char _stub[];
+};
+const char *knot_strerror(int);
+knot_dname_t *knot_dname_copy(const knot_dname_t *, knot_mm_t *);
+knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t);
+int knot_dname_in_bailiwick(const knot_dname_t *, const knot_dname_t *);
+_Bool knot_dname_is_equal(const knot_dname_t *, const knot_dname_t *);
+size_t knot_dname_labels(const uint8_t *, const uint8_t *);
+size_t knot_dname_size(const knot_dname_t *);
+void knot_dname_to_lower(knot_dname_t *);
+char *knot_dname_to_str(char *, const knot_dname_t *, size_t);
+knot_rdata_t *knot_rdataset_at(const knot_rdataset_t *, uint16_t);
+int knot_rdataset_merge(knot_rdataset_t *, const knot_rdataset_t *, knot_mm_t *);
+int knot_rrset_add_rdata(knot_rrset_t *, const uint8_t *, uint16_t, knot_mm_t *);
+int knot_rrset_txt_dump(const knot_rrset_t *, char **, size_t *, const knot_dump_style_t *);
+int knot_rrset_txt_dump_data(const knot_rrset_t *, const size_t, char *, const size_t, const knot_dump_style_t *);
+size_t knot_rrset_size(const knot_rrset_t *);
+int knot_pkt_begin(knot_pkt_t *, knot_section_t);
+int knot_pkt_put_question(knot_pkt_t *, const knot_dname_t *, uint16_t, uint16_t);
+int knot_pkt_put_rotate(knot_pkt_t *, uint16_t, const knot_rrset_t *, uint16_t, uint16_t);
+knot_pkt_t *knot_pkt_new(void *, uint16_t, knot_mm_t *);
+void knot_pkt_free(knot_pkt_t *);
+int knot_pkt_parse(knot_pkt_t *, unsigned int);
+struct kr_rplan *kr_resolve_plan(struct kr_request *);
+knot_mm_t *kr_resolve_pool(struct kr_request *);
+struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_dname_t *, uint16_t, uint16_t);
+int kr_rplan_pop(struct kr_rplan *, struct kr_query *);
+struct kr_query *kr_rplan_resolved(struct kr_rplan *);
+struct kr_query *kr_rplan_last(struct kr_rplan *);
+int kr_nsrep_set(struct kr_query *, size_t, const struct sockaddr *);
+int kr_make_query(struct kr_query *, knot_pkt_t *);
+void kr_pkt_make_auth_header(knot_pkt_t *);
+int kr_pkt_put(knot_pkt_t *, const knot_dname_t *, uint32_t, uint16_t, uint16_t, const uint8_t *, uint16_t);
+int kr_pkt_recycle(knot_pkt_t *);
+int kr_pkt_clear_payload(knot_pkt_t *);
+uint16_t kr_pkt_qclass(const knot_pkt_t *);
+uint16_t kr_pkt_qtype(const knot_pkt_t *);
+void kr_rnd_buffered(void *, unsigned int);
+uint32_t kr_rrsig_sig_inception(const knot_rdata_t *);
+uint32_t kr_rrsig_sig_expiration(const knot_rdata_t *);
+uint16_t kr_rrsig_type_covered(const knot_rdata_t *);
+const char *kr_inaddr(const struct sockaddr *);
+int kr_inaddr_family(const struct sockaddr *);
+int kr_inaddr_len(const struct sockaddr *);
+int kr_inaddr_str(const struct sockaddr *, char *, size_t *);
+int kr_sockaddr_len(const struct sockaddr *);
+uint16_t kr_inaddr_port(const struct sockaddr *);
+int kr_straddr_family(const char *);
+int kr_straddr_subnet(void *, const char *);
+int kr_bitcmp(const char *, const char *, int);
+int kr_family_len(int);
+struct sockaddr *kr_straddr_socket(const char *, int);
+int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *);
+void kr_qflags_set(struct kr_qflags *, struct kr_qflags);
+void kr_qflags_clear(struct kr_qflags *, struct kr_qflags);
+int kr_zonecut_add(struct kr_zonecut *, const knot_dname_t *, const void *, int);
+_Bool kr_zonecut_is_empty(struct kr_zonecut *);
+void kr_zonecut_set(struct kr_zonecut *, const knot_dname_t *);
+uint64_t kr_now();
+const char *kr_strptime_diff(const char *, const char *, const char *, double *);
+void lru_free_items_impl(struct lru *);
+struct lru *lru_create_impl(unsigned int, knot_mm_t *, knot_mm_t *);
+void *lru_get_impl(struct lru *, const char *, unsigned int, unsigned int, _Bool, _Bool *);
+void *mm_realloc(knot_mm_t *, void *, size_t, size_t);
+knot_rrset_t *kr_ta_get(map_t *, const knot_dname_t *);
+int kr_ta_add(map_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t);
+int kr_ta_del(map_t *, const knot_dname_t *);
+void kr_ta_clear(map_t *);
+_Bool kr_dnssec_key_ksk(const uint8_t *);
+_Bool kr_dnssec_key_revoked(const uint8_t *);
+int kr_dnssec_key_tag(uint16_t, const uint8_t *, size_t);
+int kr_dnssec_key_match(const uint8_t *, size_t, const uint8_t *, size_t);
+int kr_cache_closest_apex(struct kr_cache *, const knot_dname_t *, _Bool, knot_dname_t **);
+int kr_cache_insert_rr(struct kr_cache *, const knot_rrset_t *, const knot_rrset_t *, uint8_t, uint32_t);
+int kr_cache_remove(struct kr_cache *, const knot_dname_t *, uint16_t);
+int kr_cache_remove_subtree(struct kr_cache *, const knot_dname_t *, _Bool, int);
+int kr_cache_sync(struct kr_cache *);
+typedef struct {
+ uint8_t bitmap[32];
+ uint8_t length;
+} zs_win_t;
+typedef struct {
+ uint8_t excl_flag;
+ uint16_t addr_family;
+ uint8_t prefix_length;
+} zs_apl_t;
+typedef struct {
+ uint32_t d1;
+ uint32_t d2;
+ uint32_t m1;
+ uint32_t m2;
+ uint32_t s1;
+ uint32_t s2;
+ uint32_t alt;
+ uint64_t siz;
+ uint64_t hp;
+ uint64_t vp;
+ int8_t lat_sign;
+ int8_t long_sign;
+ int8_t alt_sign;
+} zs_loc_t;
+typedef enum {ZS_STATE_NONE, ZS_STATE_DATA, ZS_STATE_ERROR, ZS_STATE_INCLUDE, ZS_STATE_EOF, ZS_STATE_STOP} zs_state_t;
+typedef struct zs_scanner zs_scanner_t;
+struct zs_scanner {
+ int cs;
+ int top;
+ int stack[16];
+ _Bool multiline;
+ uint64_t number64;
+ uint64_t number64_tmp;
+ uint32_t decimals;
+ uint32_t decimal_counter;
+ uint32_t item_length;
+ uint32_t item_length_position;
+ uint8_t *item_length_location;
+ uint32_t buffer_length;
+ uint8_t buffer[65535];
+ char include_filename[65535];
+ char *path;
+ zs_win_t windows[256];
+ int16_t last_window;
+ zs_apl_t apl;
+ zs_loc_t loc;
+ uint8_t addr[16];
+ _Bool long_string;
+ uint8_t *dname;
+ uint32_t *dname_length;
+ uint32_t dname_tmp_length;
+ uint32_t r_data_tail;
+ uint32_t zone_origin_length;
+ uint8_t zone_origin[318];
+ uint16_t default_class;
+ uint32_t default_ttl;
+ zs_state_t state;
+ struct {
+ _Bool automatic;
+ void (*record)(zs_scanner_t *);
+ void (*error)(zs_scanner_t *);
+ void *data;
+ } process;
+ struct {
+ const char *start;
+ const char *current;
+ const char *end;
+ _Bool eof;
+ _Bool mmaped;
+ } input;
+ struct {
+ char *name;
+ int descriptor;
+ } file;
+ struct {
+ int code;
+ uint64_t counter;
+ _Bool fatal;
+ } error;
+ uint64_t line_counter;
+ uint32_t r_owner_length;
+ uint8_t r_owner[318];
+ uint16_t r_class;
+ uint32_t r_ttl;
+ uint16_t r_type;
+ uint32_t r_data_length;
+ uint8_t r_data[65535];
+};
+void zs_deinit(zs_scanner_t *);
+int zs_init(zs_scanner_t *, const char *, const uint16_t, const uint32_t);
+int zs_parse_record(zs_scanner_t *);
+int zs_set_input_file(zs_scanner_t *, const char *);
+int zs_set_input_string(zs_scanner_t *, const char *, size_t);
+const char *zs_strerror(const int);
+]]
diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh
new file mode 100755
index 0000000..538fe23
--- /dev/null
+++ b/daemon/lua/kres-gen.sh
@@ -0,0 +1,216 @@
+#!/bin/bash
+set -o pipefail -o errexit
+
+### Dev's guide
+#
+# C declarations for lua are (mostly) generated to simplify maintenance.
+# (Avoid typos, accidental mismatches, etc.)
+#
+# To regenerate the C definitions for lua:
+# - you need to have debugging symbols for knot-dns and knot-resolver;
+# you get those by compiling with -g; for knot-dns it might be enough
+# to just install it with debugging symbols included (in your distro way)
+# - remove file ./kres-gen.lua and run make as usual
+# - the knot-dns libraries are found via pkg-config
+# - you also need gdb on $PATH
+
+
+printf -- "local ffi = require('ffi')\n"
+printf -- "--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[\n"
+
+## Various types (mainly), from libknot and libkres
+
+printf "
+typedef struct knot_dump_style knot_dump_style_t;
+extern const knot_dump_style_t KNOT_DUMP_STYLE_DEFAULT;
+typedef void knot_db_t;
+struct kr_cdb_api {};
+struct lru {};
+"
+
+# The generator doesn't work well with typedefs of functions.
+printf "
+typedef struct knot_mm {
+ void *ctx, *alloc, *free;
+} knot_mm_t;
+
+typedef void *(*map_alloc_f)(void *, size_t);
+typedef void (*map_free_f)(void *baton, void *ptr);
+typedef void (*trace_log_f) (const struct kr_query *, const char *, const char *);
+typedef void (*trace_callback_f)(struct kr_request *);
+"
+
+./scripts/gen-cdefs.sh libkres types <<-EOF
+ knot_section_t
+ knot_rrinfo_t
+ knot_dname_t
+ knot_rdata_t
+ knot_rdataset_t
+EOF
+
+genResType() {
+ echo "$1" | ./scripts/gen-cdefs.sh libkres types
+}
+
+# No simple way to fixup this rename in ./kres.lua AFAIK.
+genResType "knot_rrset_t" | sed 's/\<owner\>/_owner/; s/\<ttl\>/_ttl/'
+
+./scripts/gen-cdefs.sh libkres types <<-EOF
+ knot_pkt_t
+ knot_edns_options_t
+ knot_pktsection_t
+ struct knot_compr
+ knot_compr_t
+ struct knot_pkt
+ # generics
+ map_t
+ # libkres
+ struct kr_qflags
+ rr_array_t
+ struct ranked_rr_array_entry
+ ranked_rr_array_entry_t
+ ranked_rr_array_t
+ trie_t
+ struct kr_zonecut
+ kr_qarray_t
+ struct kr_rplan
+ struct kr_request_qsource_flags
+ struct kr_request
+ enum kr_rank
+ struct kr_cache
+EOF
+
+printf "
+typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type,
+ const struct kr_query *qry);
+
+void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner,
+ uint16_t type, uint16_t rclass, uint32_t ttl);
+"
+
+## Some definitions would need too many deps, so shorten them.
+
+genResType "struct kr_nsrep" | sed '/union/,$ d'
+printf "\t/* beware: hidden stub, to avoid hardcoding sockaddr lengths */\n};\n"
+
+genResType "struct kr_query"
+
+genResType "struct kr_context" | sed '/kr_nsrep_rtt_lru_t/,$ d'
+printf "\tchar _stub[];\n};\n"
+
+## libknot API
+./scripts/gen-cdefs.sh libknot functions <<-EOF
+# Utils
+ knot_strerror
+# Domain names
+ knot_dname_copy
+ knot_dname_from_str
+ knot_dname_in_bailiwick
+ knot_dname_is_equal
+ knot_dname_labels
+ knot_dname_size
+ knot_dname_to_lower
+ knot_dname_to_str
+# Resource records
+ knot_rdataset_at
+ knot_rdataset_merge
+ knot_rrset_add_rdata
+ knot_rrset_txt_dump
+ knot_rrset_txt_dump_data
+ knot_rrset_size
+# Packet
+ knot_pkt_begin
+ knot_pkt_put_question
+ knot_pkt_put_rotate
+ knot_pkt_new
+ knot_pkt_free
+ knot_pkt_parse
+EOF
+
+## libkres API
+./scripts/gen-cdefs.sh libkres functions <<-EOF
+# Resolution request
+ kr_resolve_plan
+ kr_resolve_pool
+# Resolution plan
+ kr_rplan_push
+ kr_rplan_pop
+ kr_rplan_resolved
+ kr_rplan_last
+# Nameservers
+ kr_nsrep_set
+# Utils
+ kr_make_query
+ kr_pkt_make_auth_header
+ kr_pkt_put
+ kr_pkt_recycle
+ kr_pkt_clear_payload
+ kr_pkt_qclass
+ kr_pkt_qtype
+ kr_rnd_buffered
+ kr_rrsig_sig_inception
+ kr_rrsig_sig_expiration
+ kr_rrsig_type_covered
+ kr_inaddr
+ kr_inaddr_family
+ kr_inaddr_len
+ kr_inaddr_str
+ kr_sockaddr_len
+ kr_inaddr_port
+ kr_straddr_family
+ kr_straddr_subnet
+ kr_bitcmp
+ kr_family_len
+ kr_straddr_socket
+ kr_ranked_rrarray_add
+ kr_qflags_set
+ kr_qflags_clear
+ kr_zonecut_add
+ kr_zonecut_is_empty
+ kr_zonecut_set
+ kr_now
+ kr_strptime_diff
+ lru_free_items_impl
+ lru_create_impl
+ lru_get_impl
+ mm_realloc
+# Trust anchors
+ kr_ta_get
+ kr_ta_add
+ kr_ta_del
+ kr_ta_clear
+# DNSSEC
+ kr_dnssec_key_ksk
+ kr_dnssec_key_revoked
+ kr_dnssec_key_tag
+ kr_dnssec_key_match
+# Cache
+ kr_cache_closest_apex
+ kr_cache_insert_rr
+ kr_cache_remove
+ kr_cache_remove_subtree
+ kr_cache_sync
+EOF
+
+
+## libzscanner API for ./zonefile.lua
+./scripts/gen-cdefs.sh libzscanner types <<-EOF
+ zs_win_t
+ zs_apl_t
+ zs_loc_t
+ zs_state_t
+ zs_scanner_t
+ struct zs_scanner
+EOF
+./scripts/gen-cdefs.sh libzscanner functions <<-EOF
+ zs_deinit
+ zs_init
+ zs_parse_record
+ zs_set_input_file
+ zs_set_input_string
+ zs_strerror
+EOF
+
+printf "]]\n"
+
+exit 0
diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua
new file mode 100644
index 0000000..8cb0046
--- /dev/null
+++ b/daemon/lua/kres.lua
@@ -0,0 +1,952 @@
+-- LuaJIT ffi bindings for libkres, a DNS resolver library.
+-- @note Since it's statically compiled, it expects to find the symbols in the C namespace.
+
+local kres -- the module
+
+local ffi = require('ffi')
+local bit = require('bit')
+local bor = bit.bor
+local band = bit.band
+local C = ffi.C
+local knot = ffi.load(libknot_SONAME)
+
+-- Inverse table
+local function itable(t, tolower)
+ local it = {}
+ for k,v in pairs(t) do it[v] = tolower and string.lower(k) or k end
+ return it
+end
+
+-- Byte order conversions
+local function htonl(x) return x end
+local htons = htonl
+if ffi.abi('le') then
+ htonl = bit.bswap
+ function htons(x) return bit.rshift(htonl(x), 16) end
+end
+
+-- Basic types
+local u16_p = ffi.typeof('uint16_t *')
+
+-- Various declarations that are very stable.
+ffi.cdef[[
+/*
+ * Data structures
+ */
+
+/* stdlib */
+typedef long time_t;
+struct timeval {
+ time_t tv_sec;
+ time_t tv_usec;
+};
+struct sockaddr {
+ uint16_t sa_family;
+ uint8_t _stub[]; /* Do not touch */
+};
+struct knot_error {
+ int code;
+};
+
+/*
+ * libc APIs
+ */
+void * malloc(size_t size);
+void free(void *ptr);
+int inet_pton(int af, const char *src, void *dst);
+int gettimeofday(struct timeval *tv, struct timezone *tz);
+]]
+
+require('kres-gen')
+
+-- Error code representation
+local knot_error_t = ffi.typeof('struct knot_error')
+ffi.metatype(knot_error_t, {
+ -- Convert libknot error strings
+ __tostring = function(self)
+ return ffi.string(knot.knot_strerror(self.code))
+ end,
+});
+
+-- Constant tables
+local const_class = {
+ IN = 1,
+ CH = 3,
+ NONE = 254,
+ ANY = 255,
+}
+local const_type = {
+ A = 1,
+ NS = 2,
+ MD = 3,
+ MF = 4,
+ CNAME = 5,
+ SOA = 6,
+ MB = 7,
+ MG = 8,
+ MR = 9,
+ NULL = 10,
+ WKS = 11,
+ PTR = 12,
+ HINFO = 13,
+ MINFO = 14,
+ MX = 15,
+ TXT = 16,
+ RP = 17,
+ AFSDB = 18,
+ X25 = 19,
+ ISDN = 20,
+ RT = 21,
+ NSAP = 22,
+ ['NSAP-PTR'] = 23,
+ SIG = 24,
+ KEY = 25,
+ PX = 26,
+ GPOS = 27,
+ AAAA = 28,
+ LOC = 29,
+ NXT = 30,
+ EID = 31,
+ NIMLOC = 32,
+ SRV = 33,
+ ATMA = 34,
+ NAPTR = 35,
+ KX = 36,
+ CERT = 37,
+ A6 = 38,
+ DNAME = 39,
+ SINK = 40,
+ OPT = 41,
+ APL = 42,
+ DS = 43,
+ SSHFP = 44,
+ IPSECKEY = 45,
+ RRSIG = 46,
+ NSEC = 47,
+ DNSKEY = 48,
+ DHCID = 49,
+ NSEC3 = 50,
+ NSEC3PARAM = 51,
+ TLSA = 52,
+ SMIMEA = 53,
+ HIP = 55,
+ NINFO = 56,
+ RKEY = 57,
+ TALINK = 58,
+ CDS = 59,
+ CDNSKEY = 60,
+ OPENPGPKEY = 61,
+ CSYNC = 62,
+ SPF = 99,
+ UINFO = 100,
+ UID = 101,
+ GID = 102,
+ UNSPEC = 103,
+ NID = 104,
+ L32 = 105,
+ L64 = 106,
+ LP = 107,
+ EUI48 = 108,
+ EUI64 = 109,
+ TKEY = 249,
+ TSIG = 250,
+ IXFR = 251,
+ AXFR = 252,
+ MAILB = 253,
+ MAILA = 254,
+ ANY = 255,
+ URI = 256,
+ CAA = 257,
+ AVC = 258,
+ DOA = 259,
+ TA = 32768,
+ DLV = 32769,
+}
+local const_section = {
+ ANSWER = 0,
+ AUTHORITY = 1,
+ ADDITIONAL = 2,
+}
+local const_opcode = {
+ QUERY = 0,
+ IQUERY = 1,
+ STATUS = 2,
+ NOTIFY = 4,
+ UPDATE = 5,
+}
+local const_rcode = {
+ NOERROR = 0,
+ FORMERR = 1,
+ SERVFAIL = 2,
+ NXDOMAIN = 3,
+ NOTIMPL = 4,
+ REFUSED = 5,
+ YXDOMAIN = 6,
+ YXRRSET = 7,
+ NXRRSET = 8,
+ NOTAUTH = 9,
+ NOTZONE = 10,
+ BADVERS = 16,
+ BADCOOKIE = 23,
+}
+-- This corresponds to `enum kr_rank`, it's not possible to do this without introspection unfortunately
+local const_rank = {
+ INITIAL = 0,
+ OMIT = 1,
+ TRY = 2,
+ INDET = 4,
+ BOGUS = 5,
+ MISMATCH = 6,
+ MISSING = 7,
+ INSECURE = 8,
+ AUTH = 16,
+ SECURE = 32
+}
+
+-- Constant tables
+local const_class_str = itable(const_class)
+local const_type_str = itable(const_type)
+local const_rcode_str = itable(const_rcode)
+local const_opcode_str = itable(const_opcode)
+local const_section_str = itable(const_section)
+local const_rank_str = itable(const_rank)
+
+-- Metatype for RR types to allow anonymous types
+setmetatable(const_type, {
+ __index = function (t, k)
+ local v = rawget(t, k)
+ if v then return v end
+ -- Allow TYPE%d notation
+ if string.find(k, 'TYPE', 1, true) then
+ return tonumber(k:sub(5))
+ end
+ -- Unknown type
+ return
+ end
+})
+
+-- Metatype for RR types to allow anonymous string types
+setmetatable(const_type_str, {
+ __index = function (t, k)
+ local v = rawget(t, k)
+ if v then return v end
+ return string.format('TYPE%d', k)
+ end
+})
+
+-- Metatype for timeval
+local timeval_t = ffi.typeof('struct timeval')
+
+-- Metatype for sockaddr
+local addr_buf = ffi.new('char[16]')
+local str_addr_buf = ffi.new('char[46 + 1 + 6 + 1]') -- IPv6 + #port + \0
+local str_addr_buf_len = ffi.sizeof(str_addr_buf)
+local sockaddr_t = ffi.typeof('struct sockaddr')
+ffi.metatype( sockaddr_t, {
+ __index = {
+ len = function(sa) return C.kr_inaddr_len(sa) end,
+ ip = function (sa) return C.kr_inaddr(sa) end,
+ family = function (sa) return C.kr_inaddr_family(sa) end,
+ port = function (sa) return C.kr_inaddr_port(sa) end,
+ },
+ __tostring = function(sa)
+ assert(ffi.istype(sockaddr_t, sa))
+ local len = ffi.new('size_t[1]', str_addr_buf_len)
+ local ret = C.kr_inaddr_str(sa, str_addr_buf, len)
+ if ret ~= 0 then
+ error('kr_inaddr_str failed: ' .. tostring(ret))
+ end
+ return ffi.string(str_addr_buf)
+ end,
+
+})
+
+-- Parametrized LRU table
+local typed_lru_t = 'struct { $ value_type[1]; struct lru * lru; }'
+
+-- Metatype for LRU
+local lru_metatype = {
+ -- Create a new LRU with given value type
+ -- By default the LRU will have a capacity of 65536 elements
+ -- Note: At the point the parametrized type must be finalized
+ __new = function (ct, max_slots)
+ -- {0} will make sure that the value is coercible to a number
+ local o = ffi.new(ct, {0}, C.lru_create_impl(max_slots or 65536, nil, nil))
+ if o.lru == nil then
+ return
+ end
+ return o
+ end,
+ -- Destructor to clean allocated memory
+ __gc = function (self)
+ assert(self.lru ~= nil)
+ C.lru_free_items_impl(self.lru)
+ C.free(self.lru)
+ self.lru = nil
+ end,
+ __index = {
+ -- Look up key and return reference to current
+ -- Note: The key will be inserted if it doesn't exist
+ get_ref = function (self, key, key_len, allow_insert)
+ local insert = allow_insert and true or false
+ local ptr = C.lru_get_impl(self.lru, key, key_len or #key, ffi.sizeof(self.value_type[0]), insert, nil)
+ if ptr ~= nil then
+ return ffi.cast(self.value_type, ptr)
+ end
+ end,
+ -- Look up key and return current value
+ get = function (self, key, key_len)
+ local ref = self:get_ref(key, key_len, false)
+ if ref then
+ return ref[0]
+ end
+ end,
+ -- Set value for key to given value
+ set = function (self, key, value, key_len)
+ local ref = self:get_ref(key, key_len, true)
+ if ref then
+ ref[0] = value
+ return true
+ end
+ end,
+ },
+}
+
+-- Pretty print for domain name
+local function dname2str(dname)
+ if dname == nil then return end
+ local text_name = ffi.gc(C.knot_dname_to_str(nil, dname, 0), C.free)
+ if text_name ~= nil then
+ return ffi.string(text_name)
+ end
+end
+
+-- Convert dname pointer to wireformat string
+local function dname2wire(name)
+ if name == nil then return nil end
+ return ffi.string(name, knot.knot_dname_size(name))
+end
+
+-- RR sets created in Lua must have a destructor to release allocated memory
+local function rrset_free(rr)
+ if rr._owner ~= nil then ffi.C.free(rr._owner) end
+ if rr:rdcount() > 0 then ffi.C.free(rr.rrs.rdata) end
+end
+
+-- Metatype for RR set. Beware, the indexing is 0-based (rdata, get, tostring).
+local rrset_buflen = (64 + 1) * 1024
+local rrset_buf = ffi.new('char[?]', rrset_buflen)
+local knot_rrset_pt = ffi.typeof('knot_rrset_t *')
+local knot_rrset_t = ffi.typeof('knot_rrset_t')
+ffi.metatype( knot_rrset_t, {
+ -- Create a new empty RR set object with an allocated owner and a destructor
+ __new = function (ct, owner, rrtype, rrclass, ttl)
+ local rr = ffi.new(ct)
+ C.kr_rrset_init(rr,
+ owner and knot.knot_dname_copy(owner, nil),
+ rrtype or 0,
+ rrclass or const_class.IN,
+ ttl or 0)
+ return ffi.gc(rr, rrset_free)
+ end,
+ -- BEWARE: `owner` and `rdata` are typed as a plain lua strings
+ -- and not the real types they represent.
+ __tostring = function(rr)
+ assert(ffi.istype(knot_rrset_t, rr))
+ return rr:txt_dump()
+ end,
+ __index = {
+ owner = function(rr)
+ assert(ffi.istype(knot_rrset_t, rr))
+ return dname2wire(rr._owner)
+ end,
+ ttl = function(rr)
+ assert(ffi.istype(knot_rrset_t, rr))
+ return tonumber(rr._ttl)
+ end,
+ class = function(rr, val)
+ assert(ffi.istype(knot_rrset_t, rr))
+ if val then
+ rr.rclass = val
+ end
+ return tonumber(rr.rclass)
+ end,
+ rdata_pt = function(rr, i)
+ assert(ffi.istype(knot_rrset_t, rr) and i >= 0 and i < rr:rdcount())
+ return knot.knot_rdataset_at(rr.rrs, i)
+ end,
+ rdata = function(rr, i)
+ assert(ffi.istype(knot_rrset_t, rr))
+ local rd = rr:rdata_pt(i)
+ return ffi.string(rd.data, rd.len)
+ end,
+ get = function(rr, i)
+ assert(ffi.istype(knot_rrset_t, rr) and i >= 0 and i < rr:rdcount())
+ return {owner = rr:owner(),
+ ttl = rr:ttl(),
+ class = tonumber(rr.rclass),
+ type = tonumber(rr.type),
+ rdata = rr:rdata(i)}
+ end,
+ tostring = function(rr, i)
+ assert(ffi.istype(knot_rrset_t, rr)
+ and (i == nil or (i >= 0 and i < rr:rdcount())) )
+ if rr:rdcount() > 0 then
+ local ret
+ if i ~= nil then
+ ret = knot.knot_rrset_txt_dump_data(rr, i, rrset_buf, rrset_buflen, knot.KNOT_DUMP_STYLE_DEFAULT)
+ else
+ ret = -1
+ end
+ return ret >= 0 and ffi.string(rrset_buf)
+ end
+ end,
+
+ -- Dump the rrset in presentation format (dig-like).
+ txt_dump = function(rr, style)
+ assert(ffi.istype(knot_rrset_t, rr))
+ local bufsize = 1024
+ local dump = ffi.new('char *[1]', C.malloc(bufsize))
+ -- ^ one pointer to a string
+ local size = ffi.new('size_t[1]', { bufsize }) -- one size_t = bufsize
+
+ local ret = knot.knot_rrset_txt_dump(rr, dump, size,
+ style or knot.KNOT_DUMP_STYLE_DEFAULT)
+ local result = nil
+ if ret >= 0 then
+ result = ffi.string(dump[0], ret)
+ end
+ C.free(dump[0])
+ return result
+ end,
+ -- Return RDATA count for this RR set
+ rdcount = function(rr)
+ assert(ffi.istype(knot_rrset_t, rr))
+ return tonumber(rr.rrs.count)
+ end,
+ -- Add binary RDATA to the RR set
+ add_rdata = function (rr, rdata, rdlen, no_ttl)
+ assert(ffi.istype(knot_rrset_t, rr))
+ assert(no_ttl == nil, 'add_rdata() can not accept TTL anymore')
+ local ret = knot.knot_rrset_add_rdata(rr, rdata, tonumber(rdlen), nil)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ -- Merge data from another RR set into the current one
+ merge_rdata = function (rr, source)
+ assert(ffi.istype(knot_rrset_t, rr))
+ assert(ffi.istype(knot_rrset_t, source))
+ local ret = knot.knot_rdataset_merge(rr.rrs, source.rrs, nil)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ -- Return type covered by this RRSIG
+ type_covered = function(rr, i)
+ i = i or 0
+ assert(ffi.istype(knot_rrset_t, rr) and i >= 0 and i < rr:rdcount())
+ if rr.type ~= const_type.RRSIG then return end
+ return tonumber(C.kr_rrsig_type_covered(knot.knot_rdataset_at(rr.rrs, i)))
+ end,
+ -- Check whether a RRSIG is covering current RR set
+ is_covered_by = function(rr, rrsig)
+ assert(ffi.istype(knot_rrset_t, rr))
+ assert(ffi.istype(knot_rrset_t, rrsig))
+ assert(rrsig.type == const_type.RRSIG)
+ return (rr.type == rrsig:type_covered() and rr:owner() == rrsig:owner())
+ end,
+ -- Return RR set wire size
+ wire_size = function(rr)
+ assert(ffi.istype(knot_rrset_t, rr))
+ return tonumber(knot.knot_rrset_size(rr))
+ end,
+ },
+})
+
+-- Destructor for packet accepts pointer to pointer
+local knot_pkt_t = ffi.typeof('knot_pkt_t')
+
+-- Helpers for reading/writing 16-bit numbers from packet wire
+local function pkt_u16(pkt, off, val)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ptr = ffi.cast(u16_p, pkt.wire + off)
+ if val ~= nil then ptr[0] = htons(val) end
+ return (htons(ptr[0]))
+end
+
+-- Helpers for reading/writing message header flags
+local function pkt_bit(pkt, byteoff, bitmask, val)
+ -- If the value argument is passed, set/clear the desired bit
+ if val ~= nil then
+ if val then pkt.wire[byteoff] = bit.bor(pkt.wire[byteoff], bitmask)
+ else pkt.wire[byteoff] = bit.band(pkt.wire[byteoff], bit.bnot(bitmask)) end
+ return true
+ end
+ return (bit.band(pkt.wire[byteoff], bitmask) ~= 0)
+end
+
+local function knot_pkt_rr(section, i)
+ assert(section and ffi.istype('knot_pktsection_t', section)
+ and i >= 0 and i < section.count)
+ local ret = section.pkt.rr + section.pos + i
+ assert(ffi.istype(knot_rrset_pt, ret))
+ return ret
+end
+
+-- Helpers for converting packet to text
+local function section_tostring(pkt, section_id)
+ local data = {}
+ local section = pkt.sections + section_id
+ if section.count > 0 then
+ table.insert(data, string.format('\n;; %s\n', const_section_str[section_id]))
+ for j = 0, section.count - 1 do
+ local rrset = knot_pkt_rr(section, j)
+ local rrtype = rrset.type
+ if rrtype ~= const_type.OPT and rrtype ~= const_type.TSIG then
+ table.insert(data, rrset:txt_dump())
+ end
+ end
+ end
+ return table.concat(data, '')
+end
+
+local function packet_tostring(pkt)
+ local hdr = string.format(';; ->>HEADER<<- opcode: %s; status: %s; id: %d\n',
+ const_opcode_str[pkt:opcode()], const_rcode_str[pkt:rcode()], pkt:id())
+ local flags = {}
+ for _,v in ipairs({'rd', 'tc', 'aa', 'qr', 'cd', 'ad', 'ra'}) do
+ if(pkt[v](pkt)) then table.insert(flags, v) end
+ end
+ local info = string.format(';; Flags: %s; QUERY: %d; ANSWER: %d; AUTHORITY: %d; ADDITIONAL: %d\n',
+ table.concat(flags, ' '), pkt:qdcount(), pkt:ancount(), pkt:nscount(), pkt:arcount())
+ local data = '\n'
+ if pkt.opt_rr ~= nil then
+ data = data..string.format(';; OPT PSEUDOSECTION:\n%s', pkt.opt_rr:tostring())
+ end
+ if pkt.tsig_rr ~= nil then
+ data = data..string.format(';; TSIG PSEUDOSECTION:\n%s', pkt.tsig_rr:tostring())
+ end
+ -- Zone transfer answers may omit question
+ if pkt:qdcount() > 0 then
+ data = data..string.format(';; QUESTION\n;; %s\t%s\t%s\n',
+ dname2str(pkt:qname()), const_type_str[pkt:qtype()], const_class_str[pkt:qclass()])
+ end
+ local data_sec = {}
+ for i = const_section.ANSWER, const_section.ADDITIONAL do
+ table.insert(data_sec, section_tostring(pkt, i))
+ end
+ return hdr..info..data..table.concat(data_sec, '')
+end
+
+-- Metatype for packet
+ffi.metatype( knot_pkt_t, {
+ __new = function (_, size, wire)
+ if size < 12 or size > 65535 then
+ error('packet size must be <12, 65535>')
+ end
+
+ local pkt = knot.knot_pkt_new(nil, size, nil)
+ if pkt == nil then
+ error(string.format('failed to allocate a packet of size %d', size))
+ end
+ if wire == nil then
+ C.kr_rnd_buffered(pkt.wire, 2) -- randomize the query ID
+ else
+ assert(size <= #wire)
+ ffi.copy(pkt.wire, wire, size)
+ pkt.size = size
+ pkt.parsed = 0
+ end
+
+ return ffi.gc(pkt[0], knot.knot_pkt_free)
+ end,
+ __tostring = function(pkt)
+ return pkt:tostring()
+ end,
+ __len = function(pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return tonumber(pkt.size)
+ end,
+ __ipairs = function(self)
+ return ipairs(self:section(const_section.ANSWER))
+ end,
+ __index = {
+ -- Header
+ id = function(pkt, val) return pkt_u16(pkt, 0, val) end,
+ qdcount = function(pkt, val) return pkt_u16(pkt, 4, val) end,
+ ancount = function(pkt, val) return pkt_u16(pkt, 6, val) end,
+ nscount = function(pkt, val) return pkt_u16(pkt, 8, val) end,
+ arcount = function(pkt, val) return pkt_u16(pkt, 10, val) end,
+ opcode = function (pkt, val)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ pkt.wire[2] = (val) and bit.bor(bit.band(pkt.wire[2], 0x78), 8 * val) or pkt.wire[2]
+ return (bit.band(pkt.wire[2], 0x78) / 8)
+ end,
+ rcode = function (pkt, val)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ pkt.wire[3] = (val) and bor(band(pkt.wire[3], 0xf0), val) or pkt.wire[3]
+ return band(pkt.wire[3], 0x0f)
+ end,
+ rd = function (pkt, val) return pkt_bit(pkt, 2, 0x01, val) end,
+ tc = function (pkt, val) return pkt_bit(pkt, 2, 0x02, val) end,
+ aa = function (pkt, val) return pkt_bit(pkt, 2, 0x04, val) end,
+ qr = function (pkt, val) return pkt_bit(pkt, 2, 0x80, val) end,
+ cd = function (pkt, val) return pkt_bit(pkt, 3, 0x10, val) end,
+ ad = function (pkt, val) return pkt_bit(pkt, 3, 0x20, val) end,
+ ra = function (pkt, val) return pkt_bit(pkt, 3, 0x80, val) end,
+ -- Question
+ qname = function(pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ -- inlined knot_pkt_qname(), basically
+ if pkt == nil or pkt.qname_size == 0 then return nil end
+ return ffi.string(pkt.wire + 12, pkt.qname_size)
+ end,
+ qclass = function(pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return C.kr_pkt_qclass(pkt)
+ end,
+ qtype = function(pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return C.kr_pkt_qtype(pkt)
+ end,
+ rrsets = function (pkt, section_id)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local records = {}
+ local section = pkt.sections + section_id
+ for i = 1, section.count do
+ local rrset = knot_pkt_rr(section, i - 1)
+ table.insert(records, rrset)
+ end
+ return records
+ end,
+ section = function (pkt, section_id)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local records = {}
+ local section = pkt.sections + section_id
+ for i = 1, section.count do
+ local rrset = knot_pkt_rr(section, i - 1)
+ for k = 1, rrset:rdcount() do
+ table.insert(records, rrset:get(k - 1))
+ end
+ end
+ return records
+ end,
+ begin = function (pkt, section)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ assert(section >= pkt.current, 'cannot rewind to already written section')
+ assert(const_section_str[section], string.format('invalid section: %s', section))
+ local ret = knot.knot_pkt_begin(pkt, section)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ put = function (pkt, owner, ttl, rclass, rtype, rdata)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ret = C.kr_pkt_put(pkt, owner, ttl, rclass, rtype, rdata, #rdata)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ -- Put an RR set in the packet
+ -- Note: the packet doesn't take ownership of the RR set
+ put_rr = function (pkt, rr, rotate, flags)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ assert(ffi.istype(knot_rrset_t, rr))
+ local ret = C.knot_pkt_put_rotate(pkt, 0, rr, rotate or 0, flags or 0)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ recycle = function (pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ret = C.kr_pkt_recycle(pkt)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ clear_payload = function (pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ret = C.kr_pkt_clear_payload(pkt)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ question = function(pkt, qname, qclass, qtype)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ assert(qclass ~= nil, string.format('invalid class: %s', qclass))
+ assert(qtype ~= nil, string.format('invalid type: %s', qtype))
+ local ret = C.knot_pkt_put_question(pkt, qname, qclass, qtype)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ towire = function (pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return ffi.string(pkt.wire, pkt.size)
+ end,
+ tostring = function(pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return packet_tostring(pkt)
+ end,
+ -- Return number of remaining empty bytes in the packet
+ -- This is generally useful to check if there's enough space
+ remaining_bytes = function (pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local occupied = pkt.size + pkt.reserved
+ assert(pkt.max_size >= occupied)
+ return tonumber(pkt.max_size - occupied)
+ end,
+ -- Packet manipulation
+ parse = function (pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ret = knot.knot_pkt_parse(pkt, 0)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ -- Resize packet wire to a new size
+ resize = function (pkt, new_size)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ptr = C.mm_realloc(pkt.mm, pkt.wire, new_size, pkt.max_size)
+ if ptr == nil then return end
+ pkt.wire = ptr
+ pkt.max_size = new_size
+ return true
+ end,
+ },
+})
+-- Metatype for query
+local kr_query_t = ffi.typeof('struct kr_query')
+ffi.metatype( kr_query_t, {
+ __index = {
+ -- Return query domain name
+ name = function(qry)
+ assert(ffi.istype(kr_query_t, qry))
+ return dname2wire(qry.sname)
+ end,
+ -- Write this query into packet
+ write = function(qry, pkt)
+ assert(ffi.istype(kr_query_t, qry))
+ assert(ffi.istype(knot_pkt_t, pkt))
+ local ret = C.kr_make_query(qry, pkt)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ },
+})
+-- Metatype for request
+local kr_request_t = ffi.typeof('struct kr_request')
+ffi.metatype( kr_request_t, {
+ __index = {
+ current = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ if req.current_query == nil then return nil end
+ return req.current_query
+ end,
+ -- Return last query on the resolution plan
+ last = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ local query = C.kr_rplan_last(C.kr_resolve_plan(req))
+ if query == nil then return end
+ return query
+ end,
+ resolved = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ local qry = C.kr_rplan_resolved(C.kr_resolve_plan(req))
+ if qry == nil then return nil end
+ return qry
+ end,
+ -- returns first resolved sub query for a request
+ first_resolved = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ local rplan = C.kr_resolve_plan(req)
+ if not rplan or rplan.resolved.len < 1 then return nil end
+ return rplan.resolved.at[0]
+ end,
+ push = function(req, qname, qtype, qclass, flags, parent)
+ assert(ffi.istype(kr_request_t, req))
+ flags = kres.mk_qflags(flags) -- compatibility
+ local rplan = C.kr_resolve_plan(req)
+ local qry = C.kr_rplan_push(rplan, parent, qname, qclass, qtype)
+ if qry ~= nil and flags ~= nil then
+ C.kr_qflags_set(qry.flags, flags)
+ end
+ return qry
+ end,
+ pop = function(req, qry)
+ assert(ffi.istype(kr_request_t, req))
+ return C.kr_rplan_pop(C.kr_resolve_plan(req), qry)
+ end,
+ -- Return per-request variable table
+ -- The request can store anything in this Lua table and it will be freed
+ -- when the request is closed, it doesn't have to worry about contents.
+ vars = function (req)
+ assert(ffi.istype(kr_request_t, req))
+ -- Return variable if it's already stored
+ local var = worker.vars[req.vars_ref]
+ if var then
+ return var
+ end
+ -- Either take a slot number from freelist
+ -- or find a first free slot (expand the table)
+ local ref = worker.vars[0]
+ if ref then
+ worker.vars[0] = worker.vars[ref]
+ else
+ ref = #worker.vars + 1
+ end
+ -- Create new variables table
+ var = {}
+ worker.vars[ref] = var
+ -- Save reference in the request
+ req.vars_ref = ref
+ return var
+ end,
+ },
+})
+
+-- C array iterator
+local function c_array_iter(t, i)
+ i = i + 1
+ if i >= t.len then return end
+ return i, t.at[i][0]
+end
+
+-- Metatype for ranked record array
+local ranked_rr_array_t = ffi.typeof('ranked_rr_array_t')
+ffi.metatype(ranked_rr_array_t, {
+ __len = function(self)
+ return tonumber(self.len)
+ end,
+ __ipairs = function (self)
+ return c_array_iter, self, -1
+ end,
+ __index = {
+ get = function (self, i)
+ if i < 0 or i > self.len then return nil end
+ return self.at[i][0]
+ end,
+ }
+})
+
+-- Cache metatype
+local kr_cache_t = ffi.typeof('struct kr_cache')
+ffi.metatype( kr_cache_t, {
+ __index = {
+ insert = function (self, rr, rrsig, rank, timestamp)
+ assert(ffi.istype(kr_cache_t, self))
+ assert(ffi.istype(knot_rrset_t, rr), 'RR must be a rrset type')
+ assert(not rrsig or ffi.istype(knot_rrset_t, rrsig), 'RRSIG must be nil or of the rrset type')
+ -- Get current timestamp
+ if not timestamp then
+ local now = timeval_t()
+ C.gettimeofday(now, nil)
+ timestamp = tonumber(now.tv_sec)
+ end
+ -- Insert record into cache
+ local ret = C.kr_cache_insert_rr(self, rr, rrsig, tonumber(rank or 0), timestamp)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ sync = function (self)
+ assert(ffi.istype(kr_cache_t, self))
+ local ret = C.kr_cache_sync(self)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ },
+})
+
+-- Pretty-print a single RR (which is a table with .owner .ttl .type .rdata)
+-- Extension: append .comment if exists.
+local function rr2str(rr, style)
+ -- Construct a single-RR temporary set while minimizing copying.
+ local ret
+ do
+ local rrs = knot_rrset_t(rr.owner, rr.type, kres.class.IN, rr.ttl)
+ rrs:add_rdata(rr.rdata, #rr.rdata)
+ ret = rrs:txt_dump(style)
+ end
+
+ -- Trim the newline and append comment (optionally).
+ if ret then
+ if ret:byte(-1) == string.byte('\n', -1) then
+ ret = ret:sub(1, -2)
+ end
+ if rr.comment then
+ ret = ret .. ' ;' .. rr.comment
+ end
+ end
+ return ret
+end
+
+-- Module API
+kres = {
+ -- Constants
+ class = const_class,
+ type = const_type,
+ section = const_section,
+ rcode = const_rcode,
+ opcode = const_opcode,
+ rank = const_rank,
+
+ -- Constants to strings
+ tostring = {
+ class = const_class_str,
+ type = const_type_str,
+ section = const_section_str,
+ rcode = const_rcode_str,
+ opcode = const_opcode_str,
+ rank = const_rank_str,
+ },
+
+ -- Create a struct kr_qflags from a single flag name or a list of names.
+ mk_qflags = function (names)
+ local kr_qflags = ffi.typeof('struct kr_qflags')
+ if names == 0 or names == nil then -- compatibility: nil is common in lua
+ names = {}
+ elseif type(names) == 'string' then
+ names = {names}
+ elseif ffi.istype(kr_qflags, names) then
+ return names
+ end
+
+ local fs = ffi.new(kr_qflags)
+ for _, name in pairs(names) do
+ fs[name] = true
+ end
+ return fs
+ end,
+
+ CONSUME = 1, PRODUCE = 2, DONE = 4, FAIL = 8, YIELD = 16,
+
+ -- Export types
+ rrset = knot_rrset_t,
+ packet = knot_pkt_t,
+ lru = function (max_size, value_type)
+ local ct = ffi.typeof(typed_lru_t, value_type or ffi.typeof('uint64_t'))
+ return ffi.metatype(ct, lru_metatype)(max_size)
+ end,
+
+ -- Metatypes. Beware that any pointer will be cast silently...
+ pkt_t = function (udata) return ffi.cast('knot_pkt_t *', udata) end,
+ request_t = function (udata) return ffi.cast('struct kr_request *', udata) end,
+ sockaddr_t = function (udata) return ffi.cast('struct sockaddr *', udata) end,
+
+ -- Global API functions
+ -- Convert a lua string to a lower-case wire format (inside GC-ed ffi.string).
+ str2dname = function(name)
+ if type(name) ~= 'string' then return end
+ local dname = ffi.gc(C.knot_dname_from_str(nil, name, 0), C.free)
+ if dname == nil then return nil end
+ ffi.C.knot_dname_to_lower(dname);
+ return dname2wire(dname)
+ end,
+ dname2str = dname2str,
+ dname2wire = dname2wire,
+
+ rr2str = rr2str,
+ str2ip = function (ip)
+ local family = C.kr_straddr_family(ip)
+ local ret = C.inet_pton(family, ip, addr_buf)
+ if ret ~= 1 then return nil end
+ return ffi.string(addr_buf, C.kr_family_len(family))
+ end,
+ context = function () return ffi.cast('struct kr_context *', __engine) end,
+
+ knot_pkt_rr = knot_pkt_rr,
+}
+
+return kres
diff --git a/daemon/lua/sandbox.lua b/daemon/lua/sandbox.lua
new file mode 100644
index 0000000..017e3a3
--- /dev/null
+++ b/daemon/lua/sandbox.lua
@@ -0,0 +1,458 @@
+local ffi = require('ffi')
+
+-- Units
+kB = 1024
+MB = 1024*kB
+GB = 1024*MB
+-- Time
+sec = 1000
+second = sec
+minute = 60 * sec
+min = minute
+hour = 60 * minute
+day = 24 * hour
+
+-- Logging
+function panic(fmt, ...)
+ error(string.format('error: '..fmt, ...))
+end
+function warn(fmt, ...)
+ io.stderr:write(string.format(fmt..'\n', ...))
+end
+function log(fmt, ...)
+ print(string.format(fmt, ...))
+end
+
+-- Resolver bindings
+kres = require('kres')
+if rawget(kres, 'str2dname') ~= nil then
+ todname = kres.str2dname
+end
+
+-- Compatibility wrapper for query flags.
+worker.resolve = function (qname, qtype, qclass, options, finish, init)
+ -- Alternatively use named arguments
+ if type(qname) == 'table' then
+ local t = qname
+ qname = t.name
+ qtype = t.type or kres.type.A
+ qclass = t.class or kres.class.IN
+ options = t.options
+ finish = t.finish
+ init = t.init
+ end
+
+ local init_cb, finish_cb = init, nil
+ if finish then
+ -- Create callback for finalization
+ finish_cb = ffi.cast('trace_callback_f', function (req)
+ req = kres.request_t(req)
+ finish(req.answer, req)
+ finish_cb:free()
+ end)
+ -- Wrap initialiser to install finish callback
+ init_cb = function (req)
+ req = kres.request_t(req)
+ if init then init(req) end
+ req.trace_finish = finish_cb
+ end
+ end
+
+ -- Translate options and resolve
+ options = kres.mk_qflags(options)
+ return worker.resolve_unwrapped(qname, qtype, qclass, options, init_cb)
+end
+
+resolve = worker.resolve
+
+-- Shorthand for aggregated per-worker information
+worker.info = function ()
+ local t = worker.stats()
+ t.pid = worker.pid
+ return t
+end
+
+-- Resolver mode of operation
+local current_mode = 'normal'
+local mode_table = { normal=0, strict=1, permissive=2 }
+function mode(m)
+ if not m then return current_mode end
+ if not mode_table[m] then error('unsupported mode: '..m) end
+ -- Update current operation mode
+ current_mode = m
+ option('STRICT', current_mode == 'strict')
+ option('PERMISSIVE', current_mode == 'permissive')
+ return true
+end
+
+-- Trivial option alias
+function reorder_RR(val)
+ return option('REORDER_RR', val)
+end
+
+-- Get/set resolver options via name (string)
+function option(name, val)
+ local flags = kres.context().options;
+ -- Note: no way to test existence of flags[name] but we want error anyway.
+ name = string.upper(name) -- convenience
+ if val ~= nil then
+ if (val ~= true) and (val ~= false) then
+ panic('invalid option value: ' .. tostring(val))
+ end
+ flags[name] = val;
+ end
+ return flags[name];
+end
+
+-- Function aliases
+-- `env.VAR returns os.getenv(VAR)`
+env = {}
+setmetatable(env, {
+ __index = function (_, k) return os.getenv(k) end
+})
+
+-- Quick access to interfaces
+-- `net.<iface>` => `net.interfaces()[iface]`
+-- `net = {addr1, ..}` => `net.listen(name, addr1)`
+-- `net.ipv{4,6} = {true, false}` => enable/disable IPv{4,6}
+setmetatable(net, {
+ __index = function (t, k)
+ local v = rawget(t, k)
+ if v then return v
+ elseif k == 'ipv6' then return not option('NO_IPV6')
+ elseif k == 'ipv4' then return not option('NO_IPV4')
+ else return net.interfaces()[k]
+ end
+ end,
+ __newindex = function (t,k,v)
+ if k == 'ipv6' then return option('NO_IPV6', not v)
+ elseif k == 'ipv4' then return option('NO_IPV4', not v)
+ else
+ local iname = rawget(net.interfaces(), v)
+ if iname then t.listen(iname)
+ else t.listen(v)
+ end
+ end
+ end
+})
+
+-- Syntactic sugar for module loading
+-- `modules.<name> = <config>`
+setmetatable(modules, {
+ __newindex = function (_, k, v)
+ if type(k) == 'number' then
+ k, v = v, nil
+ end
+ if not rawget(_G, k) then
+ modules.load(k)
+ k = string.match(k, '[%w_]+')
+ local mod = _G[k]
+ local config = mod and rawget(mod, 'config')
+ if mod ~= nil and config ~= nil then
+ if k ~= v then config(v)
+ else config()
+ end
+ end
+ end
+ end
+})
+
+
+cache.clear = function (name, exact_name, rr_type, chunk_size, callback, prev_state)
+ if name == nil or (name == '.' and not exact_name) then
+ -- keep same output format as for 'standard' clear
+ local total_count = cache.count()
+ if not cache.clear_everything() then
+ error('unable to clear everything')
+ end
+ return {count = total_count}
+ end
+ -- Check parameters, in order, and set defaults if missing.
+ local dname = kres.str2dname(name)
+ if not dname then error('cache.clear(): incorrect name passed') end
+ if exact_name == nil then exact_name = false end
+ if type(exact_name) ~= 'boolean'
+ then error('cache.clear(): incorrect exact_name passed') end
+
+ local cach = kres.context().cache;
+ local rettable = {}
+ -- Apex warning. If the caller passes a custom callback,
+ -- we assume they are advanced enough not to need the check.
+ -- The point is to avoid repeating the check in each callback iteration.
+ if callback == nil then
+ local apex_array = ffi.new('knot_dname_t *[1]') -- C: dname **apex_array
+ local ret = ffi.C.kr_cache_closest_apex(cach, dname, false, apex_array)
+ if ret < 0 then
+ error(ffi.string(ffi.C.knot_strerror(ret))) end
+ if not ffi.C.knot_dname_is_equal(apex_array[0], dname) then
+ local apex_str = kres.dname2str(apex_array[0])
+ rettable.not_apex = 'to clear proofs of non-existence call '
+ .. 'cache.clear(\'' .. tostring(apex_str) ..'\')'
+ rettable.subtree = apex_str
+ end
+ ffi.C.free(apex_array[0])
+ end
+
+ if rr_type ~= nil then
+ -- Special case, without any subtree searching.
+ if not exact_name
+ then error('cache.clear(): specifying rr_type only supported with exact_name') end
+ if chunk_size or callback
+ then error('cache.clear(): chunk_size and callback parameters not supported with rr_type') end
+ local ret = ffi.C.kr_cache_remove(cach, dname, rr_type)
+ if ret < 0 then error(ffi.string(ffi.C.knot_strerror(ret))) end
+ return {count = 1}
+ end
+
+ if chunk_size == nil then chunk_size = 100 end
+ if type(chunk_size) ~= 'number' or chunk_size <= 0
+ then error('cache.clear(): chunk_size has to be a positive integer') end
+
+ -- Do the C call, and add chunk_size warning.
+ rettable.count = ffi.C.kr_cache_remove_subtree(cach, dname, exact_name, chunk_size)
+ if rettable.count == chunk_size then
+ local msg_extra = ''
+ if callback == nil then
+ msg_extra = '; the default callback will continue asynchronously'
+ end
+ rettable.chunk_limit = 'chunk size limit reached' .. msg_extra
+ end
+
+ -- Default callback function: repeat after 1ms
+ if callback == nil then callback =
+ function (cbname, cbexact_name, cbrr_type, cbchunk_size, cbself, cbprev_state, cbrettable)
+ if cbrettable.count < 0 then error(ffi.string(ffi.C.knot_strerror(cbrettable.count))) end
+ if cbprev_state == nil then cbprev_state = { round = 0 } end
+ if type(cbprev_state) ~= 'table'
+ then error('cache.clear() callback: incorrect prev_state passed') end
+ cbrettable.round = cbprev_state.round + 1
+ if (cbrettable.count == cbchunk_size) then
+ event.after(1, function ()
+ cache.clear(cbname, cbexact_name, cbrr_type, cbchunk_size, cbself, cbrettable)
+ end)
+ elseif cbrettable.round > 1 then
+ log('[cache] asynchonous cache.clear(\'' .. cbname .. '\', '
+ .. tostring(cbexact_name) .. ') finished')
+ end
+ return cbrettable
+ end
+ end
+ return callback(name, exact_name, rr_type, chunk_size, callback, prev_state, rettable)
+end
+-- Syntactic sugar for cache
+-- `cache[x] -> cache.get(x)`
+-- `cache.{size|storage} = value`
+setmetatable(cache, {
+ __index = function (t, k)
+ local res = rawget(t, k)
+ if not res and not rawget(t, 'current_size') then return res end
+ -- Beware: t.get returns empty table on failure to find.
+ -- That would be confusing here (breaking kresc), so return nil instead.
+ res = t.get(k)
+ if res and next(res) ~= nil then return res else return nil end
+ end,
+ __newindex = function (t,k,v)
+ -- Defaults
+ local storage = rawget(t, 'current_storage')
+ if not storage then storage = 'lmdb://' end
+ local size = rawget(t, 'current_size')
+ if not size then size = 10*MB end
+ -- Declarative interface for cache
+ if k == 'size' then t.open(v, storage)
+ elseif k == 'storage' then t.open(size, v) end
+ end
+})
+
+-- Register module in Lua environment
+function modules_register(module)
+ -- Syntactic sugar for get() and set() properties
+ setmetatable(module, {
+ __index = function (t, k)
+ local v = rawget(t, k)
+ if v then return v
+ elseif rawget(t, 'get') then return t.get(k)
+ end
+ end,
+ __newindex = function (t, k, v)
+ local old_v = rawget(t, k)
+ if not old_v and rawget(t, 'set') then
+ t.set(k..' '..v)
+ end
+ end
+ })
+end
+
+-- Make sandboxed environment
+local function make_sandbox(defined)
+ local __protected = { worker = true, env = true, modules = true, cache = true, net = true, trust_anchors = true }
+
+ -- Compute and export the list of top-level names (hidden otherwise)
+ local nl = ""
+ for n in pairs(defined) do
+ nl = nl .. n .. "\n"
+ end
+
+ return setmetatable({ __orig_name_list = nl }, {
+ __index = defined,
+ __newindex = function (_, k, v)
+ if __protected[k] then
+ for k2,v2 in pairs(v) do
+ defined[k][k2] = v2
+ end
+ else
+ defined[k] = v
+ end
+ end
+ })
+end
+
+-- Compatibility sandbox
+if setfenv then -- Lua 5.1 and less
+ _G = make_sandbox(getfenv(0))
+ setfenv(0, _G)
+else -- Lua 5.2+
+ _SANDBOX = make_sandbox(_ENV)
+end
+
+-- Load embedded modules
+trust_anchors = require('trust_anchors')
+modules.load('ta_signal_query')
+modules.load('policy')
+modules.load('priming')
+modules.load('detect_time_skew')
+modules.load('detect_time_jump')
+modules.load('ta_sentinel')
+modules.load('edns_keepalive')
+
+-- Interactive command evaluation
+function eval_cmd(line, raw)
+ -- Compatibility sandbox code loading
+ local function load_code(code)
+ if getfenv then -- Lua 5.1
+ return loadstring(code)
+ else -- Lua 5.2+
+ return load(code, nil, 't', _ENV)
+ end
+ end
+ local err, chunk
+ chunk, err = load_code(raw and 'return '..line or 'return table_print('..line..')')
+ if err then
+ chunk, err = load_code(line)
+ end
+ if not err then
+ return chunk()
+ else
+ error(err)
+ end
+end
+
+-- Pretty printing
+function table_print (tt, indent, done)
+ done = done or {}
+ indent = indent or 0
+ local result = ""
+ -- Convert to printable string (escape unprintable)
+ local function printable(value)
+ value = tostring(value)
+ local bytes = {}
+ for i = 1, #value do
+ local c = string.byte(value, i)
+ if c >= 0x20 and c < 0x7f then table.insert(bytes, string.char(c))
+ else table.insert(bytes, '\\'..tostring(c))
+ end
+ if i > 80 then table.insert(bytes, '...') break end
+ end
+ return table.concat(bytes)
+ end
+ if type(tt) == "table" then
+ for key, value in pairs (tt) do
+ result = result .. string.rep (" ", indent)
+ if type (value) == "table" and not done [value] then
+ done [value] = true
+ result = result .. string.format("[%s] => {\n", printable (key))
+ result = result .. table_print (value, indent + 4, done)
+ result = result .. string.rep (" ", indent)
+ result = result .. "}\n"
+ else
+ result = result .. string.format("[%s] => %s\n",
+ tostring (key), printable(value))
+ end
+ end
+ else
+ result = result .. tostring(tt) .. "\n"
+ end
+ return result
+end
+
+-- This extends the worker module to allow asynchronous execution of functions and nonblocking I/O.
+-- The current implementation combines cqueues for Lua interface, and event.socket() in order to not
+-- block resolver engine while waiting for I/O or timers.
+--
+local has_cqueues, cqueues = pcall(require, 'cqueues')
+if has_cqueues then
+
+ -- Export the asynchronous sleep function
+ worker.sleep = cqueues.sleep
+
+ -- Create metatable for workers to define the API
+ -- It can schedule multiple cqueues and yield execution when there's a wait for blocking I/O or timer
+ local asynchronous_worker_mt = {
+ work = function (self)
+ local ok, err, _, co = self.cq:step(0)
+ if not ok then
+ warn('[%s] error: %s %s', self.name or 'worker', err, debug.traceback(co))
+ end
+ -- Reschedule timeout or create new one
+ local timeout = self.cq:timeout()
+ if timeout then
+ -- Throttle timeouts to avoid too frequent wakeups
+ if timeout == 0 then timeout = 0.00001 end
+ -- Convert from seconds to duration
+ timeout = timeout * sec
+ if not self.next_timeout then
+ self.next_timeout = event.after(timeout, self.on_step)
+ else
+ event.reschedule(self.next_timeout, timeout)
+ end
+ else -- Cancel running timeout when there is no next deadline
+ if self.next_timeout then
+ event.cancel(self.next_timeout)
+ self.next_timeout = nil
+ end
+ end
+ end,
+ wrap = function (self, f)
+ self.cq:wrap(f)
+ end,
+ loop = function (self)
+ self.on_step = function () self:work() end
+ self.event_fd = event.socket(self.cq:pollfd(), self.on_step)
+ end,
+ close = function (self)
+ if self.event_fd then
+ event.cancel(self.event_fd)
+ self.event_fd = nil
+ end
+ end,
+ }
+
+ -- Implement the coroutine worker with cqueues
+ local function worker_new (name)
+ return setmetatable({name = name, cq = cqueues.new()}, { __index = asynchronous_worker_mt })
+ end
+
+ -- Create a default background worker
+ worker.bg_worker = worker_new('worker.background')
+ worker.bg_worker:loop()
+
+ -- Wrap a function for asynchronous execution
+ function worker.coroutine (f)
+ worker.bg_worker:wrap(f)
+ end
+else
+ -- Disable asynchronous execution
+ local function disabled () error('cqueues are required for asynchronous execution') end
+ worker.sleep = disabled
+ worker.map = disabled
+ worker.coroutine = disabled
+end
diff --git a/daemon/lua/trust_anchors.lua.in b/daemon/lua/trust_anchors.lua.in
new file mode 100644
index 0000000..1d16018
--- /dev/null
+++ b/daemon/lua/trust_anchors.lua.in
@@ -0,0 +1,663 @@
+-- Load the module
+local ffi = require 'ffi'
+local kres = require('kres')
+local C = ffi.C
+
+local trust_anchors -- the public pseudo-module, exported as global variable
+
+-- Fetch over HTTPS with peert cert checked
+local function https_fetch(url, ca)
+ local ssl_ok, https = pcall(require, 'ssl.https')
+ local ltn_ok, ltn12 = pcall(require, 'ltn12')
+ if not ssl_ok or not ltn_ok then
+ return nil, 'luasec and luasocket needed for root TA bootstrap'
+ end
+ local resp = {}
+ local r, c = https.request{
+ url = url,
+ cafile = ca,
+ verify = {'peer', 'fail_if_no_peer_cert' },
+ protocol = 'tlsv1_2',
+ sink = ltn12.sink.table(resp),
+ }
+ if r == nil then return r, c end
+ return resp[1]
+end
+
+-- remove UTC timezone specification if present or throw error
+local function time2utc(orig_timespec)
+ local patterns = {'[+-]00:00$', 'Z$'}
+ for _, pattern in ipairs(patterns) do
+ local timespec, removals = string.gsub(orig_timespec, pattern, '')
+ if removals == 1 then
+ return timespec
+ end
+ end
+ error(string.format('unsupported time specification: %s', orig_timespec))
+end
+
+local function keydigest_is_valid(valid_from, valid_until)
+ local format = '%Y-%m-%dT%H:%M:%S'
+ local time_now = os.date('!%Y-%m-%dT%H:%M:%S') -- ! forces UTC
+ local time_diff = ffi.new('double[1]')
+ local err = ffi.C.kr_strptime_diff(
+ format, time_now, time2utc(valid_from), time_diff)
+ if (err ~= nil) then
+ error(string.format('failed to process "validFrom" constraint: %s',
+ ffi.string(err)))
+ end
+ local from_ok = time_diff[0] > 0
+
+ -- optional attribute
+ local until_ok = true
+ if valid_until then
+ err = ffi.C.kr_strptime_diff(
+ format, time_now, time2utc(valid_until), time_diff)
+ if (err ~= nil) then
+ error(string.format('failed to process "validUntil" constraint: %s',
+ ffi.string(err)))
+ end
+ until_ok = time_diff[0] < 0
+ end
+ return from_ok and until_ok
+end
+
+local function parse_xml_keydigest(attrs, inside, output)
+ local fields = {}
+ local _, n = string.gsub(attrs, "([%w]+)=\"([^\"]*)\"", function (k, v) fields[k] = v end)
+ assert(n >= 1,
+ string.format('cannot parse XML attributes from "%s"', attrs))
+ assert(fields['validFrom'],
+ string.format('mandatory KeyDigest XML attribute validFrom ' ..
+ 'not found in "%s"', attrs))
+ local valid_attrs = {id = true, validFrom = true, validUntil = true}
+ for key, _ in pairs(fields) do
+ assert(valid_attrs[key],
+ string.format('unsupported KeyDigest attribute "%s" found in "%s"',
+ key, attrs))
+ end
+
+ _, n = string.gsub(inside, "<([%w]+).->([^<]+)</[%w]+>", function (k, v) fields[k] = v end)
+ assert(n >= 1,
+ string.format('error parsing KeyDigest XML elements from "%s"',
+ inside))
+ local mandatory_elements = {'KeyTag', 'Algorithm', 'DigestType', 'Digest'}
+ for _, key in ipairs(mandatory_elements) do
+ assert(fields[key],
+ string.format('mandatory element %s is missing in "%s"',
+ key, inside))
+ end
+ assert(n == 4, string.format('found %d elements but expected 4 in %s', n, inside))
+ table.insert(output, fields) -- append to list of parsed keydigests
+end
+
+local function generate_ds(keydigests)
+ local rrset = ''
+ for _, fields in ipairs(keydigests) do
+ local rr = string.format(
+ '. 0 IN DS %s %s %s %s',
+ fields.KeyTag, fields.Algorithm, fields.DigestType, fields.Digest)
+ if keydigest_is_valid(fields['validFrom'], fields['validUntil']) then
+ rrset = rrset .. '\n' .. rr
+ else
+ log('[ ta ] skipping trust anchor "%s" ' ..
+ 'because it is outside of validity range', rr)
+ end
+ end
+ return rrset
+end
+
+local function assert_str_match(str, pattern, expected)
+ local count = 0
+ for _ in string.gmatch(str, pattern) do
+ count = count + 1
+ end
+ assert(count == expected,
+ string.format('expected %d occurences of "%s" but got %d in "%s"',
+ expected, pattern, count, str))
+end
+
+-- Fetch root anchors in XML over HTTPS, returning a zone-file-style string
+-- or false in case of error, and a message.
+local function bootstrap(url, ca)
+ -- RFC 7958, sec. 2, but we don't do precise XML parsing.
+ -- @todo ICANN certificate is verified against current CA
+ -- this is not ideal, as it should rather verify .xml signature which
+ -- is signed by ICANN long-lived cert, but luasec has no PKCS7
+ local xml, err = https_fetch(url, ca)
+ if not xml then
+ return false, string.format('[ ta ] fetch of "%s" failed: %s', url, err)
+ end
+
+ -- we support only minimal subset of https://tools.ietf.org/html/rfc7958
+ assert_str_match(xml, '<?xml version="1%.0" encoding="UTF%-8"%?>', 1)
+ assert_str_match(xml, '<TrustAnchor ', 1)
+ assert_str_match(xml, '<Zone>.</Zone>', 1)
+ assert_str_match(xml, '</TrustAnchor>', 1)
+
+ -- Parse root trust anchor, one digest at a time, converting to a zone-file-style string.
+ local keydigests = {}
+ string.gsub(xml, "<KeyDigest([^>]*)>(.-)</KeyDigest>", function(attrs, inside)
+ parse_xml_keydigest(attrs, inside, keydigests)
+ end)
+ local rrset = generate_ds(keydigests)
+ if rrset == '' then
+ return false, string.format('[ ta ] no valid trust anchors found at "%s"', url)
+ end
+ local msg = '[ ta ] Root trust anchors bootstrapped over https with pinned certificate.\n'
+ .. ' You SHOULD verify them manually against original source:\n'
+ .. ' https://www.iana.org/dnssec/files\n'
+ .. '[ ta ] Current root trust anchors are:'
+ .. rrset
+ return rrset, msg
+end
+
+-- RFC5011 state table
+local key_state = {
+ Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
+ Missing = 'Missing', Revoked = 'Revoked', Removed = 'Removed'
+}
+
+-- Find key in current keyset
+local function ta_find(keyset, rr)
+ local rr_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
+ assert(rr_tag >= 0 and rr_tag <= 65535, string.format('invalid RR: %s: %s',
+ kres.rr2str(rr), ffi.string(C.knot_strerror(rr_tag))))
+ for i, ta in ipairs(keyset) do
+ -- Match key owner and content
+ local ta_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
+ assert(ta_tag >= 0 and ta_tag <= 65535, string.format('invalid RR: %s: %s',
+ kres.rr2str(ta), ffi.string(C.knot_strerror(ta_tag))))
+ if ta.owner == rr.owner then
+ if ta.type == rr.type then
+ if rr.type == kres.type.DNSKEY then
+ if C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
+ return ta
+ end
+ elseif rr.type == kres.type.DS and ta.rdata == rr.rdata then
+ return ta
+ end
+ -- DNSKEY superseding DS, inexact match
+ elseif rr.type == kres.type.DNSKEY and ta.type == kres.type.DS then
+ if ta.key_tag == rr_tag then
+ keyset[i] = rr -- Replace current DS
+ rr.state = ta.state
+ rr.key_tag = ta.key_tag
+ return rr
+ end
+ -- DS key matching DNSKEY, inexact match
+ elseif rr.type == kres.type.DS and ta.type == kres.type.DNSKEY then
+ if rr_tag == ta_tag then
+ return ta
+ end
+ end
+ end
+ end
+ return nil
+end
+
+-- Evaluate TA status of a RR according to RFC5011. The time is in seconds.
+local function ta_present(keyset, rr, hold_down_time, force_valid)
+ if rr.type == kres.type.DNSKEY and not C.kr_dnssec_key_ksk(rr.rdata) then
+ return false -- Ignore
+ end
+ -- Find the key in current key set and check its status
+ local now = os.time()
+ local key_revoked = (rr.type == kres.type.DNSKEY) and C.kr_dnssec_key_revoked(rr.rdata)
+ local key_tag = C.kr_dnssec_key_tag(rr.type, rr.rdata, #rr.rdata)
+ assert(key_tag >= 0 and key_tag <= 65535, string.format('invalid RR: %s: %s',
+ kres.rr2str(rr), ffi.string(C.knot_strerror(key_tag))))
+ local ta = ta_find(keyset, rr)
+ if ta then
+ -- Key reappears (KeyPres)
+ if ta.state == key_state.Missing then
+ ta.state = key_state.Valid
+ ta.timer = nil
+ end
+ -- Key is revoked (RevBit)
+ if ta.state == key_state.Valid or ta.state == key_state.Missing then
+ if key_revoked then
+ ta.state = key_state.Revoked
+ ta.timer = now + hold_down_time
+ end
+ end
+ -- Remove hold-down timer expires (RemTime)
+ if ta.state == key_state.Revoked and os.difftime(ta.timer, now) <= 0 then
+ ta.state = key_state.Removed
+ ta.timer = nil
+ end
+ -- Add hold-down timer expires (AddTime)
+ if ta.state == key_state.AddPend and os.difftime(ta.timer, now) <= 0 then
+ ta.state = key_state.Valid
+ ta.timer = nil
+ end
+ if rr.state ~= key_state.Valid or verbose() then
+ log('[ ta ] key: ' .. key_tag .. ' state: '..ta.state)
+ end
+ return true
+ elseif not key_revoked then -- First time seen (NewKey)
+ rr.key_tag = key_tag
+ if force_valid then
+ rr.state = key_state.Valid
+ else
+ rr.state = key_state.AddPend
+ rr.timer = now + hold_down_time
+ end
+ if rr.state ~= key_state.Valid or verbose() then
+ log('[ ta ] key: ' .. key_tag .. ' state: '..rr.state)
+ end
+ table.insert(keyset, rr)
+ return true
+ end
+ return false
+end
+
+-- TA is missing in the new key set. The time is in seconds.
+local function ta_missing(ta, hold_down_time)
+ -- Key is removed (KeyRem)
+ local keep_ta = true
+ local key_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
+ assert(key_tag >= 0 and key_tag <= 65535, string.format('invalid RR: %s: %s',
+ kres.rr2str(ta), ffi.string(C.knot_strerror(key_tag))))
+ if ta.state == key_state.Valid then
+ ta.state = key_state.Missing
+ ta.timer = os.time() + hold_down_time
+
+ -- Remove key that is missing for too long
+ elseif ta.state == key_state.Missing and os.difftime(ta.timer, os.time()) <= 0 then
+ ta.state = key_state.Removed
+ log('[ ta ] key: '..key_tag..' removed because missing for too long')
+ keep_ta = false
+
+ -- Purge pending key
+ elseif ta.state == key_state.AddPend then
+ log('[ ta ] key: '..key_tag..' purging')
+ keep_ta = false
+ end
+ log('[ ta ] key: '..key_tag..' state: '..ta.state)
+ return keep_ta
+end
+
+local active_refresh, update -- forwards
+
+-- Plan an event for refreshing the root DNSKEYs and re-scheduling itself
+local function refresh_plan(keyset, delay, is_initial)
+ local owner_str = kres.dname2str(keyset.owner) -- maybe fix converting back and forth?
+ keyset.refresh_ev = event.after(delay, function ()
+ resolve(owner_str, kres.type.DNSKEY, kres.class.IN, 'NO_CACHE',
+ function (pkt)
+ -- Schedule itself with updated timeout
+ local delay_new = active_refresh(keyset, kres.pkt_t(pkt), is_initial)
+ delay_new = keyset.refresh_time or trust_anchors.refresh_time or delay_new
+ log('[ ta ] next refresh for ' .. owner_str .. ' in '
+ .. delay_new/hour .. ' hours')
+ refresh_plan(keyset, delay_new)
+ end)
+ end)
+end
+
+-- Refresh the DNSKEYs from the packet, and return time to the next check.
+active_refresh = function (keyset, pkt, is_initial)
+ local retry = true
+ if pkt:rcode() == kres.rcode.NOERROR then
+ local records = pkt:section(kres.section.ANSWER)
+ local new_keys = {}
+ for _, rr in ipairs(records) do
+ if rr.type == kres.type.DNSKEY then
+ table.insert(new_keys, rr)
+ end
+ end
+ update(keyset, new_keys, is_initial)
+ retry = false
+ else
+ warn('[ ta ] active refresh failed for ' .. kres.dname2str(keyset.owner)
+ .. ' with rcode: ' .. pkt:rcode())
+ end
+ -- Calculate refresh/retry timer (RFC 5011, 2.3)
+ local min_ttl = retry and day or 15 * day
+ for _, rr in ipairs(keyset) do -- 10 or 50% of the original TTL
+ min_ttl = math.min(min_ttl, (retry and 100 or 500) * rr.ttl)
+ end
+ return math.max(hour, min_ttl)
+end
+
+-- Write keyset to a file. States and timers are stored in comments.
+local function keyset_write(keyset)
+ if not keyset.filename then return false end -- not to be persisted
+ local fname_tmp = keyset.filename .. '.lock.' .. tostring(worker.pid);
+ local file = assert(io.open(fname_tmp, 'w'))
+ for i = 1, #keyset do
+ local ta = keyset[i]
+ ta.comment = ' ' .. ta.state .. ':' .. (ta.timer or '')
+ .. ' ; KeyTag:' .. ta.key_tag -- the tag is just for humans
+ local rr_str = kres.rr2str(ta) .. '\n'
+ if ta.state ~= key_state.Valid and ta.state ~= key_state.Missing then
+ rr_str = '; '..rr_str -- Invalidate key string (for older kresd versions)
+ end
+ file:write(rr_str)
+ end
+ file:close()
+ assert(os.rename(fname_tmp, keyset.filename))
+end
+
+-- Search the values of a table and return the corrseponding key (or nil).
+local function table_search(t, val)
+ for k, v in pairs(t) do
+ if v == val then
+ return k
+ end
+ end
+ return nil
+end
+
+-- For each RR, parse .state and .timer from .comment.
+local function keyset_parse_comments(tas, default_state)
+ for _, ta in pairs(tas) do
+ ta.state = default_state
+ if ta.comment then
+ string.gsub(ta.comment, '^%s*(%a+):(%d*)', function (state, time)
+ if table_search(key_state, state) then
+ ta.state = state
+ end
+ ta.timer = tonumber(time) -- nil on failure
+ end)
+ ta.comment = nil
+ end
+ end
+ return tas
+end
+
+-- Read keyset from a file. (This includes the key states and timers.)
+local function keyset_read(path)
+ -- First load the regular entries, trusting them.
+ local zonefile = require('zonefile')
+ local tas, err = zonefile.file(path)
+ if not tas then
+ return tas, err
+ end
+ keyset_parse_comments(tas, key_state.Valid)
+
+ -- The untrusted keys are commented out but important to load.
+ for line in io.lines(path) do
+ if line:sub(1, 2) == '; ' then
+ -- Ignore the line if it fails to parse including recognized .state.
+ local l_set = zonefile.string(line:sub(3))
+ if l_set and l_set[1] then
+ keyset_parse_comments(l_set)
+ if l_set[1].state then
+ table.insert(tas, l_set[1])
+ end
+ end
+ end
+ end
+
+ for _, ta in pairs(tas) do
+ local ta_keytag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
+ if not (ta_keytag >= 0 and ta_keytag <= 65535) then
+ return nil, string.format('invalid key: "%s": %s',
+ kres.rr2str(ta), ffi.string(C.knot_strerror(ta_keytag)))
+ end
+ ta.key_tag = ta_keytag
+ end
+ return tas
+end
+
+-- Replace current TAs for given owner by the "trusted" ones from passed keyset.
+-- Return the number of trusted keys for the owner.
+local function keyset_publish(keyset)
+ local store = kres.context().trust_anchors
+ local count = 0
+ C.kr_ta_del(store, keyset.owner)
+ for _, ta in ipairs(keyset) do
+ -- Key MAY be used as a TA only in these two states (RFC5011, 4.2)
+ if ta.state == key_state.Valid or ta.state == key_state.Missing then
+ if C.kr_ta_add(store, ta.owner, ta.type, ta.ttl, ta.rdata, #ta.rdata) == 0 then
+ count = count + 1
+ end
+ end
+ end
+ if count == 0 then
+ warn('[ ta ] ERROR: no anchors are trusted for ' ..
+ kres.dname2str(keyset.owner) .. ' !')
+ end
+ return count
+end
+
+
+-- Update existing keyset; return true if successful.
+-- Param `is_initial` (bool): force .NewKey states to .Valid, i.e. init empty keyset.
+update = function (keyset, new_keys, is_initial)
+ if not new_keys then return false end
+
+ -- Filter TAs to be purged from the keyset (KeyRem), in three steps
+ -- 1: copy TAs to be kept to `keepset`
+ local hold_down = (keyset.hold_down_time or trust_anchors.hold_down_time) / 1000
+ local keepset = {}
+ local keep_removed = keyset.keep_removed or trust_anchors.keep_removed
+ for _, ta in ipairs(keyset) do
+ local keep = true
+ if not ta_find(new_keys, ta) then
+ -- Ad-hoc: RFC 5011 doesn't mention removing a Missing key.
+ -- Let's do it after a very long period has elapsed.
+ keep = ta_missing(ta, hold_down * 4)
+ end
+ -- Purge removed keys
+ if ta.state == key_state.Removed then
+ if keep_removed > 0 then
+ keep_removed = keep_removed - 1
+ else
+ keep = false
+ end
+ end
+ if keep then
+ table.insert(keepset, ta)
+ end
+ end
+ -- 2: remove all TAs - other settings etc. will remain in the keyset
+ for i, _ in ipairs(keyset) do
+ keyset[i] = nil
+ end
+ -- 3: move TAs to be kept into the keyset (same indices)
+ for k, ta in pairs(keepset) do
+ keyset[k] = ta
+ end
+
+ -- Evaluate new TAs
+ for _, rr in ipairs(new_keys) do
+ if (rr.type == kres.type.DNSKEY or rr.type == kres.type.DS) and rr.rdata ~= nil then
+ ta_present(keyset, rr, hold_down, is_initial)
+ end
+ end
+
+ -- Store the keyset
+ keyset_write(keyset)
+
+ -- Start using the new TAs.
+ if keyset_publish(keyset) == 0 then
+ -- TODO: try to rebootstrap if for root?
+ return false
+ elseif verbose() then
+ log('[ ta ] refreshed trust anchors for domain ' .. kres.dname2str(keyset.owner) .. ' are:\n'
+ .. trust_anchors.summary(keyset.owner))
+ end
+
+ return true
+end
+
+local add_file = function (path, unmanaged)
+ if not unmanaged then
+ if not io.open(path .. '.lock', 'w') then
+ error("[ ta ] ERROR: write access needed to keyfile dir '"..path.."'")
+ end
+ os.remove(path .. ".lock")
+ end
+
+ -- Bootstrap if requested and keyfile doesn't exist
+ if not unmanaged and not io.open(path, 'r') then
+ log("[ ta ] keyfile '%s': doesn't exist, bootstrapping", path);
+ local tas, msg = bootstrap(trust_anchors.bootstrap_url, trust_anchors.bootstrap_ca)
+ if not tas then
+ msg = msg .. '\n'
+ .. '[ ta ] Failed to bootstrap root trust anchors; see:\n'
+ .. ' https://knot-resolver.readthedocs.io/en/latest/daemon.html#enabling-dnssec'
+ error(msg)
+ end
+ print(msg)
+ trustanchor(tas)
+ -- Fetch DNSKEY immediately
+ if not trust_anchors.keysets['\0'] then
+ trust_anchors.keysets['\0'] = { owner = '\0' }
+ end
+ local keyset = trust_anchors.keysets['\0']
+ keyset.filename = path
+ if keyset.refresh_ev then event.cancel(keyset.refresh_ev) end
+ refresh_plan(keyset, 0, true)
+ return
+ end
+ if not unmanaged and path == (trust_anchors.keysets['\0'] or {}).filename then
+ return
+ end
+
+ -- Parse the file and check its sanity
+ local keyset, err = keyset_read(path)
+ if not keyset then
+ panic("[ ta ] ERROR: failed to read anchors from '%s' (%s)", path, err)
+ end
+ if not unmanaged then keyset.filename = path end
+ if not keyset[1] then
+ panic("[ ta ] ERROR: failed to read anchors from '%s'", path)
+ end
+ if not unmanaged then keyset.filename = path end
+ local owner = keyset[1].owner
+ for _, ta in ipairs(keyset) do
+ if ta.owner ~= owner then
+ panic("[ ta ] ERROR: mixed owner names found in file '%s'! " ..
+ "Do not mix %s and %s TAs in single file",
+ path, kres.dname2str(ta.owner), kres.dname2str(owner))
+ end
+ end
+ keyset.owner = owner
+
+ local owner_str = kres.dname2str(owner)
+ if trust_anchors.keysets[owner] then
+ warn('[ ta ] warning: overriding previously set trust anchors for ' .. owner_str)
+ local refresh_ev = trust_anchors.keysets[owner].refresh_ev
+ if refresh_ev then event.cancel(refresh_ev) end
+ end
+ trust_anchors.keysets[owner] = keyset
+
+ -- Parse new keys, refresh eventually
+ if keyset_publish(keyset) ~= 0 and verbose() then
+ log('[ ta ] installed trust anchors for domain ' .. owner_str .. ' are:\n'
+ .. trust_anchors.summary(owner))
+ end
+ -- TODO: if failed and for root, try to rebootstrap?
+
+ refresh_plan(keyset, 10 * sec, false)
+end
+
+local function ta_str(owner)
+ local owner_str = kres.dname2str(owner) .. ' '
+ local msg = ''
+ for _, nta in pairs(trust_anchors.insecure) do
+ if owner == kres.str2dname(nta) then
+ msg = owner_str .. 'is negative trust anchor\n'
+ end
+ end
+ if not trust_anchors.keysets[owner] then
+ if #msg > 0 then -- it is normal that NTA does not have explicit TA
+ return msg
+ else
+ return owner_str .. 'has no explicit trust anchors\n'
+ end
+ end
+ if #msg > 0 then
+ msg = msg .. 'WARNING! negative trust anchor also has an explicit TA\n'
+ end
+ for _, ta in ipairs(trust_anchors.keysets[owner]) do
+ msg = msg .. kres.rr2str(ta) .. '\n'
+ end
+ return msg
+end
+
+-- TA store management, for user docs see ../README.rst
+trust_anchors = {
+ -- [internal] table indexed by dname;
+ -- each item is a list of RRs and additionally contains:
+ -- - owner - that dname (for simplicity)
+ -- - [optional] filename in which to persist the state
+ -- - [optional] overrides for global defaults of
+ -- hold_down_time, refresh_time, keep_removed
+ -- The RR tables also contain some additional TA-specific fields.
+ keysets = {},
+
+ -- Documented properties:
+ insecure = {},
+ hold_down_time = 30 * day,
+ refresh_time = nil,
+ keep_removed = 0,
+
+ bootstrap_url = 'https://data.iana.org/root-anchors/root-anchors.xml',
+ bootstrap_ca = '@ETCDIR@/icann-ca.pem',
+ -- change empty string to nil
+ keyfile_default = ('@KEYFILE_DEFAULT@' ~= '' and '@KEYFILE_DEFAULT@') or nil,
+
+ -- Load keys from a file, 5011-managed by default.
+ -- If managed and the file doesn't exist, try bootstrapping the root into it.
+ add_file = add_file,
+ config = add_file,
+
+ -- Add DS/DNSKEY record(s) (unmanaged)
+ add = function (keystr)
+ local ret = trustanchor(keystr)
+ if verbose() then log(trust_anchors.summary()) end
+ return ret
+ end,
+ -- Negative TA management
+ set_insecure = function (list)
+ assert(type(list) == 'table', 'parameter must be list of domain names (e.g. {"a.test", "b.example"})')
+ local store = kres.context().negative_anchors
+ C.kr_ta_clear(store)
+ for i = 1, #list do
+ local dname = kres.str2dname(list[i])
+ C.kr_ta_add(store, dname, kres.type.DS, 0, nil, 0)
+ end
+ trust_anchors.insecure = list
+ end,
+ summary = function (single_owner)
+ if single_owner then -- single domain
+ return ta_str(single_owner)
+ end
+
+ -- all domains
+ local msg = ''
+ local ta_count = 0
+ local seen = {}
+ for _, nta_str in pairs(trust_anchors.insecure) do
+ local owner = kres.str2dname(nta_str)
+ seen[owner] = true
+ msg = msg .. ta_str(owner)
+ end
+ for owner, _ in pairs(trust_anchors.keysets) do
+ if not seen[owner] then
+ ta_count = ta_count + 1
+ msg = msg .. ta_str(owner)
+ end
+ end
+ if ta_count == 0 then
+ msg = msg .. 'No valid trust anchors, DNSSEC validation is disabled\n'
+ end
+ return msg
+ end,
+}
+
+-- Syntactic sugar for TA store
+setmetatable(trust_anchors, {
+ __newindex = function (t,k,v)
+ if k == 'file' then t.config(v)
+ elseif k == 'negative' then t.set_insecure(v)
+ else rawset(t, k, v) end
+ end,
+})
+
+return trust_anchors
diff --git a/daemon/lua/trust_anchors.test/bootstrap.test.lua b/daemon/lua/trust_anchors.test/bootstrap.test.lua
new file mode 100644
index 0000000..d5d0218
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/bootstrap.test.lua
@@ -0,0 +1,108 @@
+-- check prerequisites
+local has_http = pcall(require, 'http') and pcall(require, 'http.request')
+if not has_http then
+ pass('skipping bootstrap tests because http module is not not installed')
+ done()
+end
+
+local cqueues = require("cqueues")
+local socket = require("cqueues.socket")
+
+-- unload modules which are not related to this test
+if ta_signal_query then
+ modules.unload('ta_signal_query')
+end
+if priming then
+ modules.unload('priming')
+end
+if detect_time_skew then
+ modules.unload('detect_time_skew')
+end
+
+-- Self-checks on globals
+assert(help() ~= nil)
+assert(worker.id ~= nil)
+-- Self-checks on facilities
+assert(worker.stats() ~= nil)
+assert(net.interfaces() ~= nil)
+-- Self-checks on loaded stuff
+assert(#modules.list() > 0)
+-- Self-check timers
+ev = event.recurrent(1 * sec, function () return 1 end)
+event.cancel(ev)
+ev = event.after(0, function () return 1 end)
+
+
+-- do not attempt to contact outside world using DNS, operate only on cache
+net.ipv4 = false
+net.ipv6 = false
+-- do not listen, test is driven by config code
+env.KRESD_NO_LISTEN = true
+
+-- start test webserver
+local function start_webserver()
+ -- srvout = io.popen('luajit webserv.lua')
+ -- TODO
+ os.execute('luajit webserv.lua &')
+ -- assert(srvout, 'failed to start webserver')
+end
+
+local function wait_for_webserver()
+ local starttime = os.time()
+ local connected = false
+ while not connected and os.difftime(os.time(), starttime) < 5 do
+ local con = socket.connect("localhost", 8080)
+ connected, msg = pcall(con.connect, con, 5)
+ cqueues.sleep (0.3)
+ end
+ assert(connected, string.format('unable to connect to web server: %s', msg))
+end
+
+local host = 'https://localhost:8080/'
+-- avoid interference with configured KEYFILE_DEFAULT
+trust_anchors.keyfile_default = nil
+
+local function test_err_cert()
+ trust_anchors.bootstrap_ca = 'x509/wrongca.pem'
+ trust_anchors.bootstrap_url = host .. 'ok1.xml'
+ boom(trust_anchors.add_file, {'ok1.keys'},
+ 'fake server certificate is detected')
+end
+
+local function test_err_xml(testname, testdesc)
+ return function()
+ trust_anchors.bootstrap_ca = 'x509/ca.pem'
+ trust_anchors.bootstrap_url = host .. testname .. '.xml'
+ boom(trust_anchors.add_file, {testname .. '.keys'}, testdesc)
+ end
+end
+
+-- dumb test, right now it cannot check content of keys because
+-- it does not get written until refresh fetches DNSKEY from network
+-- (and bypassing network using policy bypasses also validation
+-- so it does not test anything)
+local function test_ok_xml(testname, testdesc)
+ return function()
+ trust_anchors.bootstrap_url = host .. testname .. '.xml'
+ same(trust_anchors.add_file(testname .. '.keys'), nil, testdesc)
+ end
+end
+
+return {
+ start_webserver,
+ wait_for_webserver,
+ test_err_cert,
+ test_err_xml('err_attr_extra_attr', 'bogus TA XML with an extra attribute'),
+ test_err_xml('err_attr_validfrom_invalid', 'bogus TA XML with invalid validFrom value'),
+ test_err_xml('err_attr_validfrom_missing', 'bogus TA XML without mandatory validFrom attribute'),
+ test_err_xml('err_elem_extra', 'bogus TA XML with an extra element'),
+ test_err_xml('err_elem_missing', 'bogus TA XML without mandatory element'),
+ test_err_xml('err_multi_ta', 'bogus TA XML with multiple TAs'),
+ test_err_xml('unsupp_nonroot', 'unsupported TA XML for non-root zone'),
+ test_err_xml('unsupp_xml_v11', 'unsupported TA XML with XML v1.1'),
+ test_err_xml('ok0_badtimes', 'TA XML with no valid keys'),
+ test_ok_xml('ok1_expired1', 'TA XML with 1 valid and 1 expired key'),
+ test_ok_xml('ok1_notyet1', 'TA XML with 1 valid and 1 not yet valid key'),
+ test_ok_xml('ok1', 'TA XML with 1 valid key'),
+ test_ok_xml('ok2', 'TA XML with 2 valid keys'),
+}
diff --git a/daemon/lua/trust_anchors.test/err_attr_extra_attr.xml b/daemon/lua/trust_anchors.test/err_attr_extra_attr.xml
new file mode 100644
index 0000000..2a87957
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/err_attr_extra_attr.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="FC4A93EC-9F4E-4597-A766-AD6723E4A56E" source="https://localhost/err_attr_extra_attr.xml">
+<Zone>.</Zone>
+<KeyDigest unknownattr="test" id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
+<KeyTag>19036</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
+</KeyDigest>
+<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
+<KeyTag>20326</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml b/daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml
new file mode 100644
index 0000000..5a4c68c
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/err_attr_validfrom_invalid.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="ABD668AB-52DF-4A59-80E3-16CE6341BC55" source="https://localhost/err_attr_validfrom_invalid.xml">
+<Zone>.</Zone>
+<KeyDigest id="Kjqmt7v" validFrom="2010-07-32T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
+<KeyTag>19036</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
+</KeyDigest>
+<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
+<KeyTag>20326</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml b/daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml
new file mode 100644
index 0000000..1261b09
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/err_attr_validfrom_missing.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="3513058C-4041-40CC-AF0A-D3CCD70F962B" source="https://localhost/err_attr_validfrom_missing.xml">
+<Zone>.</Zone>
+<KeyDigest id="Kjqmt7v" validUntil="2019-01-11T00:00:00+00:00">
+<KeyTag>19036</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
+</KeyDigest>
+<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
+<KeyTag>20326</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/err_elem_extra.xml b/daemon/lua/trust_anchors.test/err_elem_extra.xml
new file mode 100644
index 0000000..150a3b1
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/err_elem_extra.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="B1854D58-1867-4FA7-872F-0099D394114D" source="https://localhost/err_elem_extra.xml">
+<Zone>.</Zone>
+<KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
+<KeyTag>19036</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
+</KeyDigest>
+<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
+<KeyTag>20326</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
+<UnknownElement>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</UnknownElement>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/err_elem_missing.xml b/daemon/lua/trust_anchors.test/err_elem_missing.xml
new file mode 100644
index 0000000..899e1d0
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/err_elem_missing.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="BB074095-3A42-4B13-9CC1-CFFF644D4D54" source="https://localhost/err_elem_missing.xml">
+<Zone>.</Zone>
+<KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
+<KeyTag>19036</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
+</KeyDigest>
+<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
+<KeyTag>20326</KeyTag>
+<Algorithm>8</Algorithm>
+<!-- this element is missing: DigestType>2</DigestType-->
+<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/err_multi_ta.xml b/daemon/lua/trust_anchors.test/err_multi_ta.xml
new file mode 100644
index 0000000..20cd73f
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/err_multi_ta.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="9DCE46E1-FC78-48E1-81B5-94E328790BB5" source="https://localhost/err_multi_ta.xml">
+<Zone>.</Zone>
+<KeyDigest id="1" validFrom="2000-02-02T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+</TrustAnchor>
+<TrustAnchor id="9DCE46E1-FC78-48E1-81B5-94E328790BB5" source="https://localhost/err_multi_ta.xml">
+<Zone>test.</Zone>
+<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/ok0_badtimes.xml b/daemon/lua/trust_anchors.test/ok0_badtimes.xml
new file mode 100644
index 0000000..4535a41
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/ok0_badtimes.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="EDEDAA08-D2A0-421E-81DC-AF11F5A0CDCD" source="https://localhost/ok0_badtimes.xml">
+<Zone>.</Zone>
+<KeyDigest id="E" validFrom="2000-01-01T00:00:00+00:00" validUntil="2000-01-01T00:00:00+00:00">
+<KeyTag>1</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE</Digest>
+</KeyDigest>
+<KeyDigest id="F" validFrom="2001-01-01T00:00:00+00:00" validUntil="2001-01-01T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/ok1.xml b/daemon/lua/trust_anchors.test/ok1.xml
new file mode 100644
index 0000000..117495c
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/ok1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="82E6CB77-12DF-4E61-BF49-367FB95A8BAA" source="https://localhost/ok1.xml">
+<Zone>.</Zone>
+<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/ok1_expired1.xml b/daemon/lua/trust_anchors.test/ok1_expired1.xml
new file mode 100644
index 0000000..f1269da
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/ok1_expired1.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="68463155-A857-4C7E-BCA6-2F6CC2EAC1BE" source="https://localhost/ok1_expired1.xml">
+<Zone>.</Zone>
+<KeyDigest id="F" validFrom="1990-01-01T00:00:00+00:00" validUntil="2000-01-01T00:00:00+00:00">
+<KeyTag>1</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest>
+</KeyDigest>
+<KeyDigest id="1" validFrom="2000-01-01T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/ok1_notyet1.xml b/daemon/lua/trust_anchors.test/ok1_notyet1.xml
new file mode 100644
index 0000000..7b5881b
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/ok1_notyet1.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="507B39D5-049E-467C-9E9A-F5BE597C9DDA" source="https://localhost/ok1_notyet1.xml">
+<Zone>.</Zone>
+<KeyDigest id="1" validFrom="2010-07-15T00:00:00+00:00">
+<KeyTag>1</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+<KeyDigest id="2" validFrom="2050-12-31T23:59:59+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/ok2.xml b/daemon/lua/trust_anchors.test/ok2.xml
new file mode 100644
index 0000000..149f6b5
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/ok2.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="1DECEB91-0591-44A1-95CF-1788337514B8" source="https://localhost/ok2.xml">
+<Zone>.</Zone>
+<KeyDigest id="K1" validFrom="2010-07-15T00:00:00+00:00">
+<KeyTag>1</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+<KeyDigest id="K2" validFrom="2011-02-02T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>2222222222222222222222222222222222222222222222222222222222222222</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/regen.sh b/daemon/lua/trust_anchors.test/regen.sh
new file mode 100755
index 0000000..09b334c
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/regen.sh
@@ -0,0 +1,2 @@
+for F in *.xml; do sed -i "s/TrustAnchor id=\"[^\"]*\"/TrustAnchor id=\"$(uuidgen | tr '[[:lower:]]' '[[:upper:]]')\"/" $F; done
+for F in *.xml; do sed -i "s#source=\"[^\"]*\"#source=\"https://localhost/$F\"#" $F; done
diff --git a/daemon/lua/trust_anchors.test/unsupp_nonroot.xml b/daemon/lua/trust_anchors.test/unsupp_nonroot.xml
new file mode 100644
index 0000000..51b3c0a
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/unsupp_nonroot.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TrustAnchor id="8449BFB8-FD6C-4082-B0FE-1A3E3399203B" source="https://localhost/unsupp_nonroot.xml">
+<Zone>test.</Zone>
+<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/unsupp_xml_v11.xml b/daemon/lua/trust_anchors.test/unsupp_xml_v11.xml
new file mode 100644
index 0000000..87a4b57
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/unsupp_xml_v11.xml
@@ -0,0 +1,10 @@
+<?xml version="1.1" encoding="UTF-8"?>
+<TrustAnchor id="3612AE1C-E8F3-4FD8-B8CD-96C7FDACC7A5" source="https://localhost/unsupp_xml_v11.xml">
+<Zone>.</Zone>
+<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
+<KeyTag>2</KeyTag>
+<Algorithm>8</Algorithm>
+<DigestType>2</DigestType>
+<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
+</KeyDigest>
+</TrustAnchor>
diff --git a/daemon/lua/trust_anchors.test/webserv.lua b/daemon/lua/trust_anchors.test/webserv.lua
new file mode 100644
index 0000000..458d3e5
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/webserv.lua
@@ -0,0 +1,234 @@
+-- This is a module that does the heavy lifting to provide an HTTP/2 enabled
+-- server that supports TLS by default and provides endpoint for other modules
+-- in order to enable them to export restful APIs and websocket streams.
+-- One example is statistics module that can stream live metrics on the website,
+-- or publish metrics on request for Prometheus scraper.
+local http_server = require('http.server')
+local http_headers = require('http.headers')
+local http_websocket = require('http.websocket')
+local http_util = require "http.util"
+local x509, pkey = require('openssl.x509'), require('openssl.pkey')
+
+-- Module declaration
+local M = {}
+
+-- Export HTTP service endpoints
+M.endpoints = {
+ ['/'] = {'text/html', 'test'},
+}
+
+-- Serve known requests, for methods other than GET
+-- the endpoint must be a closure and not a preloaded string
+local function serve(endpoints, h, stream)
+ local hsend = http_headers.new()
+ local path = h:get(':path')
+ local entry = endpoints[path]
+ if not entry then -- Accept top-level path match
+ entry = endpoints[path:match '^/[^/?]*']
+ end
+ -- Unpack MIME and data
+ local data, mime, ttl, err
+ if entry then
+ mime = entry[1]
+ data = entry[2]
+ ttl = entry[4]
+ end
+ -- Get string data out of service endpoint
+ if type(data) == 'function' then
+ local set_mime, set_ttl
+ data, err, set_mime, set_ttl = data(h, stream)
+ -- Override default endpoint mime/TTL
+ if set_mime then mime = set_mime end
+ if set_ttl then ttl = set_ttl end
+ -- Handler doesn't provide any data
+ if data == false then return end
+ if type(data) == 'number' then return tostring(data), err end
+ -- Methods other than GET require handler to be closure
+ elseif h:get(':method') ~= 'GET' then
+ return '501', ''
+ end
+ if not mime or type(data) ~= 'string' then
+ return '404', ''
+ else
+ -- Serve content type appropriately
+ hsend:append(':status', '200')
+ hsend:append('content-type', mime)
+ hsend:append('content-length', tostring(#data))
+ if ttl then
+ hsend:append('cache-control', string.format('max-age=%d', ttl))
+ end
+ assert(stream:write_headers(hsend, false))
+ assert(stream:write_chunk(data, true))
+ end
+end
+
+-- Web server service closure
+local function route(endpoints)
+ return function (_, stream)
+ -- HTTP/2: We're only permitted to send in open/half-closed (remote)
+ local connection = stream.connection
+ if connection.version >= 2 then
+ if stream.state ~= 'open' and stream.state ~= 'half closed (remote)' then
+ return
+ end
+ end
+ -- Start reading headers
+ local h = assert(stream:get_headers())
+ local m = h:get(':method')
+ local path = h:get(':path')
+ -- Upgrade connection to WebSocket
+ local ws = http_websocket.new_from_stream(stream, h)
+ if ws then
+ assert(ws:accept { protocols = {'json'} })
+ -- Continue streaming results to client
+ local ep = endpoints[path]
+ local cb = ep[3]
+ if cb then
+ cb(h, ws)
+ end
+ ws:close()
+ return
+ else
+ local ok, err, reason = http_util.yieldable_pcall(serve, endpoints, h, stream)
+ if not ok or err then
+ print(string.format('%s err %s %s: %s (%s)', os.date(), m, path, err or '500', reason))
+ -- Method is not supported
+ local hsend = http_headers.new()
+ hsend:append(':status', err or '500')
+ if reason then
+ assert(stream:write_headers(hsend, false))
+ assert(stream:write_chunk(reason, true))
+ else
+ assert(stream:write_headers(hsend, true))
+ end
+ else
+ print(string.format('%s ok %s %s', os.date(), m, path))
+ end
+ end
+ end
+end
+
+-- @function Prefer HTTP/2 or HTTP/1.1
+local function alpnselect(_, protos)
+ for _, proto in ipairs(protos) do
+ if proto == 'h2' or proto == 'http/1.1' then
+ return proto
+ end
+ end
+ return nil
+end
+
+-- @function Create TLS context
+local function tlscontext(crt, key)
+ local http_tls = require('http.tls')
+ local ctx = http_tls.new_server_context()
+ if ctx.setAlpnSelect then
+ ctx:setAlpnSelect(alpnselect)
+ end
+ assert(ctx:setPrivateKey(key))
+ assert(ctx:setCertificate(crt))
+ return ctx
+end
+
+-- @function Listen on given HTTP(s) host
+function M.add_interface(conf)
+ local crt, key
+ if conf.tls ~= false then
+ assert(conf.cert, 'cert missing')
+ assert(conf.key, 'private key missing')
+ -- Check if a cert file was specified
+ -- Read x509 certificate
+ local f = io.open(conf.cert, 'r')
+ if f then
+ crt = assert(x509.new(f:read('*all')))
+ f:close()
+ -- Continue reading key file
+ if crt then
+ f = io.open(conf.key, 'r')
+ key = assert(pkey.new(f:read('*all')))
+ f:close()
+ end
+ end
+ -- Check loaded certificate
+ assert(crt and key,
+ string.format('failed to load certificate "%s"', conf.cert))
+ end
+ -- Compose server handler
+ local routes = route(conf.endpoints or M.endpoints)
+ -- Check if UNIX socket path is used
+ local addr_str
+ if not conf.path then
+ conf.host = conf.host or 'localhost'
+ conf.port = conf.port or 8053
+ addr_str = string.format('%s@%d', conf.host, conf.port)
+ else
+ if conf.host or conf.port then
+ error('either "path", or "host" and "port" must be provided')
+ end
+ addr_str = conf.path
+ end
+ -- Create TLS context and start listening
+ local s, err = http_server.listen {
+ -- cq = worker.bg_worker.cq,
+ host = conf.host,
+ port = conf.port,
+ path = conf.path,
+ v6only = conf.v6only,
+ unlink = conf.unlink,
+ reuseaddr = conf.reuseaddr,
+ reuseport = conf.reuseport,
+ client_timeout = conf.client_timeout or 5,
+ ctx = crt and tlscontext(crt, key),
+ tls = conf.tls,
+ onstream = routes,
+ -- Log errors, but do not throw
+ onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212
+ local msg = '[http] ' .. op .. ' on ' .. tostring(context) .. ' failed'
+ if err then
+ msg = msg .. ': ' .. tostring(err)
+ end
+ print(msg)
+ end,
+ }
+ -- Manually call :listen() so that we are bound before calling :localname()
+ if s then
+ err = select(2, s:listen())
+ end
+ assert(not err, string.format('failed to listen on %s: %s', addr_str, err))
+ return s
+end
+
+-- init
+local files = {
+ 'ok0_badtimes.xml',
+ 'ok1.xml',
+ 'ok1_expired1.xml',
+ 'ok1_notyet1.xml',
+ 'ok2.xml',
+ 'err_attr_validfrom_missing.xml',
+ 'err_attr_validfrom_invalid.xml',
+ 'err_attr_extra_attr.xml',
+ 'err_elem_missing.xml',
+ 'err_elem_extra.xml',
+ 'err_multi_ta.xml',
+ 'unsupp_nonroot.xml',
+ 'unsupp_xml_v11.xml'
+}
+
+-- Export static pages specified at command line
+for _, name in ipairs(files) do
+ local fd = io.open(name)
+ assert(fd, string.format('unable to open file "%s"', name))
+ M.endpoints['/' .. name] = { 'text/xml', fd:read('*a') }
+ fd:close()
+end
+
+local server = M.add_interface({
+ host = 'localhost',
+ port = 8080,
+ tls = true,
+ cert = 'x509/server.pem',
+ key = 'x509/server-key.pem'
+ })
+
+server:loop()
diff --git a/daemon/lua/trust_anchors.test/x509/ca-key.pem b/daemon/lua/trust_anchors.test/x509/ca-key.pem
new file mode 100644
index 0000000..28b1f52
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/ca-key.pem
@@ -0,0 +1,182 @@
+Public Key Info:
+ Public Key Algorithm: RSA
+ Key Security Level: High (3072 bits)
+
+modulus:
+ 00:cd:d7:b2:a4:37:ae:47:8b:c2:e2:24:7a:2e:77:4a
+ 9a:aa:8e:5a:f5:68:80:f0:7c:68:08:50:30:3a:f9:77
+ bb:99:a6:67:f7:ce:45:6a:c9:11:43:81:4e:a7:92:53
+ 58:53:0c:76:fe:81:9c:a5:c8:32:e9:2e:5d:1d:16:3e
+ 5d:b2:21:c6:65:d7:33:c1:04:81:7a:8e:8c:c7:2e:8c
+ d0:73:bf:37:43:b7:1b:07:90:cb:a3:6e:7c:67:76:be
+ b0:20:28:5b:5d:d7:b3:b2:09:5e:2b:23:76:fd:32:26
+ c7:cf:de:e8:0c:5b:c2:bc:a8:52:e2:58:56:89:ad:57
+ 63:b0:01:86:b3:d1:3d:e0:6c:13:ff:b5:e6:37:f4:87
+ 64:f5:d8:9e:8c:7d:48:43:60:5c:bb:bb:73:cc:d3:3f
+ ad:76:44:ff:08:25:b8:dc:c4:47:c2:29:29:b8:00:4b
+ a7:01:84:93:40:9c:80:35:78:7d:b2:bb:70:58:b8:7a
+ d9:60:7c:e6:d6:66:36:cb:62:4d:84:e8:b9:9e:62:2f
+ 97:2c:cd:c2:28:6e:32:a5:c9:33:a1:8d:96:41:05:5c
+ 84:7a:82:80:4e:df:8b:98:75:ba:84:84:a3:c7:9c:41
+ c8:19:d8:af:37:28:f2:e8:c2:dc:b9:b2:fa:7c:26:b6
+ 31:bd:2f:e7:c3:aa:5d:c4:03:fb:db:42:3d:fe:2e:15
+ 9e:23:40:b2:e5:e1:7e:a0:e1:5d:d7:ff:aa:ed:e9:09
+ 99:55:2d:48:4c:67:95:b6:3f:d8:87:c7:b3:b6:ae:b0
+ 45:42:9c:6a:3f:a7:8e:d2:79:e7:2e:51:d6:f1:25:e8
+ 4f:4a:e4:cc:4e:2e:ae:d7:a6:b2:8a:d5:5d:76:42:ff
+ 19:eb:0d:b9:a0:8c:76:ed:9d:e6:e9:83:0c:35:d7:d3
+ 4c:16:2a:6b:25:1f:7a:f1:6d:6f:33:cf:21:8c:9f:a8
+ 43:0b:11:d3:da:cf:ec:1e:64:54:c1:3e:12:13:f1:eb
+ b5:
+
+public exponent:
+ 01:00:01:
+
+private exponent:
+ 00:ab:ca:e1:64:fc:b3:8f:32:ad:ab:5f:16:39:c1:85
+ 9a:1f:ce:3f:4b:a1:b4:3b:01:19:32:16:fa:a9:bd:9a
+ 98:0f:5c:3a:59:2e:e5:f0:81:6e:cf:10:14:3c:f6:7a
+ 68:b4:a7:2a:88:ae:53:b6:68:a7:54:c5:45:21:09:77
+ 73:6b:3f:94:fd:59:e5:ef:a9:7b:06:76:02:38:1a:39
+ 9b:9f:7e:6e:f9:2c:d0:7a:37:f6:3c:a7:f1:5b:c8:56
+ cd:57:89:56:f7:b3:16:5d:f8:43:87:6d:49:d9:77:09
+ b6:a9:5e:37:fc:58:78:e6:4a:f5:21:c2:e8:36:6f:5d
+ 07:ed:d4:d8:3f:2a:da:a1:7a:92:16:50:11:9b:91:91
+ 8e:49:40:48:d2:a7:9c:af:de:b4:86:59:a4:03:c6:2a
+ d6:f2:66:13:13:11:a8:7f:57:b1:2b:f2:7e:c6:fa:38
+ 29:61:b2:eb:d2:78:ca:e7:d5:6f:d2:c3:6c:b8:1e:ff
+ 26:fc:47:62:46:b6:03:c1:d9:a0:03:4f:16:5f:fe:65
+ e7:56:a2:21:38:b0:34:17:45:00:56:26:e8:5e:8b:fe
+ e0:fc:fc:a0:3e:2c:00:fb:c3:f8:dc:07:05:73:59:59
+ fd:b6:7a:c3:cd:4d:da:5a:1c:38:17:2e:15:8f:50:18
+ 52:bb:d2:a9:fe:88:6f:d2:71:d3:7e:74:85:a3:88:ba
+ 99:79:4c:ce:36:23:63:0a:17:16:00:da:fb:76:fb:90
+ bb:c1:08:77:43:0d:ce:81:a8:89:47:ea:bc:f0:40:65
+ 07:0d:d0:9c:5e:02:b5:2b:89:cb:65:56:c3:24:b0:e1
+ 39:30:20:aa:03:4c:20:7b:36:14:24:cb:8b:64:74:fc
+ 00:2e:de:d6:d1:9b:93:17:d1:98:37:7f:6a:cd:42:d3
+ 20:b2:92:2e:96:1e:3c:04:3e:99:1c:c7:03:d4:b9:87
+ 98:e2:a5:41:b8:3f:0c:1a:94:54:16:4d:2b:83:58:de
+ 01:
+
+prime1:
+ 00:f9:82:8d:c3:92:3d:bd:52:6b:ff:4e:0b:97:b3:8f
+ a2:56:53:49:05:1e:5b:20:cc:1b:fc:a4:0e:eb:ad:ce
+ 1f:79:7a:3c:e6:b3:6d:1e:1a:bf:5a:01:76:c9:14:6c
+ 0b:2d:6e:b2:56:55:b4:4d:f8:da:15:68:eb:07:59:43
+ c7:de:8b:01:14:8f:6d:3b:c5:d6:7d:86:f1:ba:9a:88
+ 0c:bc:06:96:2a:94:59:d4:e1:eb:15:dd:20:9e:98:78
+ 58:ca:ef:19:a7:52:c4:a0:35:91:d8:0b:3b:d2:93:aa
+ 60:bc:44:c3:a5:ac:1c:f4:38:3e:60:79:da:c3:53:9a
+ 6b:87:87:77:36:09:a1:27:b2:60:3e:aa:f3:73:d4:a7
+ af:f7:74:9e:c7:19:b1:e4:75:ed:79:56:67:53:05:1c
+ 4e:55:f5:be:6a:9c:3a:4b:15:73:55:90:7c:a2:e9:45
+ 07:d5:eb:a0:3f:da:d6:7f:ae:4b:62:6d:b5:8d:af:48
+ 31:
+
+prime2:
+ 00:d3:32:5e:b2:ef:2e:bf:d1:57:85:28:bb:5c:37:c0
+ 8a:ed:46:47:23:4e:22:72:2f:de:0a:a6:36:64:8f:9e
+ f5:67:a3:5d:d4:2d:2b:e2:3c:c7:b8:0c:79:37:bf:e6
+ 67:96:84:4c:be:98:1f:86:e0:3d:c9:4a:b6:50:de:2f
+ 90:61:25:74:49:ff:33:a7:93:3a:12:2b:c2:2c:38:29
+ 9f:16:d8:20:16:77:e6:04:27:23:44:35:5a:a2:a1:72
+ 40:91:b6:39:5f:e6:57:9f:59:6a:e4:5c:da:50:c7:4f
+ fe:df:1f:40:3c:e7:05:f6:f9:52:e9:c4:2c:e0:68:46
+ 47:6e:52:76:bc:c1:19:7c:2d:50:2f:ef:53:e1:67:7c
+ b8:7e:84:72:6d:60:2c:ff:ff:7f:7a:ec:1e:54:8c:b1
+ a8:64:0b:54:f5:c3:4e:dd:dc:22:3b:54:05:7f:cc:32
+ 64:d5:87:15:9f:f8:9f:07:83:4e:a0:d2:13:48:6b:be
+ c5:
+
+coefficient:
+ 2e:c4:04:3b:cf:3f:4a:dd:f0:32:e4:fc:93:fe:ef:b8
+ 83:14:8e:a9:6b:ba:73:28:b6:b1:49:57:67:d7:ca:39
+ f0:da:23:99:df:3d:9b:8c:7b:c9:9a:fe:22:69:29:87
+ a3:ad:2a:6b:a7:5a:42:09:57:fd:8e:55:0a:1a:e9:36
+ 02:e7:b3:47:82:41:c1:21:b2:8b:6a:35:30:60:17:c3
+ a5:3b:cc:2a:cb:e1:7a:23:14:6c:8a:87:29:49:8a:0a
+ c3:34:a8:aa:64:92:74:82:f1:01:3a:00:2e:bf:d6:d3
+ 00:86:01:ee:84:83:42:8d:c7:b0:88:ac:62:12:9d:b5
+ 18:28:76:13:34:24:92:fb:a9:57:e5:4c:8a:a9:bb:73
+ a5:02:64:9b:73:4c:18:6b:29:e4:ba:04:da:66:ef:b4
+ 0c:46:fd:55:ff:62:5b:76:80:a9:13:29:c1:4b:43:51
+ 0b:44:d3:01:9d:c5:e5:6b:ac:a0:e5:b9:0c:41:08:49
+
+
+exp1:
+ 00:9d:50:d4:63:4f:cc:7f:96:fd:22:de:a9:6e:7f:b7
+ 7b:4a:64:7e:b0:ac:80:16:80:ba:d0:a2:fc:09:5a:ef
+ 90:66:be:4c:b1:c4:c5:72:ea:b8:65:5e:70:ef:bd:61
+ 95:f6:92:49:fd:27:52:64:ab:17:8d:d2:36:05:cf:21
+ 6e:5e:81:54:30:0b:72:7a:f2:75:17:76:42:e9:3d:cf
+ b3:ff:c5:43:5f:1b:64:3c:56:29:2c:02:dd:33:41:bc
+ f7:77:14:24:1f:9c:8e:fe:d8:67:d9:48:d3:f2:24:4a
+ 93:6d:81:09:be:66:73:67:04:23:48:1e:ec:70:a5:40
+ c2:b5:94:12:f4:ce:43:7e:cc:f7:e3:eb:53:4e:5f:f1
+ 4e:80:7e:56:32:00:a0:6a:04:74:b3:41:68:2a:2f:19
+ b6:c6:7a:08:12:1d:e8:9f:38:aa:1c:73:da:31:b9:54
+ 5d:e9:62:04:0a:de:c6:c9:80:32:65:9f:f8:8d:99:cb
+ b1:
+
+exp2:
+ 77:f1:8f:5e:c8:00:20:e8:5f:70:3c:a5:cb:c5:ce:10
+ 18:99:65:25:63:e6:a4:3b:13:3b:b1:12:0b:22:96:00
+ 81:8f:82:66:52:11:2e:37:9e:a5:a4:4f:e3:9d:94:d9
+ 17:de:a3:47:4b:55:fc:5f:b6:37:6f:bb:03:4a:6c:70
+ ee:fb:3a:84:1b:ef:d6:28:03:3e:f6:a3:1c:41:f9:41
+ 64:d3:f1:c5:50:ea:cd:48:fc:fa:6b:e2:c1:a2:37:24
+ fc:9c:25:11:95:dc:05:9f:de:d9:3a:f2:b2:15:c0:14
+ db:da:ff:bc:96:e8:08:4f:a9:0b:22:62:ea:3f:ce:4a
+ 1b:19:b2:5d:98:bd:44:8c:e7:91:91:b6:6d:b9:3c:57
+ fb:62:75:2a:31:08:dd:8a:d9:77:92:49:d7:72:e0:22
+ e6:4e:99:3d:ca:62:2c:16:2d:4a:cc:79:23:0f:71:3f
+ 5b:15:90:ee:7d:60:b3:ff:9a:d4:32:fa:c8:92:88:91
+
+
+
+Public Key PIN:
+ pin-sha256:D8K7fp8E6Fsg1NjgFkNjJA/Mow1IQUvondTb816FnDc=
+Public Key ID:
+ sha256:0fc2bb7e9f04e85b20d4d8e0164363240fcca30d48414be89dd4dbf35e859c37
+ sha1:3017e1bccfde068e418672642d1e181a90988264
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIG5AIBAAKCAYEAzdeypDeuR4vC4iR6LndKmqqOWvVogPB8aAhQMDr5d7uZpmf3
+zkVqyRFDgU6nklNYUwx2/oGcpcgy6S5dHRY+XbIhxmXXM8EEgXqOjMcujNBzvzdD
+txsHkMujbnxndr6wIChbXdezsgleKyN2/TImx8/e6AxbwryoUuJYVomtV2OwAYaz
+0T3gbBP/teY39Idk9diejH1IQ2Bcu7tzzNM/rXZE/wgluNzER8IpKbgAS6cBhJNA
+nIA1eH2yu3BYuHrZYHzm1mY2y2JNhOi5nmIvlyzNwihuMqXJM6GNlkEFXIR6goBO
+34uYdbqEhKPHnEHIGdivNyjy6MLcubL6fCa2Mb0v58OqXcQD+9tCPf4uFZ4jQLLl
+4X6g4V3X/6rt6QmZVS1ITGeVtj/Yh8eztq6wRUKcaj+njtJ55y5R1vEl6E9K5MxO
+Lq7XprKK1V12Qv8Z6w25oIx27Z3m6YMMNdfTTBYqayUfevFtbzPPIYyfqEMLEdPa
+z+weZFTBPhIT8eu1AgMBAAECggGBAKvK4WT8s48yratfFjnBhZofzj9LobQ7ARky
+FvqpvZqYD1w6WS7l8IFuzxAUPPZ6aLSnKoiuU7Zop1TFRSEJd3NrP5T9WeXvqXsG
+dgI4Gjmbn35u+SzQejf2PKfxW8hWzVeJVvezFl34Q4dtSdl3CbapXjf8WHjmSvUh
+wug2b10H7dTYPyraoXqSFlARm5GRjklASNKnnK/etIZZpAPGKtbyZhMTEah/V7Er
+8n7G+jgpYbLr0njK59Vv0sNsuB7/JvxHYka2A8HZoANPFl/+ZedWoiE4sDQXRQBW
+Juhei/7g/PygPiwA+8P43AcFc1lZ/bZ6w81N2locOBcuFY9QGFK70qn+iG/ScdN+
+dIWjiLqZeUzONiNjChcWANr7dvuQu8EId0MNzoGoiUfqvPBAZQcN0JxeArUrictl
+VsMksOE5MCCqA0wgezYUJMuLZHT8AC7e1tGbkxfRmDd/as1C0yCyki6WHjwEPpkc
+xwPUuYeY4qVBuD8MGpRUFk0rg1jeAQKBwQD5go3Dkj29Umv/TguXs4+iVlNJBR5b
+IMwb/KQO663OH3l6POazbR4av1oBdskUbAstbrJWVbRN+NoVaOsHWUPH3osBFI9t
+O8XWfYbxupqIDLwGliqUWdTh6xXdIJ6YeFjK7xmnUsSgNZHYCzvSk6pgvETDpawc
+9Dg+YHnaw1Oaa4eHdzYJoSeyYD6q83PUp6/3dJ7HGbHkde15VmdTBRxOVfW+apw6
+SxVzVZB8oulFB9XroD/a1n+uS2JttY2vSDECgcEA0zJesu8uv9FXhSi7XDfAiu1G
+RyNOInIv3gqmNmSPnvVno13ULSviPMe4DHk3v+ZnloRMvpgfhuA9yUq2UN4vkGEl
+dEn/M6eTOhIrwiw4KZ8W2CAWd+YEJyNENVqioXJAkbY5X+ZXn1lq5FzaUMdP/t8f
+QDznBfb5UunELOBoRkduUna8wRl8LVAv71PhZ3y4foRybWAs//9/euweVIyxqGQL
+VPXDTt3cIjtUBX/MMmTVhxWf+J8Hg06g0hNIa77FAoHBAJ1Q1GNPzH+W/SLeqW5/
+t3tKZH6wrIAWgLrQovwJWu+QZr5MscTFcuq4ZV5w771hlfaSSf0nUmSrF43SNgXP
+IW5egVQwC3J68nUXdkLpPc+z/8VDXxtkPFYpLALdM0G893cUJB+cjv7YZ9lI0/Ik
+SpNtgQm+ZnNnBCNIHuxwpUDCtZQS9M5Dfsz34+tTTl/xToB+VjIAoGoEdLNBaCov
+GbbGeggSHeifOKocc9oxuVRd6WIECt7GyYAyZZ/4jZnLsQKBwHfxj17IACDoX3A8
+pcvFzhAYmWUlY+akOxM7sRILIpYAgY+CZlIRLjeepaRP452U2Rfeo0dLVfxftjdv
+uwNKbHDu+zqEG+/WKAM+9qMcQflBZNPxxVDqzUj8+mviwaI3JPycJRGV3AWf3tk6
+8rIVwBTb2v+8lugIT6kLImLqP85KGxmyXZi9RIznkZG2bbk8V/tidSoxCN2K2XeS
+Sddy4CLmTpk9ymIsFi1KzHkjD3E/WxWQ7n1gs/+a1DL6yJKIkQKBwC7EBDvPP0rd
+8DLk/JP+77iDFI6pa7pzKLaxSVdn18o58Nojmd89m4x7yZr+Imkph6OtKmunWkIJ
+V/2OVQoa6TYC57NHgkHBIbKLajUwYBfDpTvMKsvheiMUbIqHKUmKCsM0qKpkknSC
+8QE6AC6/1tMAhgHuhINCjcewiKxiEp21GCh2EzQkkvupV+VMiqm7c6UCZJtzTBhr
+KeS6BNpm77QMRv1V/2JbdoCpEynBS0NRC0TTAZ3F5WusoOW5DEEISQ==
+-----END RSA PRIVATE KEY-----
diff --git a/daemon/lua/trust_anchors.test/x509/ca.pem b/daemon/lua/trust_anchors.test/x509/ca.pem
new file mode 100644
index 0000000..70e0fd5
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/ca.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEFzCCAn+gAwIBAgIUBSlZzv0lYdpFv+zOqzsn3zZn7q0wDQYJKoZIhvcNAQEL
+BQAwIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZlciB0ZXN0aW5nIENBMB4XDTE5MDEw
+MzE2MjczM1oXDTIwMDEwMzE2MjczM1owIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZl
+ciB0ZXN0aW5nIENBMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzdey
+pDeuR4vC4iR6LndKmqqOWvVogPB8aAhQMDr5d7uZpmf3zkVqyRFDgU6nklNYUwx2
+/oGcpcgy6S5dHRY+XbIhxmXXM8EEgXqOjMcujNBzvzdDtxsHkMujbnxndr6wIChb
+XdezsgleKyN2/TImx8/e6AxbwryoUuJYVomtV2OwAYaz0T3gbBP/teY39Idk9die
+jH1IQ2Bcu7tzzNM/rXZE/wgluNzER8IpKbgAS6cBhJNAnIA1eH2yu3BYuHrZYHzm
+1mY2y2JNhOi5nmIvlyzNwihuMqXJM6GNlkEFXIR6goBO34uYdbqEhKPHnEHIGdiv
+Nyjy6MLcubL6fCa2Mb0v58OqXcQD+9tCPf4uFZ4jQLLl4X6g4V3X/6rt6QmZVS1I
+TGeVtj/Yh8eztq6wRUKcaj+njtJ55y5R1vEl6E9K5MxOLq7XprKK1V12Qv8Z6w25
+oIx27Z3m6YMMNdfTTBYqayUfevFtbzPPIYyfqEMLEdPaz+weZFTBPhIT8eu1AgMB
+AAGjQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcEADAdBgNVHQ4E
+FgQUMBfhvM/eBo5BhnJkLR4YGpCYgmQwDQYJKoZIhvcNAQELBQADggGBAMpUHwLa
+v9sDAkhZ+Wp/I5LdzP/7i4f/EI8tEh+GYeoWNvW9FkvaW1ydU6ZPx+j2hzZRCpIR
+WTT/w0dB7d+2EOUJKrytPpx5O/uYIYOqCYbWAYIF6Vdk0vakwmWSg9YsdwLfDHfI
+/K6vt3cIPLCzSXQdimHzKd4+6pXZcjsEFSpmueaHk08HGErEVeWgG3Ro3XwuBJNt
+9QUN7fF8DAi3705/NuoF1fR9MvuorR3KBgYKFO/7F6gxgTVHXfwK8iRN0YZLPU02
+eUUs7zq8FS87chuq9ABodBmsmkVI6671N57U8duxVacSuSHEyYdHamYznBdGnCMv
+GCvn0VyUuqXe45LoRK4um4MC/3tPp70bY1LqyWXoCLMOdnObzI341PXoFvI/ScYa
+La2lkRz4wMqBecM0fLOJqshdDt6ZgEP3YMHgIhM87VR4savECnfH+RHAIZt/oEEt
+dJmAC9H2f2/zoxIXcRrGbU2kB5wBYW69qRzlOOtScimCmHdtVK/iAzg4KQ==
+-----END CERTIFICATE-----
diff --git a/daemon/lua/trust_anchors.test/x509/ca.tmpl b/daemon/lua/trust_anchors.test/x509/ca.tmpl
new file mode 100644
index 0000000..6de507d
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/ca.tmpl
@@ -0,0 +1,3 @@
+cn = Knot Resolver testing CA
+ca
+cert_signing_key
diff --git a/daemon/lua/trust_anchors.test/x509/gen.sh b/daemon/lua/trust_anchors.test/x509/gen.sh
new file mode 100755
index 0000000..10ecad3
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/gen.sh
@@ -0,0 +1,11 @@
+# CA
+certtool --generate-privkey > ca-key.pem
+certtool --generate-self-signed --load-privkey ca-key.pem --template ca.tmpl --outfile ca.pem
+
+# server cert signed by CA above
+certtool --generate-privkey > server-key.pem
+certtool --generate-certificate --load-privkey server-key.pem --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem --template server.tmpl --outfile server.pem
+
+# wrong CA - unrelated to others
+certtool --generate-privkey > wrongca-key.pem
+certtool --generate-self-signed --load-privkey wrongca-key.pem --template wrongca.tmpl --outfile wrongca.pem
diff --git a/daemon/lua/trust_anchors.test/x509/server-key.pem b/daemon/lua/trust_anchors.test/x509/server-key.pem
new file mode 100644
index 0000000..4410e27
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/server-key.pem
@@ -0,0 +1,182 @@
+Public Key Info:
+ Public Key Algorithm: RSA
+ Key Security Level: High (3072 bits)
+
+modulus:
+ 00:ba:f8:5c:15:1b:85:bd:32:5e:59:c4:ae:73:a2:b0
+ 67:06:07:69:e6:88:04:4d:98:45:5d:b6:43:78:8b:1f
+ 53:b3:22:0a:d1:b2:48:78:96:38:b8:98:1a:08:41:12
+ 2a:38:cc:3a:ae:b8:9d:73:ea:7c:ba:7a:08:ea:bc:24
+ f3:9b:1f:81:eb:54:6e:d9:16:b2:15:bc:66:36:78:43
+ 0c:06:8e:ba:aa:b6:f4:fb:12:d4:1f:04:22:b3:5b:03
+ 8f:b7:a6:25:df:58:c4:ca:b3:09:c4:54:b2:a4:25:fa
+ af:a6:24:d8:83:9f:18:fc:08:6c:da:f4:ae:68:57:75
+ d6:a8:c1:bf:db:39:f7:b0:c2:63:8d:95:18:01:0a:ee
+ 81:d0:e2:76:d5:52:5c:25:f8:aa:0c:a8:7f:34:ca:e2
+ bc:0e:34:be:63:7f:4a:01:90:36:5c:4d:d9:d9:ee:05
+ fc:e2:c8:a1:32:76:dc:39:54:33:dd:c4:b8:1e:a1:f5
+ 23:c1:7f:3b:6a:ff:84:98:60:1f:53:86:bf:ac:d6:4e
+ 58:9a:91:aa:13:1e:a5:9e:61:eb:ba:8c:35:da:89:4d
+ 48:7b:82:cf:95:d4:68:fd:24:aa:43:9c:3a:5b:86:f6
+ 7d:2d:41:dc:63:36:2c:c2:c5:0b:49:04:46:f3:c4:b9
+ e0:19:e4:06:80:4c:9f:9f:1a:6b:9a:88:42:6c:6c:a9
+ 48:1c:9d:ff:a1:71:2c:44:5a:e5:3f:b4:bc:b6:43:db
+ 47:1f:65:15:41:3f:d0:7a:8f:c1:1c:f0:93:11:c7:13
+ 74:5f:7c:47:e9:2c:bc:d0:7a:fc:c0:89:6e:e9:1e:82
+ 85:ef:a7:af:57:d3:fc:af:c7:9a:34:06:60:2e:db:bc
+ 01:d5:08:63:8e:07:27:51:3a:17:0a:71:22:56:eb:e9
+ af:f6:48:31:42:87:2d:95:05:ad:f8:c6:76:bc:17:2f
+ 50:47:68:95:4a:6b:4d:ce:ba:51:2b:1e:e1:b3:d5:79
+ 27:
+
+public exponent:
+ 01:00:01:
+
+private exponent:
+ 3a:95:8d:8c:d3:95:e1:45:82:08:d0:b6:f4:e6:ba:2d
+ 5d:d7:3e:d8:8c:30:04:fc:3c:67:f3:af:4a:7b:15:32
+ cd:c3:51:ee:88:d5:6a:6b:6f:94:6f:9c:60:8f:bb:18
+ 5d:b9:a8:7d:8c:bd:51:4f:dd:0b:35:27:cc:1d:6d:da
+ a2:f5:89:68:ea:88:dd:1e:de:68:2f:23:3b:d8:0c:f2
+ 1c:af:e6:4f:8d:8a:4f:8d:83:c7:c0:2c:fb:53:fe:56
+ ae:ed:b1:9a:3d:1f:54:80:2b:04:48:a1:bd:0b:65:74
+ 5a:33:db:5f:ca:9f:32:81:95:52:3a:2e:d7:e4:e7:b8
+ 7e:22:4f:72:6a:c4:70:af:48:30:59:d2:2e:a9:75:38
+ 59:f2:4b:d7:e5:b9:97:f0:45:a0:37:bf:bc:14:02:5d
+ 78:7a:10:59:ee:cd:8a:95:dc:62:a9:c6:24:22:0f:e0
+ 40:53:2b:27:3b:d5:1f:65:81:e6:f0:37:dc:e9:54:7c
+ f8:81:f0:e0:f7:80:05:ac:91:14:5b:5d:03:14:c2:b9
+ 74:64:f6:ab:6e:a5:ce:e7:bc:56:58:41:30:31:8e:e9
+ f5:6e:31:62:31:69:a8:17:44:89:b3:b9:95:5c:cd:fc
+ 40:86:2c:a1:1d:46:48:94:26:ae:35:bf:14:0e:ae:49
+ 2d:56:5f:bd:e0:d6:f5:03:ed:df:a5:a9:43:89:5c:01
+ 33:5e:e6:3e:80:da:9f:df:2d:c7:e8:13:60:32:27:0e
+ 77:11:fd:43:94:2b:6e:6b:12:79:aa:59:e6:54:b6:47
+ dc:78:7e:fe:4b:40:32:12:3a:cc:6d:d2:9f:49:fc:b2
+ dc:6f:d0:01:03:1d:1d:a8:e5:ee:60:d7:8c:26:7f:73
+ 8a:c0:81:95:ff:b6:c5:5e:d7:6b:50:1f:02:61:3b:31
+ f4:41:da:0a:a7:c6:ea:d2:a2:c4:b5:6d:ce:3f:e0:3b
+ 03:a7:38:37:db:88:82:18:09:a2:da:cf:77:3a:33:f9
+
+
+prime1:
+ 00:f7:94:41:16:98:0c:cb:a1:31:a8:33:38:96:03:f1
+ 33:bf:b7:03:1b:28:bc:43:62:40:77:e6:d9:75:64:77
+ ff:64:51:34:9c:65:3a:ca:10:0f:3e:ce:37:ef:14:85
+ d5:7d:f5:ee:29:f3:76:02:c4:dd:52:91:5c:2e:7e:ad
+ 76:6b:65:e9:60:fc:7c:30:64:9a:bb:65:22:39:a3:a0
+ 54:78:9b:15:2d:a7:e1:36:25:2f:29:ee:01:d7:1d:b6
+ f6:07:2c:b6:8f:b4:66:82:48:c0:3a:d8:2f:bb:d7:bb
+ f4:f0:54:59:ea:66:d9:e9:f8:f2:dc:a8:76:d3:5f:30
+ 8e:18:37:2e:b8:a1:64:b6:d6:14:8e:9d:ef:d2:90:45
+ a8:fe:f9:a6:75:42:9b:69:d7:99:df:2e:f9:58:b8:a0
+ 7c:57:d6:10:c4:7b:b3:30:d0:4e:80:74:37:1a:86:bb
+ d8:bf:90:9f:62:be:f1:22:bf:0c:cf:d7:c0:cf:4c:e0
+ 4b:
+
+prime2:
+ 00:c1:54:5d:70:8f:b2:93:c7:68:a0:a3:0f:3b:00:9d
+ f7:99:96:2f:6f:46:11:7a:71:e8:be:84:b4:57:3f:32
+ d0:bc:09:64:04:61:e7:ce:a9:e2:60:a4:6e:18:8a:e1
+ f0:05:88:e0:a1:ae:6e:9e:db:f9:39:8d:04:b4:12:a5
+ 0d:fe:bb:95:ce:bc:13:5a:3a:a9:18:a3:7b:70:39:99
+ d4:a4:eb:92:22:6a:85:66:f9:50:b7:fe:10:b9:a7:d3
+ f3:2b:96:66:93:e8:00:ae:c7:eb:cb:08:cc:3e:d3:ab
+ c3:aa:a4:4a:36:72:07:c8:eb:6b:75:17:f2:1f:e3:32
+ f0:db:ce:8b:5b:93:e7:dc:58:56:ab:38:5e:72:3c:3b
+ b1:08:c5:51:b0:fe:21:d9:a0:63:49:5b:bc:c6:fe:de
+ 99:1d:9e:84:37:dc:4f:63:0d:ff:0c:b5:33:8e:18:74
+ 6d:9f:07:45:ca:1b:15:c7:83:64:6f:2b:39:73:87:59
+ 15:
+
+coefficient:
+ 25:67:50:eb:3e:0e:3c:1d:a4:71:da:11:9b:64:59:83
+ ad:df:a3:82:07:aa:3e:a2:c6:cc:c1:6f:cf:5e:09:f7
+ 18:f8:a1:75:6a:43:99:c1:a8:01:2c:43:b8:d4:7f:5a
+ fe:a5:aa:3d:18:a8:39:5d:87:f6:88:fb:22:a4:13:09
+ 92:bb:8f:e4:23:5f:07:e7:3c:11:2a:55:38:35:86:ad
+ 63:44:ae:7b:25:1a:27:58:47:b2:ca:a2:07:04:d5:e5
+ c9:af:b1:09:da:1d:5c:49:0b:07:cd:93:b2:70:1e:9a
+ 2d:90:e0:80:75:93:8f:4e:97:1a:c6:af:a4:6c:9d:fe
+ 5c:41:80:5c:3f:2e:c7:b3:7f:ed:36:78:46:50:ef:c7
+ 6f:fe:1b:b0:60:f0:3a:d9:67:7f:23:74:9d:c4:10:70
+ 00:cd:27:a3:72:45:35:17:a5:86:17:be:e6:4a:70:a4
+ 03:9d:ea:70:50:64:65:f1:30:56:ce:0e:b7:e2:58:a7
+
+
+exp1:
+ 00:9e:dc:1e:37:a5:30:f0:a8:69:f8:87:85:53:9d:0b
+ f4:2c:9b:fd:fe:3b:51:31:db:a5:8a:4a:32:56:c5:34
+ ca:47:50:63:f5:c6:6e:c6:a1:2f:67:19:63:82:a1:24
+ 8f:2c:d7:d5:0e:4e:0d:f7:10:e3:02:cc:0a:de:3a:a2
+ 8b:4d:b6:82:dd:9c:a5:03:58:4a:80:dc:0f:ed:f4:34
+ 38:7f:7a:e3:47:fc:64:e2:1d:51:fa:11:a2:54:a9:d8
+ 70:5d:82:2f:52:5e:6b:38:45:fe:32:c3:ed:3d:16:dc
+ 9f:fa:65:e5:9c:26:8a:c5:3a:dc:7b:02:0d:dc:eb:43
+ 78:a9:c9:1e:cd:91:a1:d2:3f:e3:c8:ef:46:a7:51:b3
+ a1:10:9a:98:58:bd:78:83:9d:b8:3a:21:26:15:eb:c1
+ ee:87:5d:f0:3c:63:33:43:ab:25:f3:fe:9e:2d:03:2f
+ 1d:91:2d:f7:57:a1:35:91:1a:0d:da:7f:92:54:71:fb
+ a9:
+
+exp2:
+ 2b:a1:e5:c0:cc:bd:a9:fa:9c:53:7c:d9:a8:20:58:86
+ 94:24:40:2a:65:ee:f5:ea:95:73:c2:31:8d:6b:57:05
+ a3:1a:9f:77:19:bd:9e:77:da:fe:a2:bd:b2:4e:4d:f5
+ c4:da:02:90:9a:f4:9e:67:d9:14:b3:0d:f7:b2:29:8c
+ 42:0c:86:1f:f5:74:8c:ad:a6:92:47:fb:48:f5:c7:11
+ 25:f3:80:b4:c1:c3:bf:dc:ce:e9:e7:ae:50:a8:5e:fe
+ 87:bc:d7:03:d4:9d:aa:d4:b6:13:c9:b5:87:0c:70:bc
+ a5:5b:94:e0:3a:d6:24:f3:74:fa:25:60:60:ef:ff:04
+ 3b:27:9f:6e:18:b0:80:9b:73:5c:0b:49:cd:90:68:8c
+ 69:05:57:8d:91:9d:84:27:5d:a1:25:d2:32:3b:3d:73
+ e3:2a:6e:7e:c8:fb:25:c8:f7:e2:1f:57:36:5f:b0:8f
+ 39:10:04:21:3c:01:ab:58:ad:27:25:e3:3e:7e:b2:8d
+
+
+
+Public Key PIN:
+ pin-sha256:39HEiK68VoVnfvhMoLHp8J9yLV3u4CjiDlcNu1pmUxw=
+Public Key ID:
+ sha256:dfd1c488aebc5685677ef84ca0b1e9f09f722d5deee028e20e570dbb5a66531c
+ sha1:fab196e0eaaf8f48c8f4fed07c97e4aabba3a1ed
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIG4wIBAAKCAYEAuvhcFRuFvTJeWcSuc6KwZwYHaeaIBE2YRV22Q3iLH1OzIgrR
+skh4lji4mBoIQRIqOMw6rridc+p8unoI6rwk85sfgetUbtkWshW8ZjZ4QwwGjrqq
+tvT7EtQfBCKzWwOPt6Yl31jEyrMJxFSypCX6r6Yk2IOfGPwIbNr0rmhXddaowb/b
+OfewwmONlRgBCu6B0OJ21VJcJfiqDKh/NMrivA40vmN/SgGQNlxN2dnuBfziyKEy
+dtw5VDPdxLgeofUjwX87av+EmGAfU4a/rNZOWJqRqhMepZ5h67qMNdqJTUh7gs+V
+1Gj9JKpDnDpbhvZ9LUHcYzYswsULSQRG88S54BnkBoBMn58aa5qIQmxsqUgcnf+h
+cSxEWuU/tLy2Q9tHH2UVQT/Qeo/BHPCTEccTdF98R+ksvNB6/MCJbukegoXvp69X
+0/yvx5o0BmAu27wB1QhjjgcnUToXCnEiVuvpr/ZIMUKHLZUFrfjGdrwXL1BHaJVK
+a03OulErHuGz1XknAgMBAAECggGAOpWNjNOV4UWCCNC29Oa6LV3XPtiMMAT8PGfz
+r0p7FTLNw1HuiNVqa2+Ub5xgj7sYXbmofYy9UU/dCzUnzB1t2qL1iWjqiN0e3mgv
+IzvYDPIcr+ZPjYpPjYPHwCz7U/5Wru2xmj0fVIArBEihvQtldFoz21/KnzKBlVI6
+Ltfk57h+Ik9yasRwr0gwWdIuqXU4WfJL1+W5l/BFoDe/vBQCXXh6EFnuzYqV3GKp
+xiQiD+BAUysnO9UfZYHm8Dfc6VR8+IHw4PeABayRFFtdAxTCuXRk9qtupc7nvFZY
+QTAxjun1bjFiMWmoF0SJs7mVXM38QIYsoR1GSJQmrjW/FA6uSS1WX73g1vUD7d+l
+qUOJXAEzXuY+gNqf3y3H6BNgMicOdxH9Q5QrbmsSeapZ5lS2R9x4fv5LQDISOsxt
+0p9J/LLcb9ABAx0dqOXuYNeMJn9zisCBlf+2xV7Xa1AfAmE7MfRB2gqnxurSosS1
+bc4/4DsDpzg324iCGAmi2s93OjP5AoHBAPeUQRaYDMuhMagzOJYD8TO/twMbKLxD
+YkB35tl1ZHf/ZFE0nGU6yhAPPs437xSF1X317inzdgLE3VKRXC5+rXZrZelg/Hww
+ZJq7ZSI5o6BUeJsVLafhNiUvKe4B1x229gcsto+0ZoJIwDrYL7vXu/TwVFnqZtnp
++PLcqHbTXzCOGDcuuKFkttYUjp3v0pBFqP75pnVCm2nXmd8u+Vi4oHxX1hDEe7Mw
+0E6AdDcahrvYv5CfYr7xIr8Mz9fAz0zgSwKBwQDBVF1wj7KTx2igow87AJ33mZYv
+b0YRenHovoS0Vz8y0LwJZARh586p4mCkbhiK4fAFiOChrm6e2/k5jQS0EqUN/ruV
+zrwTWjqpGKN7cDmZ1KTrkiJqhWb5ULf+ELmn0/MrlmaT6ACux+vLCMw+06vDqqRK
+NnIHyOtrdRfyH+My8NvOi1uT59xYVqs4XnI8O7EIxVGw/iHZoGNJW7zG/t6ZHZ6E
+N9xPYw3/DLUzjhh0bZ8HRcobFceDZG8rOXOHWRUCgcEAntweN6Uw8Khp+IeFU50L
+9Cyb/f47UTHbpYpKMlbFNMpHUGP1xm7GoS9nGWOCoSSPLNfVDk4N9xDjAswK3jqi
+i022gt2cpQNYSoDcD+30NDh/euNH/GTiHVH6EaJUqdhwXYIvUl5rOEX+MsPtPRbc
+n/pl5ZwmisU63HsCDdzrQ3ipyR7NkaHSP+PI70anUbOhEJqYWL14g524OiEmFevB
+7odd8DxjM0OrJfP+ni0DLx2RLfdXoTWRGg3af5JUcfupAoHAK6HlwMy9qfqcU3zZ
+qCBYhpQkQCpl7vXqlXPCMY1rVwWjGp93Gb2ed9r+or2yTk31xNoCkJr0nmfZFLMN
+97IpjEIMhh/1dIytppJH+0j1xxEl84C0wcO/3M7p565QqF7+h7zXA9SdqtS2E8m1
+hwxwvKVblOA61iTzdPolYGDv/wQ7J59uGLCAm3NcC0nNkGiMaQVXjZGdhCddoSXS
+Mjs9c+Mqbn7I+yXI9+IfVzZfsI85EAQhPAGrWK0nJeM+frKNAoHAJWdQ6z4OPB2k
+cdoRm2RZg63fo4IHqj6ixszBb89eCfcY+KF1akOZwagBLEO41H9a/qWqPRioOV2H
+9oj7IqQTCZK7j+QjXwfnPBEqVTg1hq1jRK57JRonWEeyyqIHBNXlya+xCdodXEkL
+B82TsnAemi2Q4IB1k49OlxrGr6Rsnf5cQYBcPy7Hs3/tNnhGUO/Hb/4bsGDwOtln
+fyN0ncQQcADNJ6NyRTUXpYYXvuZKcKQDnepwUGRl8TBWzg634lin
+-----END RSA PRIVATE KEY-----
diff --git a/daemon/lua/trust_anchors.test/x509/server.pem b/daemon/lua/trust_anchors.test/x509/server.pem
new file mode 100644
index 0000000..47fb6a4
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/server.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEezCCAuOgAwIBAgIUAYjlKE92sFzGxwB2+PN+dV2rKOEwDQYJKoZIhvcNAQEL
+BQAwIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZlciB0ZXN0aW5nIENBMB4XDTE5MDEw
+MzE2MjczM1oXDTIwMDEwMzE2MjczM1owPDESMBAGA1UEAxMJbG9jYWxob3N0MSYw
+JAYDVQQKEx1GYWtlIEROUyByb290IG9yZyB0ZXN0IHNlcnZlcjCCAaIwDQYJKoZI
+hvcNAQEBBQADggGPADCCAYoCggGBALr4XBUbhb0yXlnErnOisGcGB2nmiARNmEVd
+tkN4ix9TsyIK0bJIeJY4uJgaCEESKjjMOq64nXPqfLp6COq8JPObH4HrVG7ZFrIV
+vGY2eEMMBo66qrb0+xLUHwQis1sDj7emJd9YxMqzCcRUsqQl+q+mJNiDnxj8CGza
+9K5oV3XWqMG/2zn3sMJjjZUYAQrugdDidtVSXCX4qgyofzTK4rwONL5jf0oBkDZc
+TdnZ7gX84sihMnbcOVQz3cS4HqH1I8F/O2r/hJhgH1OGv6zWTliakaoTHqWeYeu6
+jDXaiU1Ie4LPldRo/SSqQ5w6W4b2fS1B3GM2LMLFC0kERvPEueAZ5AaATJ+fGmua
+iEJsbKlIHJ3/oXEsRFrlP7S8tkPbRx9lFUE/0HqPwRzwkxHHE3RffEfpLLzQevzA
+iW7pHoKF76evV9P8r8eaNAZgLtu8AdUIY44HJ1E6FwpxIlbr6a/2SDFChy2VBa34
+xna8Fy9QR2iVSmtNzrpRKx7hs9V5JwIDAQABo4GNMIGKMAwGA1UdEwEB/wQCMAAw
+FAYDVR0RBA0wC4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
+DwEB/wQFAwMHoAAwHQYDVR0OBBYEFPqxluDqr49IyPT+0HyX5Kq7o6HtMB8GA1Ud
+IwQYMBaAFDAX4bzP3gaOQYZyZC0eGBqQmIJkMA0GCSqGSIb3DQEBCwUAA4IBgQCA
+K1GVJ9NhEbkH4vbZrRtUwyIWJgFbUXy3TzXIRZTUMgMzEZXDyVkElOcGPxev6kMt
+TdSRZrc2DteAnWpJ6fSUt3hU13O7r7H8R7jF3UvqotgiQi+lOvUKPKUiU3ecuClO
+NBFE7bjiTOtMyiGWjsVcBb3aHcPjEWm8pTPW3yuSXdg0J5pBjjxqK8m3ExMPK3P+
+sBE7eSTuHiLBWAIRgM1I/F5COV7QKX1CM2COmJWEDS1t3qORKZPzNWuaVXwtqbbA
+qC4OIpT8DakSLWyPK74vLx1yoUJu5wtoifHG9nnrFvstLE4DNLiB/fN08FI1ka13
+hJyJMQecl84kunX1hkwP/o4IrZ/pSWk2d9PCm35Td/g7jaI+06IqnTIyQblXRYIQ
+iPfmCWEyFBwFJQVND2c0JJfNeu16qVniarwnXa8z6j5eBI3E94weH0ZOdOSKD//c
+iQvU7JpoC9Gc+jJFJqVE5RE7ueZRqN+7dJbe4CyrAhm2U0zwel9XNZtWaHMHT7Y=
+-----END CERTIFICATE-----
diff --git a/daemon/lua/trust_anchors.test/x509/server.tmpl b/daemon/lua/trust_anchors.test/x509/server.tmpl
new file mode 100644
index 0000000..7ee40d2
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/server.tmpl
@@ -0,0 +1,6 @@
+organization = Fake DNS root org test server
+cn = localhost
+tls_www_server
+encryption_key
+signing_key
+dns_name = localhost
diff --git a/daemon/lua/trust_anchors.test/x509/wrongca-key.pem b/daemon/lua/trust_anchors.test/x509/wrongca-key.pem
new file mode 100644
index 0000000..56b69fe
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/wrongca-key.pem
@@ -0,0 +1,182 @@
+Public Key Info:
+ Public Key Algorithm: RSA
+ Key Security Level: High (3072 bits)
+
+modulus:
+ 00:e4:cc:5d:b2:a0:a5:0e:94:f2:d1:ca:cd:04:36:72
+ 95:9a:61:ac:c7:d6:35:24:63:d6:3b:2d:38:4b:a5:69
+ fe:91:f5:d8:a6:db:df:9a:17:fd:21:5d:bc:94:9f:3b
+ cf:f7:fa:9d:1d:7f:22:0f:90:36:91:4f:3a:92:77:55
+ 6e:33:e6:c9:94:a6:a1:c7:e6:14:f4:3d:27:18:32:5c
+ 0c:9a:5d:5a:8a:77:45:50:6e:00:e7:c5:14:cf:4f:75
+ 39:60:99:5b:5d:78:61:f1:5e:cb:6e:78:81:fb:fe:ad
+ dc:50:89:ce:b6:4d:78:79:e2:84:1c:c1:be:72:ce:d5
+ 7c:66:82:46:bb:da:ba:cc:69:4c:66:dc:a9:86:56:c3
+ 0e:bc:a4:b9:85:0b:c9:07:6f:a4:a7:24:33:ee:bc:02
+ 9d:73:61:82:e0:f0:dd:a6:e9:24:a0:ca:db:14:09:79
+ 9b:66:6f:2b:4f:19:b0:d3:44:85:04:c3:e6:b6:38:a0
+ 57:8b:bb:b2:95:03:ab:45:97:9c:fd:55:fd:bc:84:64
+ 9b:f1:f0:6a:c7:be:e3:3d:1c:ba:7b:b9:b9:3d:96:ca
+ ad:ac:c3:dc:69:80:ba:27:1a:91:73:61:11:64:09:7b
+ e6:c7:ac:ef:16:66:2f:c1:dc:8e:8c:4b:23:8f:14:39
+ 01:20:2b:97:fd:30:72:bd:12:e1:e1:4b:b7:2f:ec:08
+ 47:4b:7c:ec:8f:2f:b1:2b:5d:b0:af:fe:f0:0e:8e:5e
+ 90:87:53:3f:f9:4f:fe:cf:9e:6a:ac:41:4e:30:be:93
+ 5c:2b:7e:d3:14:c6:a6:23:ac:aa:4c:88:73:f9:79:77
+ df:e3:66:0a:9b:fa:b2:7e:1d:f4:23:1d:e9:76:48:2e
+ 48:34:0d:92:7d:30:05:ea:00:6e:57:cd:23:49:2b:35
+ d0:08:6d:84:ab:9d:6f:54:92:16:8e:cb:c5:25:76:96
+ 02:c3:9f:89:33:4c:a3:ee:d4:ee:cc:53:bc:a3:a8:30
+ a7:
+
+public exponent:
+ 01:00:01:
+
+private exponent:
+ 42:04:6d:ec:c2:c9:9d:81:80:e6:e3:db:70:21:bd:c2
+ 48:ff:71:f9:5f:67:8a:0e:7c:9c:2a:9a:19:c9:aa:e1
+ d7:7f:d6:79:9e:eb:cf:ec:a8:0f:5e:9a:b1:4f:98:d4
+ 93:3a:ee:e6:b9:ee:3c:0b:62:93:5f:07:09:88:01:50
+ 81:0d:50:90:e1:db:c0:70:35:0f:9c:2d:91:9f:c6:4b
+ a4:a6:d7:1f:28:f7:09:14:14:92:cb:9d:0b:8c:63:c8
+ a0:84:df:86:02:dc:4f:e7:08:4f:e1:d9:af:ba:76:b4
+ 21:51:02:22:3c:0c:4d:2e:fc:eb:c7:43:8a:a0:ff:9c
+ 9b:7f:6f:a5:78:a1:79:a5:d0:73:c5:ab:ec:a5:50:37
+ 0a:5b:85:64:4c:58:3f:1b:09:a9:68:9a:cb:81:d8:47
+ 52:29:8a:b8:19:07:fb:c7:3d:d8:5f:70:03:42:10:a2
+ b4:55:d3:6b:49:80:6a:15:55:44:60:ba:4b:4d:df:85
+ 97:7c:47:4f:3e:8c:46:ed:72:7b:d8:63:5d:4d:05:96
+ f8:47:e4:65:c9:18:8f:13:e4:36:24:cf:c7:fe:3e:2e
+ 9c:7f:09:5a:0a:3d:0a:6a:f7:e6:27:0b:ed:8c:fc:db
+ 9c:c3:77:fd:6d:f2:d5:ab:c3:0e:3a:cb:49:95:8a:f3
+ 9b:ab:3e:2c:d2:82:9a:61:f0:2c:2c:f7:37:ad:f5:32
+ ee:96:f8:cc:a4:a1:97:19:45:fc:8c:82:33:ca:73:86
+ 7e:79:94:fe:6a:a0:53:bf:8b:7d:b0:da:1c:a4:f4:7f
+ a1:d9:4f:76:75:20:35:b4:69:9c:c8:23:7e:07:b9:81
+ 4e:04:81:8e:88:d5:83:51:6e:cc:f2:6b:e9:af:ad:bc
+ 42:f9:2c:8a:c3:54:ce:f1:1a:bc:2c:8f:99:a0:a6:0a
+ cc:92:5b:6d:78:c7:c0:cc:7d:eb:42:eb:c3:69:46:0b
+ 33:d1:3e:c8:1a:13:e8:db:ee:ea:d4:91:77:0b:44:51
+
+
+prime1:
+ 00:e9:63:e7:aa:1a:2e:41:51:17:10:33:53:de:95:f1
+ 7a:8e:55:57:c5:f8:a4:25:c3:e1:83:fe:ad:68:fe:d3
+ 86:10:f7:f0:9a:d2:69:dc:15:bc:0c:e4:44:d3:98:0a
+ 0d:71:77:e6:53:36:57:a2:2e:22:b1:e7:8d:4a:64:33
+ 0f:5c:11:20:bb:ae:21:46:d4:82:35:b1:9a:95:96:22
+ 50:c6:6c:8c:e5:55:93:d0:87:9a:3a:6a:3f:13:8a:3b
+ cf:0e:04:38:b5:19:2e:95:d4:d8:2d:80:36:ac:18:63
+ 74:ad:ea:eb:20:f5:9a:13:1e:82:c2:10:9f:78:12:f8
+ 74:af:a4:ee:29:bb:da:d9:a4:fa:a7:25:4f:ae:52:3e
+ 92:02:dd:f6:65:28:0c:27:36:d1:3d:a2:7c:c7:01:55
+ 5c:c1:88:d4:3e:fd:92:70:4a:15:43:0a:5b:ec:37:ea
+ 12:42:db:7c:45:c6:2f:bc:ac:a7:c7:f2:4b:4a:b0:3c
+ 63:
+
+prime2:
+ 00:fa:f6:94:95:53:f4:c7:7e:93:e1:3c:c6:6f:97:d2
+ a0:52:44:04:56:18:40:fb:f0:c0:5b:00:cb:45:71:c9
+ 54:c9:e2:50:91:77:ee:60:f3:33:fc:c5:7e:fc:07:7e
+ 06:5e:5e:8e:db:18:72:83:9c:00:02:d0:a0:86:5c:9f
+ 80:72:37:16:3e:39:3f:b7:1c:08:c8:79:ed:51:f2:4d
+ 61:3a:48:4b:09:7d:a5:f4:7a:8e:b4:5c:cc:0d:56:66
+ 98:2e:fc:2f:26:1c:75:6f:a4:7d:2b:7d:f9:5c:dc:52
+ a9:51:f9:26:b0:94:71:5e:d2:8e:85:38:02:57:9b:31
+ 74:40:7b:81:65:ad:56:5a:b4:ac:b8:40:80:7d:b2:a2
+ 5b:81:9b:b3:c2:41:de:7d:f6:66:b0:a2:a7:13:11:a6
+ 07:51:6f:e9:79:97:43:67:dd:8f:a2:c3:e3:5b:74:50
+ d9:c2:e7:cf:2d:5b:9e:bf:01:c0:77:cf:9c:06:22:63
+ ed:
+
+coefficient:
+ 00:83:36:59:9d:73:95:c9:07:11:60:b5:0f:1e:8d:81
+ ee:78:9e:92:50:4c:e7:00:a6:21:8a:e8:fd:12:de:6c
+ 2d:b3:99:53:90:79:74:e2:75:7b:f8:d9:d1:c5:29:28
+ 9e:bc:5a:ad:28:7c:8b:47:14:17:14:a6:08:e6:ec:9d
+ dd:74:9b:d5:bb:c6:54:24:25:81:a2:b7:b6:8a:bd:e4
+ da:f5:7e:08:4a:cd:f2:7c:2a:cc:bf:ed:c3:d4:66:05
+ 3f:f8:39:07:77:e6:13:cf:88:28:d5:1e:2a:4b:13:66
+ 59:39:ef:7b:3d:43:fa:77:9f:64:81:45:52:da:03:b0
+ 2b:04:46:f1:79:a0:bc:08:b1:9b:0c:ae:91:4b:96:08
+ ad:26:a0:a6:f1:c4:d5:74:c6:f8:4a:7c:01:5c:12:99
+ 1e:58:92:b9:e5:13:20:0b:fc:b4:2a:af:00:00:a3:22
+ 24:1c:f8:f9:67:ec:37:c0:fd:22:c7:97:cb:4f:85:e5
+ fb:
+
+exp1:
+ 07:0a:5d:7d:b3:26:7c:0f:ef:2b:2c:f1:35:c0:be:35
+ 1d:40:13:d2:c4:0a:67:9d:3e:1b:56:3e:72:f1:64:fe
+ 21:5a:e9:66:32:3c:c1:47:e2:91:5c:fd:7a:88:96:9d
+ 0b:34:3b:bd:7c:e1:2d:e2:48:67:a7:7d:8a:a5:f5:28
+ 5c:75:a3:d0:25:93:99:68:65:b9:2a:ef:67:dd:cc:91
+ 35:3b:27:10:f0:00:f2:84:74:b1:98:6c:e8:b0:fd:d6
+ e4:2c:5a:6d:94:21:e4:a1:34:18:43:4a:e2:ec:25:6b
+ ea:a7:30:8a:a0:fe:11:df:94:c0:37:f2:27:94:22:ec
+ 9a:33:d6:7f:69:cc:53:4e:77:c5:3d:09:e7:4f:51:d5
+ e3:c2:40:61:92:d0:b3:0a:23:4b:c1:b0:13:ec:c5:5f
+ 73:f3:25:bb:f4:b8:4b:2c:e0:f1:51:c9:ae:19:8d:b5
+ 19:51:37:b1:7f:26:07:82:09:d9:ad:44:7a:2f:50:d3
+
+
+exp2:
+ 54:61:21:d6:0f:73:66:bb:ed:56:b6:cb:75:6b:d3:9a
+ a9:ee:4a:92:f3:f6:ad:7e:e5:fd:f3:07:65:62:fb:b1
+ 71:6b:91:71:47:a4:b3:9e:31:e5:94:35:bc:e1:7c:03
+ 02:29:c2:d8:71:a0:d7:15:55:7f:9c:cb:cc:41:4a:33
+ b2:b3:48:dc:44:fd:62:40:9f:c7:60:0f:66:15:14:e5
+ 52:e6:49:ac:78:3d:9b:34:b5:d4:78:ba:f6:e5:0c:fb
+ b0:18:84:75:c8:ed:c7:4a:c3:f7:22:94:fc:1e:ec:00
+ 18:1a:b0:62:80:96:99:ae:2b:d3:28:e0:c8:b9:da:67
+ de:e2:67:c0:5b:06:84:da:e8:93:ce:c4:24:ff:31:cd
+ 98:87:54:6a:45:21:5c:b2:c3:16:32:aa:00:24:57:f1
+ 6f:f5:33:c7:f9:0e:e5:d6:3d:dc:19:06:d1:92:0b:39
+ 1c:6a:3a:63:62:c1:be:31:05:98:83:0a:4f:99:b2:85
+
+
+
+Public Key PIN:
+ pin-sha256:75s1y3dUDBiOALg1xQKcI2/wXlZsFuALytI0Khf7WYQ=
+Public Key ID:
+ sha256:ef9b35cb77540c188e00b835c5029c236ff05e566c16e00bcad2342a17fb5984
+ sha1:f91b5944557c4e3bb4cee62a6a2a525b950bbd99
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIG4wIBAAKCAYEA5MxdsqClDpTy0crNBDZylZphrMfWNSRj1jstOEulaf6R9dim
+29+aF/0hXbyUnzvP9/qdHX8iD5A2kU86kndVbjPmyZSmocfmFPQ9JxgyXAyaXVqK
+d0VQbgDnxRTPT3U5YJlbXXhh8V7LbniB+/6t3FCJzrZNeHnihBzBvnLO1Xxmgka7
+2rrMaUxm3KmGVsMOvKS5hQvJB2+kpyQz7rwCnXNhguDw3abpJKDK2xQJeZtmbytP
+GbDTRIUEw+a2OKBXi7uylQOrRZec/VX9vIRkm/Hwase+4z0cunu5uT2Wyq2sw9xp
+gLonGpFzYRFkCXvmx6zvFmYvwdyOjEsjjxQ5ASArl/0wcr0S4eFLty/sCEdLfOyP
+L7ErXbCv/vAOjl6Qh1M/+U/+z55qrEFOML6TXCt+0xTGpiOsqkyIc/l5d9/jZgqb
++rJ+HfQjHel2SC5INA2SfTAF6gBuV80jSSs10AhthKudb1SSFo7LxSV2lgLDn4kz
+TKPu1O7MU7yjqDCnAgMBAAECggGAQgRt7MLJnYGA5uPbcCG9wkj/cflfZ4oOfJwq
+mhnJquHXf9Z5nuvP7KgPXpqxT5jUkzru5rnuPAtik18HCYgBUIENUJDh28BwNQ+c
+LZGfxkukptcfKPcJFBSSy50LjGPIoITfhgLcT+cIT+HZr7p2tCFRAiI8DE0u/OvH
+Q4qg/5ybf2+leKF5pdBzxavspVA3CluFZExYPxsJqWiay4HYR1IpirgZB/vHPdhf
+cANCEKK0VdNrSYBqFVVEYLpLTd+Fl3xHTz6MRu1ye9hjXU0FlvhH5GXJGI8T5DYk
+z8f+Pi6cfwlaCj0KavfmJwvtjPzbnMN3/W3y1avDDjrLSZWK85urPizSgpph8Cws
+9zet9TLulvjMpKGXGUX8jIIzynOGfnmU/mqgU7+LfbDaHKT0f6HZT3Z1IDW0aZzI
+I34HuYFOBIGOiNWDUW7M8mvpr628QvksisNUzvEavCyPmaCmCsySW214x8DMfetC
+68NpRgsz0T7IGhPo2+7q1JF3C0RRAoHBAOlj56oaLkFRFxAzU96V8XqOVVfF+KQl
+w+GD/q1o/tOGEPfwmtJp3BW8DORE05gKDXF35lM2V6IuIrHnjUpkMw9cESC7riFG
+1II1sZqVliJQxmyM5VWT0IeaOmo/E4o7zw4EOLUZLpXU2C2ANqwYY3St6usg9ZoT
+HoLCEJ94Evh0r6TuKbva2aT6pyVPrlI+kgLd9mUoDCc20T2ifMcBVVzBiNQ+/ZJw
+ShVDClvsN+oSQtt8RcYvvKynx/JLSrA8YwKBwQD69pSVU/THfpPhPMZvl9KgUkQE
+VhhA+/DAWwDLRXHJVMniUJF37mDzM/zFfvwHfgZeXo7bGHKDnAAC0KCGXJ+AcjcW
+Pjk/txwIyHntUfJNYTpISwl9pfR6jrRczA1WZpgu/C8mHHVvpH0rfflc3FKpUfkm
+sJRxXtKOhTgCV5sxdEB7gWWtVlq0rLhAgH2yoluBm7PCQd599mawoqcTEaYHUW/p
+eZdDZ92PosPjW3RQ2cLnzy1bnr8BwHfPnAYiY+0CgcAHCl19syZ8D+8rLPE1wL41
+HUAT0sQKZ50+G1Y+cvFk/iFa6WYyPMFH4pFc/XqIlp0LNDu9fOEt4khnp32KpfUo
+XHWj0CWTmWhluSrvZ93MkTU7JxDwAPKEdLGYbOiw/dbkLFptlCHkoTQYQ0ri7CVr
+6qcwiqD+Ed+UwDfyJ5Qi7Joz1n9pzFNOd8U9CedPUdXjwkBhktCzCiNLwbAT7MVf
+c/Mlu/S4Syzg8VHJrhmNtRlRN7F/JgeCCdmtRHovUNMCgcBUYSHWD3Nmu+1Wtst1
+a9Oaqe5KkvP2rX7l/fMHZWL7sXFrkXFHpLOeMeWUNbzhfAMCKcLYcaDXFVV/nMvM
+QUozsrNI3ET9YkCfx2APZhUU5VLmSax4PZs0tdR4uvblDPuwGIR1yO3HSsP3IpT8
+HuwAGBqwYoCWma4r0yjgyLnaZ97iZ8BbBoTa6JPOxCT/Mc2Yh1RqRSFcssMWMqoA
+JFfxb/Uzx/kO5dY93BkG0ZILORxqOmNiwb4xBZiDCk+ZsoUCgcEAgzZZnXOVyQcR
+YLUPHo2B7nieklBM5wCmIYro/RLebC2zmVOQeXTidXv42dHFKSievFqtKHyLRxQX
+FKYI5uyd3XSb1bvGVCQlgaK3toq95Nr1fghKzfJ8Ksy/7cPUZgU/+DkHd+YTz4go
+1R4qSxNmWTnvez1D+nefZIFFUtoDsCsERvF5oLwIsZsMrpFLlgitJqCm8cTVdMb4
+SnwBXBKZHliSueUTIAv8tCqvAACjIiQc+Pln7DfA/SLHl8tPheX7
+-----END RSA PRIVATE KEY-----
diff --git a/daemon/lua/trust_anchors.test/x509/wrongca.pem b/daemon/lua/trust_anchors.test/x509/wrongca.pem
new file mode 100644
index 0000000..2b536b9
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/wrongca.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAnegAwIBAgIUSDi0iXY6awSZoHpkeTMf8gD8u8UwDQYJKoZIhvcNAQEL
+BQAwHzEdMBsGA1UEAxMUQW5vdGhlciB1bnJlbGF0ZWQgQ0EwHhcNMTkwMTAzMTYy
+NzMzWhcNMjAwMTAzMTYyNzMzWjAfMR0wGwYDVQQDExRBbm90aGVyIHVucmVsYXRl
+ZCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOTMXbKgpQ6U8tHK
+zQQ2cpWaYazH1jUkY9Y7LThLpWn+kfXYptvfmhf9IV28lJ87z/f6nR1/Ig+QNpFP
+OpJ3VW4z5smUpqHH5hT0PScYMlwMml1aindFUG4A58UUz091OWCZW114YfFey254
+gfv+rdxQic62TXh54oQcwb5yztV8ZoJGu9q6zGlMZtyphlbDDrykuYULyQdvpKck
+M+68Ap1zYYLg8N2m6SSgytsUCXmbZm8rTxmw00SFBMPmtjigV4u7spUDq0WXnP1V
+/byEZJvx8GrHvuM9HLp7ubk9lsqtrMPcaYC6JxqRc2ERZAl75ses7xZmL8HcjoxL
+I48UOQEgK5f9MHK9EuHhS7cv7AhHS3zsjy+xK12wr/7wDo5ekIdTP/lP/s+eaqxB
+TjC+k1wrftMUxqYjrKpMiHP5eXff42YKm/qyfh30Ix3pdkguSDQNkn0wBeoAblfN
+I0krNdAIbYSrnW9UkhaOy8UldpYCw5+JM0yj7tTuzFO8o6gwpwIDAQABo0MwQTAP
+BgNVHRMBAf8EBTADAQH/MA8GA1UdDwEB/wQFAwMHBAAwHQYDVR0OBBYEFPkbWURV
+fE47tM7mKmoqUluVC72ZMA0GCSqGSIb3DQEBCwUAA4IBgQB7Nbn5Pc4eX8XuVnk+
+RzkB1XZ8UubdQKBUGZ2t4TFZDBuu6MZDhgyyJUU2rD/ndp+9No0BLCEB/qhL5p5W
+SEsSb0t0jmJFmCkHdBefUPqhOW7k7ttuEGN8ctWXloczFrV1dJNR1gfcb6guhIGu
+Q5k+xM0x69VbM0iTMaXqEtAYQqzmDsEpRep6lUbd9g7MLhRE9Y5NWms4EzLlKypR
+z0TSdSFPS/Fz//bzEct/e+Wp8ss42/0JljQMIRYYD7i+x6azF1brhvnadfNmOVFE
+1iN0SnHsPeuD3+v7eji389tnRF3xjKO1Vx8BUVPkQqAWt13xxDqhV41MjyZwFynB
+aG+zGYZ0DyTE/8/ouN5+SN0eMwW/oFSckvFIvCXqS9qf3YuMo+e0eJ/xKozejg+r
+DPUVGmKEFfEZYWjxl3ZVrM4tfSkjzwRKNPZ1NaXcDU3/fbfWtR2cGnNi60AquENA
+GUxwKfDZA3n8dOJQwFRnGXiBNT7nwZZXdow00kGuN/0BZ6Q=
+-----END CERTIFICATE-----
diff --git a/daemon/lua/trust_anchors.test/x509/wrongca.tmpl b/daemon/lua/trust_anchors.test/x509/wrongca.tmpl
new file mode 100644
index 0000000..81dfbfc
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/wrongca.tmpl
@@ -0,0 +1,3 @@
+cn = Another unrelated CA
+ca
+cert_signing_key
diff --git a/daemon/lua/zonefile.lua b/daemon/lua/zonefile.lua
new file mode 100644
index 0000000..fdfc211
--- /dev/null
+++ b/daemon/lua/zonefile.lua
@@ -0,0 +1,91 @@
+-- LuaJIT ffi bindings for zscanner, a DNS zone parser.
+-- Author: Marek Vavrusa <marek.vavrusa@nic.cz>
+
+local ffi = require('ffi')
+local libzscanner = ffi.load(libzscanner_SONAME)
+
+-- Wrap scanner context
+local zs_scanner_t = ffi.typeof('zs_scanner_t')
+ffi.metatype( zs_scanner_t, {
+ __gc = function(zs) return libzscanner.zs_deinit(zs) end,
+ __new = function(ct, origin, class, ttl)
+ if not class then class = 1 end
+ if not ttl then ttl = 3600 end
+ local parser = ffi.new(ct)
+ libzscanner.zs_init(parser, origin, class, ttl)
+ return parser
+ end,
+ __index = {
+ open = function (zs, file)
+ assert(ffi.istype(zs, zs_scanner_t))
+ local ret = libzscanner.zs_set_input_file(zs, file)
+ if ret ~= 0 then return false, zs:strerr() end
+ return true
+ end,
+ parse = function(zs, input)
+ assert(ffi.istype(zs, zs_scanner_t))
+ if input ~= nil then libzscanner.zs_set_input_string(zs, input, #input) end
+ local ret = libzscanner.zs_parse_record(zs)
+ -- Return current state only when parsed correctly, otherwise return error
+ if ret == 0 and zs.state ~= "ZS_STATE_ERROR" then
+ return zs.state == "ZS_STATE_DATA"
+ else
+ return false, zs:strerr()
+ end
+ end,
+ current_rr = function(zs)
+ assert(ffi.istype(zs, zs_scanner_t))
+ return {
+ owner = ffi.string(zs.r_owner, zs.r_owner_length),
+ ttl = tonumber(zs.r_ttl),
+ class = tonumber(zs.r_class),
+ type = tonumber(zs.r_type),
+ rdata = ffi.string(zs.r_data, zs.r_data_length),
+ comment = zs:current_comment(),
+ }
+ end,
+ strerr = function(zs)
+ assert(ffi.istype(zs, zs_scanner_t))
+ return ffi.string(libzscanner.zs_strerror(zs.error.code))
+ end,
+ current_comment = function(zs)
+ if zs.buffer_length > 0 then
+ return ffi.string(zs.buffer, zs.buffer_length - 1)
+ else
+ return nil
+ end
+ end
+ },
+})
+
+-- Module API
+local rrparser = {
+ new = zs_scanner_t,
+
+ -- Parse a file into a list of RRs
+ file = function (path)
+ local zs = zs_scanner_t()
+ local ok, err = zs:open(path)
+ if not ok then
+ return ok, err
+ end
+ local results = {}
+ while zs:parse() do
+ table.insert(results, zs:current_rr())
+ end
+ return results
+ end,
+
+ -- Parse a string into a list of RRs.
+ string = function (input)
+ local zs = zs_scanner_t()
+ local results = {}
+ local ok = zs:parse(input)
+ while ok do
+ table.insert(results, zs:current_rr())
+ ok = zs:parse()
+ end
+ return results
+ end,
+}
+return rrparser
diff --git a/daemon/main.c b/daemon/main.c
new file mode 100644
index 0000000..15c7c1c
--- /dev/null
+++ b/daemon/main.c
@@ -0,0 +1,840 @@
+/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <uv.h>
+#ifdef HAS_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+#include <libknot/error.h>
+
+#include <contrib/cleanup.h>
+#include <contrib/ucw/mempool.h>
+#include <contrib/ccan/asprintf/asprintf.h>
+#include "lib/defines.h"
+#include "lib/resolve.h"
+#include "lib/dnssec.h"
+#include "daemon/network.h"
+#include "daemon/worker.h"
+#include "daemon/engine.h"
+#include "daemon/bindings.h"
+#include "daemon/tls.h"
+#include "lib/dnssec/ta.h"
+
+/* We can fork early on Linux 3.9+ and do SO_REUSEPORT for better performance. */
+#if defined(UV_VERSION_HEX) && defined(SO_REUSEPORT) && defined(__linux__)
+ #define CAN_FORK_EARLY 1
+#endif
+
+/* @internal Array of ip address shorthand. */
+typedef array_t(char*) addr_array_t;
+
+struct args {
+ int forks;
+ addr_array_t addr_set;
+ addr_array_t tls_set;
+ fd_array_t fd_set;
+ fd_array_t tls_fd_set;
+ char *keyfile;
+ int keyfile_unmanaged;
+ const char *moduledir;
+ const char *config;
+ int control_fd;
+ const char *rundir;
+ bool interactive;
+ bool quiet;
+ bool tty_binary_output;
+};
+
+/* lua_pcall helper function */
+static inline char *lua_strerror(int lua_err) {
+ switch (lua_err) {
+ case LUA_ERRRUN: return "a runtime error";
+ case LUA_ERRMEM: return "memory allocation error.";
+ case LUA_ERRERR: return "error while running the error handler function.";
+ default: return "a unknown error";
+ }
+}
+
+/**
+ * TTY control: process input and free() the buffer.
+ *
+ * For parameters see http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb
+ *
+ * - This is just basic read-eval-print; libedit is supported through kresc;
+ * - stream->data contains program arguments (struct args);
+ */
+static void tty_process_input(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
+{
+ char *cmd = buf ? buf->base : NULL; /* To be free()d on return. */
+
+ /* Set output streams */
+ FILE *out = stdout;
+ uv_os_fd_t stream_fd = 0;
+ struct args *args = stream->data;
+ if (uv_fileno((uv_handle_t *)stream, &stream_fd)) {
+ uv_close((uv_handle_t *)stream, (uv_close_cb) free);
+ free(cmd);
+ return;
+ }
+ if (stream_fd != STDIN_FILENO) {
+ if (nread < 0) { /* Close if disconnected */
+ uv_close((uv_handle_t *)stream, (uv_close_cb) free);
+ }
+ if (nread <= 0) {
+ free(cmd);
+ return;
+ }
+ uv_os_fd_t dup_fd = dup(stream_fd);
+ if (dup_fd >= 0) {
+ out = fdopen(dup_fd, "w");
+ }
+ }
+
+ /* Execute */
+ if (stream && cmd && nread > 0) {
+ /* Ensure cmd is 0-terminated */
+ if (cmd[nread - 1] == '\n') {
+ cmd[nread - 1] = '\0';
+ } else {
+ if (nread >= buf->len) { /* only equality should be possible */
+ char *newbuf = realloc(cmd, nread + 1);
+ if (!newbuf)
+ goto finish;
+ cmd = newbuf;
+ }
+ cmd[nread] = '\0';
+ }
+
+ /* Pseudo-command for switching to "binary output"; */
+ if (strcmp(cmd, "__binary") == 0) {
+ args->tty_binary_output = true;
+ goto finish;
+ }
+
+ struct engine *engine = ((struct worker_ctx *)stream->loop->data)->engine;
+ lua_State *L = engine->L;
+ int ret = engine_cmd(L, cmd, false);
+ const char *message = "";
+ if (lua_gettop(L) > 0) {
+ message = lua_tostring(L, -1);
+ }
+
+ /* Simpler output in binary mode */
+ if (args->tty_binary_output) {
+ size_t len_s = strlen(message);
+ if (len_s > UINT32_MAX)
+ goto finish;
+ uint32_t len_n = htonl(len_s);
+ fwrite(&len_n, sizeof(len_n), 1, out);
+ fwrite(message, len_s, 1, out);
+ lua_settop(L, 0);
+ goto finish;
+ }
+
+ /* Log to remote socket if connected */
+ const char *delim = args->quiet ? "" : "> ";
+ if (stream_fd != STDIN_FILENO) {
+ fprintf(stdout, "%s\n", cmd); /* Duplicate command to logs */
+ if (message)
+ fprintf(out, "%s", message); /* Duplicate output to sender */
+ if (message || !args->quiet)
+ fprintf(out, "\n");
+ fprintf(out, "%s", delim);
+ }
+ /* Log to standard streams */
+ FILE *fp_out = ret ? stderr : stdout;
+ if (message)
+ fprintf(fp_out, "%s", message);
+ if (message || !args->quiet)
+ fprintf(fp_out, "\n");
+ fprintf(fp_out, "%s", delim);
+ lua_settop(L, 0);
+ }
+finish:
+ fflush(out);
+ free(cmd);
+ /* Close if redirected */
+ if (stream_fd != STDIN_FILENO) {
+ fclose(out);
+ }
+}
+
+static void tty_alloc(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) {
+ buf->len = suggested;
+ buf->base = malloc(suggested);
+}
+
+static void tty_accept(uv_stream_t *master, int status)
+{
+ uv_tcp_t *client = malloc(sizeof(*client));
+ struct args *args = master->data;
+ if (client) {
+ uv_tcp_init(master->loop, client);
+ if (uv_accept(master, (uv_stream_t *)client) != 0) {
+ free(client);
+ return;
+ }
+ client->data = args;
+ uv_read_start((uv_stream_t *)client, tty_alloc, tty_process_input);
+ /* Write command line */
+ if (!args->quiet) {
+ uv_buf_t buf = { "> ", 2 };
+ uv_try_write((uv_stream_t *)client, &buf, 1);
+ }
+ }
+}
+
+/* @internal AF_LOCAL reads may still be interrupted, loop it. */
+static bool ipc_readall(int fd, char *dst, size_t len)
+{
+ while (len > 0) {
+ int rb = read(fd, dst, len);
+ if (rb > 0) {
+ dst += rb;
+ len -= rb;
+ } else if (errno != EAGAIN && errno != EINTR) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void ipc_activity(uv_poll_t *handle, int status, int events)
+{
+ struct engine *engine = handle->data;
+ if (status != 0) {
+ kr_log_error("[system] ipc: %s\n", uv_strerror(status));
+ return;
+ }
+ /* Get file descriptor from handle */
+ uv_os_fd_t fd = 0;
+ (void) uv_fileno((uv_handle_t *)(handle), &fd);
+ /* Read expression from IPC pipe */
+ uint32_t len = 0;
+ auto_free char *rbuf = NULL;
+ if (!ipc_readall(fd, (char *)&len, sizeof(len))) {
+ goto failure;
+ }
+ if (len < UINT32_MAX) {
+ rbuf = malloc(len + 1);
+ } else {
+ errno = EINVAL;
+ }
+ if (!rbuf) {
+ goto failure;
+ }
+ if (!ipc_readall(fd, rbuf, len)) {
+ goto failure;
+ }
+ rbuf[len] = '\0';
+ /* Run expression */
+ const char *message = "";
+ int ret = engine_ipc(engine, rbuf);
+ if (ret > 0) {
+ message = lua_tostring(engine->L, -1);
+ }
+ /* Clear the Lua stack */
+ lua_settop(engine->L, 0);
+ /* Send response back */
+ len = strlen(message);
+ if (write(fd, &len, sizeof(len)) != sizeof(len) ||
+ write(fd, message, len) != len) {
+ goto failure;
+ }
+ return; /* success! */
+failure:
+ /* Note that if the piped command got read or written partially,
+ * we would get out of sync and only receive rubbish now.
+ * Therefore we prefer to stop IPC, but we try to continue with all else.
+ */
+ kr_log_error("[system] stopping ipc because of: %s\n", strerror(errno));
+ uv_poll_stop(handle);
+ uv_close((uv_handle_t *)handle, (uv_close_cb)free);
+}
+
+static bool ipc_watch(uv_loop_t *loop, struct engine *engine, int fd)
+{
+ uv_poll_t *poller = malloc(sizeof(*poller));
+ if (!poller) {
+ return false;
+ }
+ int ret = uv_poll_init(loop, poller, fd);
+ if (ret != 0) {
+ free(poller);
+ return false;
+ }
+ poller->data = engine;
+ ret = uv_poll_start(poller, UV_READABLE, ipc_activity);
+ if (ret != 0) {
+ free(poller);
+ return false;
+ }
+ /* libuv sets O_NONBLOCK whether we want it or not */
+ (void) fcntl(fd, F_SETFD, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
+ return true;
+}
+
+static void signal_handler(uv_signal_t *handle, int signum)
+{
+ uv_stop(uv_default_loop());
+ uv_signal_stop(handle);
+}
+
+/** SIGBUS -> attempt to remove the overflowing cache file and abort. */
+static void sigbus_handler(int sig, siginfo_t *siginfo, void *ptr)
+{
+ /* We can't safely assume that printf-like functions work, but write() is OK.
+ * See POSIX for the safe functions, e.g. 2017 version just above this link:
+ * http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_04
+ */
+ #define WRITE_ERR(err_charray) \
+ (void)write(STDERR_FILENO, err_charray, sizeof(err_charray))
+ /* Unfortunately, void-cast on the write isn't enough to avoid the warning. */
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wunused-result"
+ const char msg_typical[] =
+ "\nSIGBUS received; this is most likely due to filling up the filesystem where cache resides.\n",
+ msg_unknown[] = "\nSIGBUS received, cause unknown.\n",
+ msg_deleted[] = "Cache file deleted.\n",
+ msg_del_fail[] = "Cache file deletion failed.\n",
+ msg_final[] = "kresd can not recover reliably by itself, exiting.\n";
+ if (siginfo->si_code != BUS_ADRERR) {
+ WRITE_ERR(msg_unknown);
+ goto end;
+ }
+ WRITE_ERR(msg_typical);
+ if (!kr_cache_emergency_file_to_remove) goto end;
+ if (unlink(kr_cache_emergency_file_to_remove)) {
+ WRITE_ERR(msg_del_fail);
+ } else {
+ WRITE_ERR(msg_deleted);
+ }
+end:
+ WRITE_ERR(msg_final);
+ _exit(128 - sig); /*< regular return from OS-raised SIGBUS can't work anyway */
+ #undef WRITE_ERR
+ #pragma GCC diagnostic pop
+}
+
+/** Split away port from the address. */
+static const char *set_addr(char *addr, int *port)
+{
+ char *p = strchr(addr, '@');
+ if (!p) {
+ p = strchr(addr, '#');
+ }
+ if (p) {
+ *port = strtol(p + 1, NULL, 10);
+ *p = '\0';
+ }
+
+ return addr;
+}
+
+/*
+ * Server operation.
+ */
+
+static int fork_workers(fd_array_t *ipc_set, int forks)
+{
+ /* Fork subprocesses if requested */
+ while (--forks > 0) {
+ int sv[2] = {-1, -1};
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0) {
+ perror("[system] socketpair");
+ return kr_error(errno);
+ }
+ int pid = fork();
+ if (pid < 0) {
+ perror("[system] fork");
+ return kr_error(errno);
+ }
+
+ /* Forked process */
+ if (pid == 0) {
+ array_clear(*ipc_set);
+ array_push(*ipc_set, sv[0]);
+ close(sv[1]);
+ return forks;
+ /* Parent process */
+ } else {
+ array_push(*ipc_set, sv[1]);
+ /* Do not share parent-end with other forks. */
+ (void) fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+ close(sv[0]);
+ }
+ }
+ return 0;
+}
+
+static void help(int argc, char *argv[])
+{
+ printf("Usage: %s [parameters] [rundir]\n", argv[0]);
+ printf("\nParameters:\n"
+ " -a, --addr=[addr] Server address (default: localhost@53).\n"
+ " -t, --tls=[addr] Server address for TLS (default: off).\n"
+ " -S, --fd=[fd] Listen on given fd (handed out by supervisor).\n"
+ " -T, --tlsfd=[fd] Listen using TLS on given fd (handed out by supervisor).\n"
+ " -c, --config=[path] Config file path (relative to [rundir]) (default: config).\n"
+ " -k, --keyfile=[path] File with root domain trust anchors (DS or DNSKEY), automatically updated.\n"
+ " -K, --keyfile-ro=[path] File with read-only root domain trust anchors, for use with an external updater.\n"
+ " -m, --moduledir=[path] Override the default module path (" MODULEDIR ").\n"
+ " -f, --forks=N Start N forks sharing the configuration.\n"
+ " -q, --quiet No command prompt in interactive mode.\n"
+ " -v, --verbose Run in verbose mode."
+#ifdef NOVERBOSELOG
+ " (Recompile without -DNOVERBOSELOG to activate.)"
+#endif
+ "\n"
+ " -V, --version Print version of the server.\n"
+ " -h, --help Print help and usage.\n"
+ "Options:\n"
+ " [rundir] Path to the working directory (default: .)\n");
+}
+
+/** \return exit code for main() */
+static int run_worker(uv_loop_t *loop, struct engine *engine, fd_array_t *ipc_set, bool leader, struct args *args)
+{
+ /* Only some kinds of stdin work with uv_pipe_t.
+ * Otherwise we would abort() from libuv e.g. with </dev/null */
+ if (args->interactive) switch (uv_guess_handle(0)) {
+ case UV_TTY: /* standard terminal */
+ /* TODO: it has worked OK so far, but we'd better use uv_tty_*
+ * for this case instead of uv_pipe_*. */
+ case UV_NAMED_PIPE: /* echo 'quit()' | kresd ... */
+ break;
+ default:
+ kr_log_error(
+ "[system] error: standard input is not a terminal or pipe; "
+ "use '-f 1' if you want non-interactive mode. "
+ "Commands can be simply added to your configuration file or sent over the tty/$PID control socket.\n"
+ );
+ return 1;
+ }
+
+ /* Control sockets or TTY */
+ auto_free char *sock_file = NULL;
+ uv_pipe_t pipe;
+ uv_pipe_init(loop, &pipe, 0);
+ pipe.data = args;
+ if (args->interactive) {
+ if (!args->quiet)
+ printf("[system] interactive mode\n> ");
+ fflush(stdout);
+ uv_pipe_open(&pipe, 0);
+ uv_read_start((uv_stream_t*) &pipe, tty_alloc, tty_process_input);
+ } else {
+ int pipe_ret = -1;
+ if (args->control_fd != -1) {
+ pipe_ret = uv_pipe_open(&pipe, args->control_fd);
+ } else {
+ (void) mkdir("tty", S_IRWXU|S_IRWXG);
+ sock_file = afmt("tty/%ld", (long)getpid());
+ if (sock_file) {
+ pipe_ret = uv_pipe_bind(&pipe, sock_file);
+ }
+ }
+ if (!pipe_ret)
+ uv_listen((uv_stream_t *) &pipe, 16, tty_accept);
+ }
+ /* Watch IPC pipes (or just assign them if leading the pgroup). */
+ if (!leader) {
+ for (size_t i = 0; i < ipc_set->len; ++i) {
+ if (!ipc_watch(loop, engine, ipc_set->at[i])) {
+ kr_log_error("[system] failed to create poller: %s\n", strerror(errno));
+ close(ipc_set->at[i]);
+ }
+ }
+ }
+ memcpy(&engine->ipc_set, ipc_set, sizeof(*ipc_set));
+
+ /* Notify supervisor. */
+#ifdef HAS_SYSTEMD
+ sd_notify(0, "READY=1");
+#endif
+ /* Run event loop */
+ uv_run(loop, UV_RUN_DEFAULT);
+ if (sock_file) {
+ unlink(sock_file);
+ }
+ uv_close((uv_handle_t *)&pipe, NULL); /* Seems OK even on the stopped loop. */
+ return 0;
+}
+
+#ifdef HAS_SYSTEMD
+static void free_sd_socket_names(char **socket_names, int count)
+{
+ for (int i = 0; i < count; i++) {
+ free(socket_names[i]);
+ }
+ free(socket_names);
+}
+#endif
+
+static int set_keyfile(struct engine *engine, char *keyfile, bool unmanaged)
+{
+ assert(keyfile != NULL);
+ auto_free char *cmd = afmt("trust_anchors.config('%s',%s)",
+ keyfile, unmanaged ? "true" : "nil");
+ if (!cmd) {
+ kr_log_error("[system] not enough memory\n");
+ return kr_error(ENOMEM);
+ }
+ int lua_ret = engine_cmd(engine->L, cmd, false);
+ if (lua_ret != 0) {
+ if (lua_gettop(engine->L) > 0) {
+ kr_log_error("%s\n", lua_tostring(engine->L, -1));
+ } else {
+ kr_log_error("[ ta ] keyfile '%s': failed to load (%s)\n",
+ keyfile, lua_strerror(lua_ret));
+ }
+ return lua_ret;
+ }
+
+ lua_settop(engine->L, 0);
+ return kr_ok();
+}
+
+
+static void args_init(struct args *args)
+{
+ memset(args, 0, sizeof(struct args));
+ args->forks = 1;
+ array_init(args->addr_set);
+ array_init(args->tls_set);
+ array_init(args->fd_set);
+ array_init(args->tls_fd_set);
+ args->moduledir = MODULEDIR;
+ args->control_fd = -1;
+ args->interactive = true;
+ args->quiet = false;
+}
+
+static long strtol_10(const char *s)
+{
+ if (!s) abort();
+ /* ^^ This shouldn't ever happen. When getopt_long() returns an option
+ * character that has a mandatory parameter, optarg can't be NULL. */
+ return strtol(s, NULL, 10);
+}
+
+/** Process arguments into struct args.
+ * @return >=0 if main() should be exited immediately.
+ */
+static int parse_args(int argc, char **argv, struct args *args)
+{
+ /* Long options. */
+ int c = 0, li = 0;
+ struct option opts[] = {
+ {"addr", required_argument, 0, 'a'},
+ {"tls", required_argument, 0, 't'},
+ {"fd", required_argument, 0, 'S'},
+ {"tlsfd", required_argument, 0, 'T'},
+ {"config", required_argument, 0, 'c'},
+ {"keyfile", required_argument, 0, 'k'},
+ {"keyfile-ro", required_argument, 0, 'K'},
+ {"forks", required_argument, 0, 'f'},
+ {"moduledir", required_argument, 0, 'm'},
+ {"verbose", no_argument, 0, 'v'},
+ {"quiet", no_argument, 0, 'q'},
+ {"version", no_argument, 0, 'V'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+ while ((c = getopt_long(argc, argv, "a:t:S:T:c:f:m:K:k:vqVh", opts, &li)) != -1) {
+ switch (c)
+ {
+ case 'a':
+ array_push(args->addr_set, optarg);
+ break;
+ case 't':
+ array_push(args->tls_set, optarg);
+ break;
+ case 'S':
+ array_push(args->fd_set, strtol_10(optarg));
+ break;
+ case 'T':
+ array_push(args->tls_fd_set, strtol_10(optarg));
+ break;
+ case 'c':
+ args->config = optarg;
+ break;
+ case 'f':
+ args->interactive = false;
+ args->forks = strtol_10(optarg);
+ if (args->forks <= 0) {
+ kr_log_error("[system] error '-f' requires a positive"
+ " number, not '%s'\n", optarg);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'K':
+ args->keyfile_unmanaged = 1;
+ case 'k':
+ if (args->keyfile != NULL) {
+ kr_log_error("[system] error only one of '--keyfile' and '--keyfile-ro' allowed\n");
+ return EXIT_FAILURE;
+ }
+ args->keyfile = optarg;
+ break;
+ case 'm':
+ args->moduledir = optarg;
+ break;
+ case 'v':
+ kr_verbose_set(true);
+#ifdef NOVERBOSELOG
+ kr_log_info("--verbose flag has no effect due to compilation with -DNOVERBOSELOG.\n");
+#endif
+ break;
+ case 'q':
+ args->quiet = true;
+ break;
+ case 'V':
+ kr_log_info("%s, version %s\n", "Knot Resolver", PACKAGE_VERSION);
+ return EXIT_SUCCESS;
+ case 'h':
+ case '?':
+ help(argc, argv);
+ return EXIT_SUCCESS;
+ default:
+ help(argc, argv);
+ return EXIT_FAILURE;
+ }
+ }
+ if (optind < argc) {
+ args->rundir = argv[optind];
+ }
+ return -1;
+}
+
+static int bind_fds(struct network *net, fd_array_t *fd_set, bool tls) {
+ int ret = 0;
+ for (size_t i = 0; i < fd_set->len; ++i) {
+ ret = network_listen_fd(net, fd_set->at[i], tls);
+ if (ret != 0) {
+ kr_log_error("[system] %slisten on fd=%d %s\n",
+ tls ? "TLS " : "", fd_set->at[i], kr_strerror(ret));
+ break;
+ }
+ }
+ return ret;
+}
+
+static int bind_sockets(struct network *net, addr_array_t *addr_set, bool tls) {
+ uint32_t flags = tls ? NET_TCP|NET_TLS : NET_UDP|NET_TCP;
+ int ret = 0;
+ for (size_t i = 0; i < addr_set->len; ++i) {
+ int port = tls ? KR_DNS_TLS_PORT : KR_DNS_PORT;
+ const char *addr = set_addr(addr_set->at[i], &port);
+ ret = network_listen(net, addr, (uint16_t)port, flags);
+ if (ret != 0) {
+ kr_log_error("[system] bind to '%s@%d' %s%s\n",
+ addr, port, tls ? "(TLS) " : "", kr_strerror(ret));
+ break;
+ }
+ }
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 0;
+ struct args args;
+ args_init(&args);
+ if ((ret = parse_args(argc, argv, &args)) >= 0) {
+ return ret;
+ }
+
+#ifdef HAS_SYSTEMD
+ /* Accept passed sockets from systemd supervisor. */
+ char **socket_names = NULL;
+ int sd_nsocks = sd_listen_fds_with_names(0, &socket_names);
+ for (int i = 0; i < sd_nsocks; ++i) {
+ int fd = SD_LISTEN_FDS_START + i;
+ /* when run under systemd supervision, do not use interactive mode */
+ args.interactive = false;
+ if (args.forks != 1) {
+ kr_log_error("[system] when run under systemd-style supervision, "
+ "use single-process only (bad: --forks=%d).\n", args.forks);
+ free_sd_socket_names(socket_names, sd_nsocks);
+ return EXIT_FAILURE;
+ }
+ if (!strcasecmp("control",socket_names[i])) {
+ args.control_fd = fd;
+ } else if (!strcasecmp("tls",socket_names[i])) {
+ array_push(args.tls_fd_set, fd);
+ } else {
+ array_push(args.fd_set, fd);
+ }
+ }
+ free_sd_socket_names(socket_names, sd_nsocks);
+#endif
+
+ /* Switch to rundir. */
+ if (args.rundir != NULL) {
+ /* FIXME: access isn't a good way if we start as root and drop privileges later */
+ if (access(args.rundir, W_OK) != 0) {
+ kr_log_error("[system] rundir '%s': %s\n", args.rundir, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ ret = chdir(args.rundir);
+ if (ret != 0) {
+ kr_log_error("[system] rundir '%s': %s\n", args.rundir, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (args.config && strcmp(args.config, "-") != 0 && access(args.config, R_OK) != 0) {
+ kr_log_error("[system] config '%s': %s\n", args.config, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ if (!args.config && access("config", R_OK) == 0) {
+ args.config = "config";
+ }
+
+#ifndef CAN_FORK_EARLY
+ /* Forking is currently broken with libuv. We need libuv to bind to
+ * sockets etc. before forking, but at the same time can't touch it before
+ * forking otherwise it crashes, so it's a chicken and egg problem.
+ * Disabling until https://github.com/libuv/libuv/pull/846 is done. */
+ if (args.forks > 1 && args.fd_set.len == 0 && args.tls_fd_set.len == 0) {
+ kr_log_error("[system] forking >1 workers supported only on Linux 3.9+ or with supervisor\n");
+ return EXIT_FAILURE;
+ }
+#endif
+
+ /* Connect forks with local socket */
+ fd_array_t ipc_set;
+ array_init(ipc_set);
+ /* Fork subprocesses if requested */
+ int fork_id = fork_workers(&ipc_set, args.forks);
+ if (fork_id < 0) {
+ return EXIT_FAILURE;
+ }
+
+ kr_crypto_init();
+
+ /* Create a server engine. */
+ knot_mm_t pool = {
+ .ctx = mp_new (4096),
+ .alloc = (knot_mm_alloc_t) mp_alloc
+ };
+ struct engine engine;
+ ret = engine_init(&engine, &pool);
+ if (ret != 0) {
+ kr_log_error("[system] failed to initialize engine: %s\n", kr_strerror(ret));
+ return EXIT_FAILURE;
+ }
+ /* Create worker */
+ struct worker_ctx *worker = worker_create(&engine, &pool, fork_id, args.forks);
+ if (!worker) {
+ kr_log_error("[system] not enough memory\n");
+ return EXIT_FAILURE;
+ }
+
+ uv_loop_t *loop = uv_default_loop();
+ worker->loop = loop;
+ loop->data = worker;
+
+ /* Catch some signals. */
+ uv_signal_t sigint, sigterm;
+ if (true) ret = uv_signal_init(loop, &sigint);
+ if (!ret) ret = uv_signal_init(loop, &sigterm);
+ if (!ret) ret = uv_signal_start(&sigint, signal_handler, SIGINT);
+ if (!ret) ret = uv_signal_start(&sigterm, signal_handler, SIGTERM);
+ /* Block SIGPIPE; see https://github.com/libuv/libuv/issues/45 */
+ if (!ret && signal(SIGPIPE, SIG_IGN) == SIG_ERR) ret = errno;
+ if (!ret) {
+ /* Catching SIGBUS via uv_signal_* can't work; see:
+ * https://github.com/libuv/libuv/pull/1987 */
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = sigbus_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGBUS, &sa, NULL)) {
+ ret = errno;
+ }
+ }
+ if (ret) {
+ kr_log_error("[system] failed to set up signal handlers: %s\n",
+ strerror(abs(errno)));
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* Bind to passed fds and sockets*/
+ if (bind_fds(&engine.net, &args.fd_set, false) != 0 ||
+ bind_fds(&engine.net, &args.tls_fd_set, true) != 0 ||
+ bind_sockets(&engine.net, &args.addr_set, false) != 0 ||
+ bind_sockets(&engine.net, &args.tls_set, true) != 0
+ ) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* Start the scripting engine */
+ engine_set_moduledir(&engine, args.moduledir);
+
+ if (engine_load_sandbox(&engine) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+ if (args.config != NULL && strcmp(args.config, "-") != 0) {
+ if(engine_loadconf(&engine, args.config) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+ lua_settop(engine.L, 0);
+ }
+ if (args.keyfile != NULL && set_keyfile(&engine, args.keyfile, args.keyfile_unmanaged) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+ if (args.config == NULL || strcmp(args.config, "-") !=0) {
+ if(engine_load_defaults(&engine) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+ }
+ if (engine_start(&engine) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* Run the event loop */
+ ret = run_worker(loop, &engine, &ipc_set, fork_id == 0, &args);
+
+cleanup:/* Cleanup. */
+ engine_deinit(&engine);
+ worker_reclaim(worker);
+ if (loop != NULL) {
+ uv_loop_close(loop);
+ }
+ mp_delete(pool.ctx);
+ array_clear(args.addr_set);
+ array_clear(args.tls_set);
+ kr_crypto_cleanup();
+ return ret;
+}
diff --git a/daemon/network.c b/daemon/network.c
new file mode 100644
index 0000000..6a148bb
--- /dev/null
+++ b/daemon/network.c
@@ -0,0 +1,446 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <assert.h>
+#include "daemon/network.h"
+#include "daemon/worker.h"
+#include "daemon/io.h"
+#include "daemon/tls.h"
+
+/* libuv 1.7.0+ is able to support SO_REUSEPORT for loadbalancing */
+#if defined(UV_VERSION_HEX)
+#if (__linux__ && SO_REUSEPORT)
+ #define handle_init(type, loop, handle, family) do { \
+ uv_ ## type ## _init_ex((loop), (handle), (family)); \
+ uv_os_fd_t hi_fd = 0; \
+ if (uv_fileno((uv_handle_t *)(handle), &hi_fd) == 0) { \
+ int hi_on = 1; \
+ int hi_ret = setsockopt(hi_fd, SOL_SOCKET, SO_REUSEPORT, &hi_on, sizeof(hi_on)); \
+ if (hi_ret) { \
+ return hi_ret; \
+ } \
+ } \
+ } while (0)
+/* libuv 1.7.0+ is able to assign fd immediately */
+#else
+ #define handle_init(type, loop, handle, family) do { \
+ uv_ ## type ## _init_ex((loop), (handle), (family)); \
+ } while (0)
+#endif
+#else
+ #define handle_init(type, loop, handle, family) \
+ uv_ ## type ## _init((loop), (handle))
+#endif
+
+void network_init(struct network *net, uv_loop_t *loop, int tcp_backlog)
+{
+ if (net != NULL) {
+ net->loop = loop;
+ net->endpoints = map_make(NULL);
+ net->tls_client_params = map_make(NULL);
+ net->tls_session_ticket_ctx = /* unsync. random, by default */
+ tls_session_ticket_ctx_create(loop, NULL, 0);
+ net->tcp.in_idle_timeout = 10000;
+ net->tcp.tls_handshake_timeout = TLS_MAX_HANDSHAKE_TIME;
+ net->tcp_backlog = tcp_backlog;
+ }
+}
+
+static void close_handle(uv_handle_t *handle, bool force)
+{
+ if (force) { /* Force close if event loop isn't running. */
+ uv_os_fd_t fd = 0;
+ if (uv_fileno(handle, &fd) == 0) {
+ close(fd);
+ }
+ handle->loop = NULL;
+ io_free(handle);
+ } else { /* Asynchronous close */
+ uv_close(handle, io_free);
+ }
+}
+
+static int close_endpoint(struct endpoint *ep, bool force)
+{
+ if (ep->udp) {
+ close_handle((uv_handle_t *)ep->udp, force);
+ }
+ if (ep->tcp) {
+ close_handle((uv_handle_t *)ep->tcp, force);
+ }
+
+ free(ep);
+ return kr_ok();
+}
+
+/** Endpoint visitor (see @file map.h) */
+static int close_key(const char *key, void *val, void *ext)
+{
+ endpoint_array_t *ep_array = val;
+ for (size_t i = ep_array->len; i--;) {
+ close_endpoint(ep_array->at[i], true);
+ }
+ return 0;
+}
+
+static int free_key(const char *key, void *val, void *ext)
+{
+ endpoint_array_t *ep_array = val;
+ array_clear(*ep_array);
+ free(ep_array);
+ return kr_ok();
+}
+
+void network_deinit(struct network *net)
+{
+ if (net != NULL) {
+ map_walk(&net->endpoints, close_key, 0);
+ map_walk(&net->endpoints, free_key, 0);
+ map_clear(&net->endpoints);
+ tls_credentials_free(net->tls_credentials);
+ tls_client_params_free(&net->tls_client_params);
+ net->tls_credentials = NULL;
+ tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx);
+ net->tcp.in_idle_timeout = 0;
+ }
+}
+
+/** Fetch or create endpoint array and insert endpoint. */
+static int insert_endpoint(struct network *net, const char *addr, struct endpoint *ep)
+{
+ /* Fetch or insert address into map */
+ endpoint_array_t *ep_array = map_get(&net->endpoints, addr);
+ if (ep_array == NULL) {
+ ep_array = malloc(sizeof(*ep_array));
+ if (ep_array == NULL) {
+ return kr_error(ENOMEM);
+ }
+ if (map_set(&net->endpoints, addr, ep_array) != 0) {
+ free(ep_array);
+ return kr_error(ENOMEM);
+ }
+ array_init(*ep_array);
+ }
+
+ if (array_push(*ep_array, ep) < 0) {
+ return kr_error(ENOMEM);
+ }
+ return kr_ok();
+}
+
+/** Open endpoint protocols. */
+static int open_endpoint(struct network *net, struct endpoint *ep, struct sockaddr *sa, uint32_t flags)
+{
+ int ret = 0;
+ if (flags & NET_UDP) {
+ ep->udp = malloc(sizeof(*ep->udp));
+ if (!ep->udp) {
+ return kr_error(ENOMEM);
+ }
+ memset(ep->udp, 0, sizeof(*ep->udp));
+ handle_init(udp, net->loop, ep->udp, sa->sa_family); /* can return! */
+ ret = udp_bind(ep->udp, sa);
+ if (ret != 0) {
+ return ret;
+ }
+ ep->flags |= NET_UDP;
+ }
+ if (flags & NET_TCP) {
+ ep->tcp = malloc(sizeof(*ep->tcp));
+ if (!ep->tcp) {
+ return kr_error(ENOMEM);
+ }
+ memset(ep->tcp, 0, sizeof(*ep->tcp));
+ handle_init(tcp, net->loop, ep->tcp, sa->sa_family); /* can return! */
+ if (flags & NET_TLS) {
+ ret = tcp_bind_tls(ep->tcp, sa, net->tcp_backlog);
+ ep->flags |= NET_TLS;
+ } else {
+ ret = tcp_bind(ep->tcp, sa, net->tcp_backlog);
+ }
+ if (ret != 0) {
+ return ret;
+ }
+ ep->flags |= NET_TCP;
+ }
+ return ret;
+}
+
+/** Open fd as endpoint. */
+static int open_endpoint_fd(struct network *net, struct endpoint *ep, int fd, int sock_type, bool use_tls)
+{
+ int ret = kr_ok();
+ if (sock_type == SOCK_DGRAM) {
+ if (use_tls) {
+ /* we do not support TLS over UDP */
+ return kr_error(EBADF);
+ }
+ if (ep->udp) {
+ return kr_error(EEXIST);
+ }
+ ep->udp = malloc(sizeof(*ep->udp));
+ if (!ep->udp) {
+ return kr_error(ENOMEM);
+ }
+ uv_udp_init(net->loop, ep->udp);
+ ret = udp_bindfd(ep->udp, fd);
+ if (ret != 0) {
+ close_handle((uv_handle_t *)ep->udp, false);
+ return ret;
+ }
+ ep->flags |= NET_UDP;
+ return kr_ok();
+ } else if (sock_type == SOCK_STREAM) {
+ if (ep->tcp) {
+ return kr_error(EEXIST);
+ }
+ ep->tcp = malloc(sizeof(*ep->tcp));
+ if (!ep->tcp) {
+ return kr_error(ENOMEM);
+ }
+ uv_tcp_init(net->loop, ep->tcp);
+ if (use_tls) {
+ ret = tcp_bindfd_tls(ep->tcp, fd, net->tcp_backlog);
+ ep->flags |= NET_TLS;
+ } else {
+ ret = tcp_bindfd(ep->tcp, fd, net->tcp_backlog);
+ }
+ if (ret != 0) {
+ close_handle((uv_handle_t *)ep->tcp, false);
+ return ret;
+ }
+ ep->flags |= NET_TCP;
+ return kr_ok();
+ }
+ return kr_error(EINVAL);
+}
+
+/** @internal Fetch endpoint array and offset of the address/port query. */
+static endpoint_array_t *network_get(struct network *net, const char *addr, uint16_t port, size_t *index)
+{
+ endpoint_array_t *ep_array = map_get(&net->endpoints, addr);
+ if (ep_array) {
+ for (size_t i = ep_array->len; i--;) {
+ struct endpoint *ep = ep_array->at[i];
+ if (ep->port == port) {
+ *index = i;
+ return ep_array;
+ }
+ }
+ }
+ return NULL;
+}
+
+int network_listen_fd(struct network *net, int fd, bool use_tls)
+{
+ /* Extract local address and socket type. */
+ int sock_type = SOCK_DGRAM;
+ socklen_t len = sizeof(sock_type);
+ int ret = getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &len);
+ if (ret != 0) {
+ return kr_error(EBADF);
+ }
+ /* Extract local address for this socket. */
+ struct sockaddr_storage ss = { .ss_family = AF_UNSPEC };
+ socklen_t addr_len = sizeof(ss);
+ ret = getsockname(fd, (struct sockaddr *)&ss, &addr_len);
+ if (ret != 0) {
+ return kr_error(EBADF);
+ }
+ int port = 0;
+ char addr_str[INET6_ADDRSTRLEN]; /* https://tools.ietf.org/html/rfc4291 */
+ if (ss.ss_family == AF_INET) {
+ uv_ip4_name((const struct sockaddr_in*)&ss, addr_str, sizeof(addr_str));
+ port = ntohs(((struct sockaddr_in *)&ss)->sin_port);
+ } else if (ss.ss_family == AF_INET6) {
+ uv_ip6_name((const struct sockaddr_in6*)&ss, addr_str, sizeof(addr_str));
+ port = ntohs(((struct sockaddr_in6 *)&ss)->sin6_port);
+ } else {
+ return kr_error(EAFNOSUPPORT);
+ }
+
+ /* always create endpoint for supervisor supplied fd
+ * even if addr+port is not unique */
+ struct endpoint *ep = malloc(sizeof(*ep));
+ memset(ep, 0, sizeof(*ep));
+ ep->flags = NET_DOWN;
+ ep->port = port;
+ ret = insert_endpoint(net, addr_str, ep);
+ if (ret != 0) {
+ return ret;
+ }
+ /* Create a libuv struct for this socket. */
+ return open_endpoint_fd(net, ep, fd, sock_type, use_tls);
+}
+
+int network_listen(struct network *net, const char *addr, uint16_t port, uint32_t flags)
+{
+ if (net == NULL || addr == 0 || port == 0) {
+ return kr_error(EINVAL);
+ }
+
+ /* Already listening */
+ size_t index = 0;
+ if (network_get(net, addr, port, &index)) {
+ return kr_ok();
+ }
+
+ /* Parse address. */
+ int ret = 0;
+ struct sockaddr_storage sa;
+ if (strchr(addr, ':') != NULL) {
+ ret = uv_ip6_addr(addr, port, (struct sockaddr_in6 *)&sa);
+ } else {
+ ret = uv_ip4_addr(addr, port, (struct sockaddr_in *)&sa);
+ }
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Bind interfaces */
+ struct endpoint *ep = malloc(sizeof(*ep));
+ memset(ep, 0, sizeof(*ep));
+ ep->flags = NET_DOWN;
+ ep->port = port;
+ ret = open_endpoint(net, ep, (struct sockaddr *)&sa, flags);
+ if (ret == 0) {
+ ret = insert_endpoint(net, addr, ep);
+ }
+ if (ret != 0) {
+ close_endpoint(ep, false);
+ }
+
+ return ret;
+}
+
+int network_close(struct network *net, const char *addr, uint16_t port)
+{
+ size_t index = 0;
+ endpoint_array_t *ep_array = network_get(net, addr, port, &index);
+ if (!ep_array) {
+ return kr_error(ENOENT);
+ }
+
+ /* Close endpoint in array. */
+ close_endpoint(ep_array->at[index], false);
+ array_del(*ep_array, index);
+
+ /* Collapse key if it has no endpoint. */
+ if (ep_array->len == 0) {
+ free(ep_array);
+ map_del(&net->endpoints, addr);
+ }
+
+ return kr_ok();
+}
+
+void network_new_hostname(struct network *net, struct engine *engine)
+{
+ if (net->tls_credentials &&
+ net->tls_credentials->ephemeral_servicename) {
+ struct tls_credentials *newcreds;
+ newcreds = tls_get_ephemeral_credentials(engine);
+ if (newcreds) {
+ tls_credentials_release(net->tls_credentials);
+ net->tls_credentials = newcreds;
+ kr_log_info("[tls] Updated ephemeral X.509 cert with new hostname\n");
+ } else {
+ kr_log_error("[tls] Failed to update ephemeral X.509 cert with new hostname, using existing one\n");
+ }
+ }
+}
+
+static int set_bpf_cb(const char *key, void *val, void *ext)
+{
+#ifdef SO_ATTACH_BPF
+ endpoint_array_t *endpoints = (endpoint_array_t *)val;
+ assert(endpoints != NULL);
+ int *bpffd = (int *)ext;
+ assert(bpffd != NULL);
+
+ for (size_t i = 0; i < endpoints->len; i++) {
+ struct endpoint *endpoint = (struct endpoint *)endpoints->at[i];
+ uv_os_fd_t sockfd = -1;
+ if (endpoint->tcp != NULL) uv_fileno((const uv_handle_t *)endpoint->tcp, &sockfd);
+ if (endpoint->udp != NULL) uv_fileno((const uv_handle_t *)endpoint->udp, &sockfd);
+ assert(sockfd != -1);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_BPF, bpffd, sizeof(int)) != 0) {
+ return 1; /* return error (and stop iterating over net->endpoints) */
+ }
+ }
+#else
+ kr_log_error("[network] SO_ATTACH_BPF socket option doesn't supported\n");
+ (void)key; (void)val; (void)ext;
+ return 1;
+#endif
+ return 0; /* OK */
+}
+
+int network_set_bpf(struct network *net, int bpf_fd)
+{
+#ifdef SO_ATTACH_BPF
+ if (map_walk(&net->endpoints, set_bpf_cb, &bpf_fd) != 0) {
+ /* set_bpf_cb() has returned error. */
+ network_clear_bpf(net);
+ return 0;
+ }
+#else
+ kr_log_error("[network] SO_ATTACH_BPF socket option doesn't supported\n");
+ (void)net;
+ (void)bpf_fd;
+ return 0;
+#endif
+ return 1;
+}
+
+static int clear_bpf_cb(const char *key, void *val, void *ext)
+{
+#ifdef SO_DETACH_BPF
+ endpoint_array_t *endpoints = (endpoint_array_t *)val;
+ assert(endpoints != NULL);
+
+ for (size_t i = 0; i < endpoints->len; i++) {
+ struct endpoint *endpoint = (struct endpoint *)endpoints->at[i];
+ uv_os_fd_t sockfd = -1;
+ if (endpoint->tcp != NULL) uv_fileno((const uv_handle_t *)endpoint->tcp, &sockfd);
+ if (endpoint->udp != NULL) uv_fileno((const uv_handle_t *)endpoint->udp, &sockfd);
+ assert(sockfd != -1);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_DETACH_BPF, NULL, 0) != 0) {
+ kr_log_error("[network] failed to clear SO_DETACH_BPF socket option\n");
+ }
+ /* Proceed even if setsockopt() failed,
+ * as we want to process all opened sockets. */
+ }
+#else
+ kr_log_error("[network] SO_DETACH_BPF socket option doesn't supported\n");
+ (void)key; (void)val; (void)ext;
+ return 1;
+#endif
+ return 0;
+}
+
+void network_clear_bpf(struct network *net)
+{
+#ifdef SO_DETACH_BPF
+ map_walk(&net->endpoints, clear_bpf_cb, NULL);
+#else
+ kr_log_error("[network] SO_DETACH_BPF socket option doesn't supported\n");
+ (void)net;
+#endif
+}
diff --git a/daemon/network.h b/daemon/network.h
new file mode 100644
index 0000000..11e1feb
--- /dev/null
+++ b/daemon/network.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2015-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <uv.h>
+#include <stdbool.h>
+
+#include "lib/generic/array.h"
+#include "lib/generic/map.h"
+
+struct engine;
+
+enum endpoint_flag {
+ NET_DOWN = 0 << 0,
+ NET_UDP = 1 << 0,
+ NET_TCP = 1 << 1,
+ NET_TLS = 1 << 2,
+};
+
+struct endpoint {
+ uv_udp_t *udp;
+ uv_tcp_t *tcp;
+ uint16_t port;
+ uint16_t flags;
+};
+
+/** @cond internal Array of endpoints */
+typedef array_t(struct endpoint*) endpoint_array_t;
+/* @endcond */
+
+struct net_tcp_param {
+ uint64_t in_idle_timeout;
+ uint64_t tls_handshake_timeout;
+};
+
+struct tls_session_ticket_ctx;
+struct network {
+ uv_loop_t *loop;
+ map_t endpoints;
+ struct tls_credentials *tls_credentials;
+ map_t tls_client_params;
+ struct tls_session_ticket_ctx *tls_session_ticket_ctx;
+ struct net_tcp_param tcp;
+ int tcp_backlog;
+};
+
+void network_init(struct network *net, uv_loop_t *loop, int tcp_backlog);
+void network_deinit(struct network *net);
+int network_listen_fd(struct network *net, int fd, bool use_tls);
+int network_listen(struct network *net, const char *addr, uint16_t port, uint32_t flags);
+int network_close(struct network *net, const char *addr, uint16_t port);
+int network_set_tls_cert(struct network *net, const char *cert);
+int network_set_tls_key(struct network *net, const char *key);
+void network_new_hostname(struct network *net, struct engine *engine);
+int network_set_bpf(struct network *net, int bpf_fd);
+void network_clear_bpf(struct network *net);
diff --git a/daemon/session.c b/daemon/session.c
new file mode 100644
index 0000000..c870d8c
--- /dev/null
+++ b/daemon/session.c
@@ -0,0 +1,767 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include <libknot/packet/pkt.h>
+
+#include "lib/defines.h"
+#include "daemon/session.h"
+#include "daemon/engine.h"
+#include "daemon/tls.h"
+#include "daemon/worker.h"
+#include "daemon/io.h"
+#include "lib/generic/queue.h"
+
+#define TLS_CHUNK_SIZE (16 * 1024)
+
+/* Per-session (TCP or UDP) persistent structure,
+ * that exists between remote counterpart and a local socket.
+ */
+struct session {
+ struct session_flags sflags; /**< miscellaneous flags. */
+ union inaddr peer; /**< address of peer; is not set for client's UDP sessions. */
+ uv_handle_t *handle; /**< libuv handle for IO operations. */
+ uv_timer_t timeout; /**< libuv handle for timer. */
+
+ struct tls_ctx_t *tls_ctx; /**< server side tls-related data. */
+ struct tls_client_ctx_t *tls_client_ctx; /**< client side tls-related data. */
+
+ trie_t *tasks; /**< list of tasks assotiated with given session. */
+ queue_t(struct qr_task *) waiting; /**< list of tasks waiting for sending to upstream. */
+
+ uint8_t *wire_buf; /**< Buffer for DNS message. */
+ ssize_t wire_buf_size; /**< Buffer size. */
+ ssize_t wire_buf_start_idx; /**< Data start offset in wire_buf. */
+ ssize_t wire_buf_end_idx; /**< Data end offset in wire_buf. */
+ uint64_t last_activity; /**< Time of last IO activity (if any occurs).
+ * Otherwise session creation time. */
+};
+
+static void on_session_close(uv_handle_t *handle)
+{
+ struct session *session = handle->data;
+ assert(session->handle == handle); (void)session;
+ io_free(handle);
+}
+
+static void on_session_timer_close(uv_handle_t *timer)
+{
+ struct session *session = timer->data;
+ uv_handle_t *handle = session->handle;
+ assert(handle && handle->data == session);
+ assert (session->sflags.outgoing || handle->type == UV_TCP);
+ if (!uv_is_closing(handle)) {
+ uv_close(handle, on_session_close);
+ }
+}
+
+void session_free(struct session *session)
+{
+ if (session) {
+ assert(session_is_empty(session));
+ session_clear(session);
+ free(session);
+ }
+}
+
+void session_clear(struct session *session)
+{
+ assert(session_is_empty(session));
+ if (session->handle && session->handle->type == UV_TCP) {
+ free(session->wire_buf);
+ }
+ trie_clear(session->tasks);
+ trie_free(session->tasks);
+ queue_deinit(session->waiting);
+ tls_free(session->tls_ctx);
+ tls_client_ctx_free(session->tls_client_ctx);
+ memset(session, 0, sizeof(*session));
+}
+
+void session_close(struct session *session)
+{
+ assert(session_is_empty(session));
+ if (session->sflags.closing) {
+ return;
+ }
+
+ uv_handle_t *handle = session->handle;
+ io_stop_read(handle);
+ session->sflags.closing = true;
+
+ if (!uv_is_closing((uv_handle_t *)&session->timeout)) {
+ uv_timer_stop(&session->timeout);
+ if (session->tls_client_ctx) {
+ tls_close(&session->tls_client_ctx->c);
+ }
+ if (session->tls_ctx) {
+ tls_close(&session->tls_ctx->c);
+ }
+
+ session->timeout.data = session;
+ uv_close((uv_handle_t *)&session->timeout, on_session_timer_close);
+ }
+}
+
+int session_start_read(struct session *session)
+{
+ return io_start_read(session->handle);
+}
+
+int session_stop_read(struct session *session)
+{
+ return io_stop_read(session->handle);
+}
+
+int session_waitinglist_push(struct session *session, struct qr_task *task)
+{
+ queue_push(session->waiting, task);
+ worker_task_ref(task);
+ return kr_ok();
+}
+
+struct qr_task *session_waitinglist_get(const struct session *session)
+{
+ return (queue_len(session->waiting) > 0) ? (queue_head(session->waiting)) : NULL;
+}
+
+struct qr_task *session_waitinglist_pop(struct session *session, bool deref)
+{
+ struct qr_task *t = session_waitinglist_get(session);
+ queue_pop(session->waiting);
+ if (deref) {
+ worker_task_unref(t);
+ }
+ return t;
+}
+
+int session_tasklist_add(struct session *session, struct qr_task *task)
+{
+ trie_t *t = session->tasks;
+ uint16_t task_msg_id = 0;
+ const char *key = NULL;
+ size_t key_len = 0;
+ if (session->sflags.outgoing) {
+ knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
+ task_msg_id = knot_wire_get_id(pktbuf->wire);
+ key = (const char *)&task_msg_id;
+ key_len = sizeof(task_msg_id);
+ } else {
+ key = (const char *)&task;
+ key_len = sizeof(char *);
+ }
+ trie_val_t *v = trie_get_ins(t, key, key_len);
+ if (unlikely(!v)) {
+ assert(false);
+ return kr_error(ENOMEM);
+ }
+ if (*v == NULL) {
+ *v = task;
+ worker_task_ref(task);
+ } else if (*v != task) {
+ assert(false);
+ return kr_error(EINVAL);
+ }
+ return kr_ok();
+}
+
+int session_tasklist_del(struct session *session, struct qr_task *task)
+{
+ trie_t *t = session->tasks;
+ uint16_t task_msg_id = 0;
+ const char *key = NULL;
+ size_t key_len = 0;
+ trie_val_t val;
+ if (session->sflags.outgoing) {
+ knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
+ task_msg_id = knot_wire_get_id(pktbuf->wire);
+ key = (const char *)&task_msg_id;
+ key_len = sizeof(task_msg_id);
+ } else {
+ key = (const char *)&task;
+ key_len = sizeof(char *);
+ }
+ int ret = trie_del(t, key, key_len, &val);
+ if (ret == kr_ok()) {
+ assert(val == task);
+ worker_task_unref(val);
+ }
+ return ret;
+}
+
+struct qr_task *session_tasklist_get_first(struct session *session)
+{
+ trie_val_t *val = trie_get_first(session->tasks, NULL, NULL);
+ return val ? (struct qr_task *) *val : NULL;
+}
+
+struct qr_task *session_tasklist_del_first(struct session *session, bool deref)
+{
+ trie_val_t val = NULL;
+ int res = trie_del_first(session->tasks, NULL, NULL, &val);
+ if (res != kr_ok()) {
+ val = NULL;
+ } else if (deref) {
+ worker_task_unref(val);
+ }
+ return (struct qr_task *)val;
+}
+struct qr_task* session_tasklist_del_msgid(const struct session *session, uint16_t msg_id)
+{
+ trie_t *t = session->tasks;
+ assert(session->sflags.outgoing);
+ struct qr_task *ret = NULL;
+ const char *key = (const char *)&msg_id;
+ size_t key_len = sizeof(msg_id);
+ trie_val_t val;
+ int res = trie_del(t, key, key_len, &val);
+ if (res == kr_ok()) {
+ if (worker_task_numrefs(val) > 1) {
+ ret = val;
+ }
+ worker_task_unref(val);
+ }
+ return ret;
+}
+
+struct qr_task* session_tasklist_find_msgid(const struct session *session, uint16_t msg_id)
+{
+ trie_t *t = session->tasks;
+ assert(session->sflags.outgoing);
+ struct qr_task *ret = NULL;
+ trie_val_t *val = trie_get_try(t, (char *)&msg_id, sizeof(msg_id));
+ if (val) {
+ ret = *val;
+ }
+ return ret;
+}
+
+struct session_flags *session_flags(struct session *session)
+{
+ return &session->sflags;
+}
+
+struct sockaddr *session_get_peer(struct session *session)
+{
+ return &session->peer.ip;
+}
+
+struct tls_ctx_t *session_tls_get_server_ctx(const struct session *session)
+{
+ return session->tls_ctx;
+}
+
+void session_tls_set_server_ctx(struct session *session, struct tls_ctx_t *ctx)
+{
+ session->tls_ctx = ctx;
+}
+
+struct tls_client_ctx_t *session_tls_get_client_ctx(const struct session *session)
+{
+ return session->tls_client_ctx;
+}
+
+void session_tls_set_client_ctx(struct session *session, struct tls_client_ctx_t *ctx)
+{
+ session->tls_client_ctx = ctx;
+}
+
+struct tls_common_ctx *session_tls_get_common_ctx(const struct session *session)
+{
+ struct tls_common_ctx *tls_ctx = session->sflags.outgoing ? &session->tls_client_ctx->c :
+ &session->tls_ctx->c;
+ return tls_ctx;
+}
+
+uv_handle_t *session_get_handle(struct session *session)
+{
+ return session->handle;
+}
+
+struct session *session_get(uv_handle_t *h)
+{
+ return h->data;
+}
+
+struct session *session_new(uv_handle_t *handle, bool has_tls)
+{
+ if (!handle) {
+ return NULL;
+ }
+ struct session *session = calloc(1, sizeof(struct session));
+ if (!session) {
+ return NULL;
+ }
+
+ queue_init(session->waiting);
+ session->tasks = trie_create(NULL);
+ if (handle->type == UV_TCP) {
+ size_t wire_buffer_size = KNOT_WIRE_MAX_PKTSIZE;
+ if (has_tls) {
+ /* When decoding large packets,
+ * gnutls gives the application chunks of size 16 kb each. */
+ wire_buffer_size += TLS_CHUNK_SIZE;
+ session->sflags.has_tls = true;
+ }
+ uint8_t *wire_buf = malloc(wire_buffer_size);
+ if (!wire_buf) {
+ free(session);
+ return NULL;
+ }
+ session->wire_buf = wire_buf;
+ session->wire_buf_size = wire_buffer_size;
+ } else if (handle->type == UV_UDP) {
+ /* We use the singleton buffer from worker for all UDP (!)
+ * libuv documentation doesn't really guarantee this is OK,
+ * but the implementation for unix systems does not hold
+ * the buffer (both UDP and TCP) - always makes a NON-blocking
+ * syscall that fills the buffer and immediately calls
+ * the callback, whatever the result of the operation.
+ * We still need to keep in mind to only touch the buffer
+ * in this callback... */
+ assert(handle->loop->data);
+ struct worker_ctx *worker = handle->loop->data;
+ session->wire_buf = worker->wire_buf;
+ session->wire_buf_size = sizeof(worker->wire_buf);
+ }
+
+ uv_timer_init(handle->loop, &session->timeout);
+
+ session->handle = handle;
+ handle->data = session;
+ session->timeout.data = session;
+ session_touch(session);
+
+ return session;
+}
+
+size_t session_tasklist_get_len(const struct session *session)
+{
+ return trie_weight(session->tasks);
+}
+
+size_t session_waitinglist_get_len(const struct session *session)
+{
+ return queue_len(session->waiting);
+}
+
+bool session_tasklist_is_empty(const struct session *session)
+{
+ return session_tasklist_get_len(session) == 0;
+}
+
+bool session_waitinglist_is_empty(const struct session *session)
+{
+ return session_waitinglist_get_len(session) == 0;
+}
+
+bool session_is_empty(const struct session *session)
+{
+ return session_tasklist_is_empty(session) &&
+ session_waitinglist_is_empty(session);
+}
+
+bool session_has_tls(const struct session *session)
+{
+ return session->sflags.has_tls;
+}
+
+void session_set_has_tls(struct session *session, bool has_tls)
+{
+ session->sflags.has_tls = has_tls;
+}
+
+void session_waitinglist_retry(struct session *session, bool increase_timeout_cnt)
+{
+ while (!session_waitinglist_is_empty(session)) {
+ struct qr_task *task = session_waitinglist_pop(session, false);
+ if (increase_timeout_cnt) {
+ worker_task_timeout_inc(task);
+ }
+ worker_task_step(task, &session->peer.ip, NULL);
+ worker_task_unref(task);
+ }
+}
+
+void session_waitinglist_finalize(struct session *session, int status)
+{
+ while (!session_waitinglist_is_empty(session)) {
+ struct qr_task *t = session_waitinglist_pop(session, false);
+ worker_task_finalize(t, status);
+ worker_task_unref(t);
+ }
+}
+
+void session_tasklist_finalize(struct session *session, int status)
+{
+ while (session_tasklist_get_len(session) > 0) {
+ struct qr_task *t = session_tasklist_del_first(session, false);
+ assert(worker_task_numrefs(t) > 0);
+ worker_task_finalize(t, status);
+ worker_task_unref(t);
+ }
+}
+
+int session_tasklist_finalize_expired(struct session *session)
+{
+ int ret = 0;
+ queue_t(struct qr_task *) q;
+ uint64_t now = kr_now();
+ trie_t *t = session->tasks;
+ trie_it_t *it;
+ queue_init(q);
+ for (it = trie_it_begin(t); !trie_it_finished(it); trie_it_next(it)) {
+ trie_val_t *v = trie_it_val(it);
+ struct qr_task *task = (struct qr_task *)*v;
+ if ((now - worker_task_creation_time(task)) >= KR_RESOLVE_TIME_LIMIT) {
+ queue_push(q, task);
+ worker_task_ref(task);
+ }
+ }
+ trie_it_free(it);
+
+ struct qr_task *task = NULL;
+ uint16_t msg_id = 0;
+ char *key = (char *)&task;
+ int32_t keylen = sizeof(struct qr_task *);
+ if (session->sflags.outgoing) {
+ key = (char *)&msg_id;
+ keylen = sizeof(msg_id);
+ }
+ while (queue_len(q) > 0) {
+ task = queue_head(q);
+ if (session->sflags.outgoing) {
+ knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
+ msg_id = knot_wire_get_id(pktbuf->wire);
+ }
+ int res = trie_del(t, key, keylen, NULL);
+ if (!worker_task_finished(task)) {
+ /* task->pending_count must be zero,
+ * but there are can be followers,
+ * so run worker_task_subreq_finalize() to ensure retrying
+ * for all the followers. */
+ worker_task_subreq_finalize(task);
+ worker_task_finalize(task, KR_STATE_FAIL);
+ }
+ if (res == KNOT_EOK) {
+ worker_task_unref(task);
+ }
+ queue_pop(q);
+ worker_task_unref(task);
+ ++ret;
+ }
+
+ queue_deinit(q);
+ return ret;
+}
+
+int session_timer_start(struct session *session, uv_timer_cb cb,
+ uint64_t timeout, uint64_t repeat)
+{
+ uv_timer_t *timer = &session->timeout;
+ assert(timer->data == session);
+ int ret = uv_timer_start(timer, cb, timeout, repeat);
+ if (ret != 0) {
+ uv_timer_stop(timer);
+ return kr_error(ENOMEM);
+ }
+ return 0;
+}
+
+int session_timer_restart(struct session *session)
+{
+ return uv_timer_again(&session->timeout);
+}
+
+int session_timer_stop(struct session *session)
+{
+ return uv_timer_stop(&session->timeout);
+}
+
+ssize_t session_wirebuf_consume(struct session *session, const uint8_t *data, ssize_t len)
+{
+ if (data != &session->wire_buf[session->wire_buf_end_idx]) {
+ /* shouldn't happen */
+ return kr_error(EINVAL);
+ }
+
+ if (len < 0) {
+ /* shouldn't happen */
+ return kr_error(EINVAL);
+ }
+
+ if (session->wire_buf_end_idx + len > session->wire_buf_size) {
+ /* shouldn't happen */
+ return kr_error(EINVAL);
+ }
+
+ session->wire_buf_end_idx += len;
+ return len;
+}
+
+knot_pkt_t *session_produce_packet(struct session *session, knot_mm_t *mm)
+{
+ session->sflags.wirebuf_error = false;
+ if (session->wire_buf_end_idx == 0) {
+ return NULL;
+ }
+
+ if (session->wire_buf_start_idx == session->wire_buf_end_idx) {
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return NULL;
+ }
+
+ if (session->wire_buf_start_idx > session->wire_buf_end_idx) {
+ session->sflags.wirebuf_error = true;
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return NULL;
+ }
+
+ const uv_handle_t *handle = session->handle;
+ uint8_t *msg_start = &session->wire_buf[session->wire_buf_start_idx];
+ ssize_t wirebuf_msg_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx;
+ uint16_t msg_size = 0;
+
+ if (!handle) {
+ session->sflags.wirebuf_error = true;
+ return NULL;
+ } else if (handle->type == UV_TCP) {
+ if (wirebuf_msg_data_size < 2) {
+ return NULL;
+ }
+ msg_size = knot_wire_read_u16(msg_start);
+ if (msg_size >= session->wire_buf_size) {
+ session->sflags.wirebuf_error = true;
+ return NULL;
+ }
+ if (msg_size + 2 > wirebuf_msg_data_size) {
+ return NULL;
+ }
+ if (msg_size == 0) {
+ session->sflags.wirebuf_error = true;
+ return NULL;
+ }
+ msg_start += 2;
+ } else if (wirebuf_msg_data_size < UINT16_MAX) {
+ msg_size = wirebuf_msg_data_size;
+ } else {
+ session->sflags.wirebuf_error = true;
+ return NULL;
+ }
+
+
+ knot_pkt_t *pkt = knot_pkt_new(msg_start, msg_size, mm);
+ session->sflags.wirebuf_error = (pkt == NULL);
+ return pkt;
+}
+
+int session_discard_packet(struct session *session, const knot_pkt_t *pkt)
+{
+ uv_handle_t *handle = session->handle;
+ /* Pointer to data start in wire_buf */
+ uint8_t *wirebuf_data_start = &session->wire_buf[session->wire_buf_start_idx];
+ /* Number of data bytes in wire_buf */
+ size_t wirebuf_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx;
+ /* Pointer to message start in wire_buf */
+ uint8_t *wirebuf_msg_start = wirebuf_data_start;
+ /* Number of message bytes in wire_buf.
+ * For UDP it is the same number as wirebuf_data_size. */
+ size_t wirebuf_msg_size = wirebuf_data_size;
+ /* Wire data from parsed packet. */
+ uint8_t *pkt_msg_start = pkt->wire;
+ /* Number of bytes in packet wire buffer. */
+ size_t pkt_msg_size = pkt->size;
+ if (knot_pkt_has_tsig(pkt)) {
+ pkt_msg_size += pkt->tsig_wire.len;
+ }
+
+ session->sflags.wirebuf_error = true;
+
+ if (!handle) {
+ return kr_error(EINVAL);
+ } else if (handle->type == UV_TCP) {
+ /* wire_buf contains TCP DNS message. */
+ if (wirebuf_data_size < 2) {
+ /* TCP message length field isn't in buffer, must not happen. */
+ assert(0);
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return kr_error(EINVAL);
+ }
+ wirebuf_msg_size = knot_wire_read_u16(wirebuf_msg_start);
+ wirebuf_msg_start += 2;
+ if (wirebuf_msg_size + 2 > wirebuf_data_size) {
+ /* TCP message length field is greater then
+ * number of bytes in buffer, must not happen. */
+ assert(0);
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return kr_error(EINVAL);
+ }
+ }
+
+ if (wirebuf_msg_start != pkt_msg_start) {
+ /* packet wirebuf must be located at the beginning
+ * of the session wirebuf, must not happen. */
+ assert(0);
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return kr_error(EINVAL);
+ }
+
+ if (wirebuf_msg_size < pkt_msg_size) {
+ /* Message length field is lesser then packet size,
+ * must not happen. */
+ assert(0);
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return kr_error(EINVAL);
+ }
+
+ if (handle->type == UV_TCP) {
+ session->wire_buf_start_idx += wirebuf_msg_size + 2;
+ } else {
+ session->wire_buf_start_idx += pkt_msg_size;
+ }
+ session->sflags.wirebuf_error = false;
+
+ wirebuf_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx;
+ if (wirebuf_data_size == 0) {
+ session_wirebuf_discard(session);
+ } else if (wirebuf_data_size < KNOT_WIRE_HEADER_SIZE) {
+ session_wirebuf_compress(session);
+ }
+
+ return kr_ok();
+}
+
+void session_wirebuf_discard(struct session *session)
+{
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+}
+
+void session_wirebuf_compress(struct session *session)
+{
+ if (session->wire_buf_start_idx == 0) {
+ return;
+ }
+ uint8_t *wirebuf_data_start = &session->wire_buf[session->wire_buf_start_idx];
+ size_t wirebuf_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx;
+ if (session->wire_buf_start_idx < wirebuf_data_size) {
+ memmove(session->wire_buf, wirebuf_data_start, wirebuf_data_size);
+ } else {
+ memcpy(session->wire_buf, wirebuf_data_start, wirebuf_data_size);
+ }
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = wirebuf_data_size;
+}
+
+bool session_wirebuf_error(struct session *session)
+{
+ return session->sflags.wirebuf_error;
+}
+
+uint8_t *session_wirebuf_get_start(struct session *session)
+{
+ return session->wire_buf;
+}
+
+size_t session_wirebuf_get_size(struct session *session)
+{
+ return session->wire_buf_size;
+}
+
+uint8_t *session_wirebuf_get_free_start(struct session *session)
+{
+ return &session->wire_buf[session->wire_buf_end_idx];
+}
+
+size_t session_wirebuf_get_free_size(struct session *session)
+{
+ return session->wire_buf_size - session->wire_buf_end_idx;
+}
+
+void session_poison(struct session *session)
+{
+ kr_asan_poison(session, sizeof(*session));
+}
+
+void session_unpoison(struct session *session)
+{
+ kr_asan_unpoison(session, sizeof(*session));
+}
+
+int session_wirebuf_process(struct session *session)
+{
+ int ret = 0;
+ if (session->wire_buf_start_idx == session->wire_buf_end_idx) {
+ return ret;
+ }
+ struct worker_ctx *worker = session_get_handle(session)->loop->data;
+ size_t wirebuf_data_size = session->wire_buf_end_idx - session->wire_buf_start_idx;
+ uint32_t max_iterations = (wirebuf_data_size / (KNOT_WIRE_HEADER_SIZE + KNOT_WIRE_QUESTION_MIN_SIZE)) + 1;
+ knot_pkt_t *query = NULL;
+ while (((query = session_produce_packet(session, &worker->pkt_pool)) != NULL) &&
+ (ret < max_iterations)) {
+ assert (!session_wirebuf_error(session));
+ int res = worker_submit(session, query);
+ if (res != kr_error(EILSEQ)) {
+ /* Packet has been successfully parsed. */
+ ret += 1;
+ }
+ if (session_discard_packet(session, query) < 0) {
+ /* Packet data isn't stored in memory as expected.
+ something went wrong, normally should not happen. */
+ break;
+ }
+ }
+ if (session_wirebuf_error(session)) {
+ ret = -1;
+ }
+ return ret;
+}
+
+void session_kill_ioreq(struct session *s, struct qr_task *task)
+{
+ if (!s) {
+ return;
+ }
+ assert(s->sflags.outgoing && s->handle);
+ if (s->sflags.closing) {
+ return;
+ }
+ session_tasklist_del(s, task);
+ if (s->handle->type == UV_UDP) {
+ assert(session_tasklist_is_empty(s));
+ session_close(s);
+ return;
+ }
+}
+
+/** Update timestamp */
+void session_touch(struct session *s)
+{
+ s->last_activity = kr_now();
+}
+
+uint64_t session_last_activity(struct session *s)
+{
+ return s->last_activity;
+}
diff --git a/daemon/session.h b/daemon/session.h
new file mode 100644
index 0000000..56f7eb4
--- /dev/null
+++ b/daemon/session.h
@@ -0,0 +1,147 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <uv.h>
+
+struct qr_task;
+struct worker_ctx;
+struct session;
+
+struct session_flags {
+ bool outgoing : 1; /**< True: to upstream; false: from a client. */
+ bool throttled : 1; /**< True: data reading from peer is temporarily stopped. */
+ bool has_tls : 1; /**< True: given session uses TLS. */
+ bool connected : 1; /**< True: TCP connection is established. */
+ bool closing : 1; /**< True: session close sequence is in progress. */
+ bool wirebuf_error : 1; /**< True: last operation with wirebuf ended up with an error. */
+};
+
+/* Allocate new session for a libuv handle.
+ * If handle->tyoe is UV_UDP, tls parameter will be ignored. */
+struct session *session_new(uv_handle_t *handle, bool has_tls);
+/* Clear and free given session. */
+void session_free(struct session *session);
+/* Clear session. */
+void session_clear(struct session *session);
+/** Close session. */
+void session_close(struct session *session);
+/** Start reading from underlying libuv IO handle. */
+int session_start_read(struct session *session);
+/** Stop reading from underlying libuv IO handle. */
+int session_stop_read(struct session *session);
+
+/** List of tasks been waiting for IO. */
+/** Check if list is empty. */
+bool session_waitinglist_is_empty(const struct session *session);
+/** Add task to the end of the list. */
+int session_waitinglist_push(struct session *session, struct qr_task *task);
+/** Get the first element. */
+struct qr_task *session_waitinglist_get(const struct session *session);
+/** Get the first element and remove it from the list. */
+struct qr_task *session_waitinglist_pop(struct session *session, bool deref);
+/** Get the list length. */
+size_t session_waitinglist_get_len(const struct session *session);
+/** Retry resolution for each task in the list. */
+void session_waitinglist_retry(struct session *session, bool increase_timeout_cnt);
+/** Finalize all tasks in the list. */
+void session_waitinglist_finalize(struct session *session, int status);
+
+/** List of tasks associated with session. */
+/** Check if list is empty. */
+bool session_tasklist_is_empty(const struct session *session);
+/** Get the first element. */
+struct qr_task *session_tasklist_get_first(struct session *session);
+/** Get the first element and remove it from the list. */
+struct qr_task *session_tasklist_del_first(struct session *session, bool deref);
+/** Get the list length. */
+size_t session_tasklist_get_len(const struct session *session);
+/** Add task to the list. */
+int session_tasklist_add(struct session *session, struct qr_task *task);
+/** Remove task from the list. */
+int session_tasklist_del(struct session *session, struct qr_task *task);
+/** Remove task with given msg_id, session_flags(session)->outgoing must be true. */
+struct qr_task* session_tasklist_del_msgid(const struct session *session, uint16_t msg_id);
+/** Find task with given msg_id */
+struct qr_task* session_tasklist_find_msgid(const struct session *session, uint16_t msg_id);
+/** Finalize all tasks in the list. */
+void session_tasklist_finalize(struct session *session, int status);
+/** Finalize all expired tasks in the list. */
+int session_tasklist_finalize_expired(struct session *session);
+
+/** Both of task lists (associated & waiting). */
+/** Check if empty. */
+bool session_is_empty(const struct session *session);
+/** Get pointer to session flags */
+struct session_flags *session_flags(struct session *session);
+/** Get peer address. */
+struct sockaddr *session_get_peer(struct session *session);
+/** Get pointer to server-side tls-related data. */
+struct tls_ctx_t *session_tls_get_server_ctx(const struct session *session);
+/** Set pointer to server-side tls-related data. */
+void session_tls_set_server_ctx(struct session *session, struct tls_ctx_t *ctx);
+/** Get pointer to client-side tls-related data. */
+struct tls_client_ctx_t *session_tls_get_client_ctx(const struct session *session);
+/** Set pointer to client-side tls-related data. */
+void session_tls_set_client_ctx(struct session *session, struct tls_client_ctx_t *ctx);
+/** Get pointer to that part of tls-related data which has common structure for
+ * server and client. */
+struct tls_common_ctx *session_tls_get_common_ctx(const struct session *session);
+
+/** Get pointer to underlying libuv handle for IO operations. */
+uv_handle_t *session_get_handle(struct session *session);
+struct session *session_get(uv_handle_t *h);
+
+/** Start session timer. */
+int session_timer_start(struct session *session, uv_timer_cb cb,
+ uint64_t timeout, uint64_t repeat);
+/** Restart session timer without changing it parameters. */
+int session_timer_restart(struct session *session);
+/** Stop session timer. */
+int session_timer_stop(struct session *session);
+
+/** Get pointer to the beginning of session wirebuffer. */
+uint8_t *session_wirebuf_get_start(struct session *session);
+/** Get size of session wirebuffer. */
+size_t session_wirebuf_get_size(struct session *session);
+/** Get pointer to the beginning of free space in session wirebuffer. */
+uint8_t *session_wirebuf_get_free_start(struct session *session);
+/** Get amount of free space in session wirebuffer. */
+size_t session_wirebuf_get_free_size(struct session *session);
+/** Discard all data in session wirebuffer. */
+void session_wirebuf_discard(struct session *session);
+/** Move all data to the beginning of the buffer. */
+void session_wirebuf_compress(struct session *session);
+int session_wirebuf_process(struct session *session);
+ssize_t session_wirebuf_consume(struct session *session,
+ const uint8_t *data, ssize_t len);
+
+/** poison session structure with ASAN. */
+void session_poison(struct session *session);
+/** unpoison session structure with ASAN. */
+void session_unpoison(struct session *session);
+
+knot_pkt_t *session_produce_packet(struct session *session, knot_mm_t *mm);
+int session_discard_packet(struct session *session, const knot_pkt_t *pkt);
+
+void session_kill_ioreq(struct session *s, struct qr_task *task);
+/** Update timestamp */
+void session_touch(struct session *s);
+/** Returns either creation time or time of last IO activity if any occurs. */
+/* Used for TCP timeout calculation. */
+uint64_t session_last_activity(struct session *s);
diff --git a/daemon/tls.c b/daemon/tls.c
new file mode 100644
index 0000000..fd7fac5
--- /dev/null
+++ b/daemon/tls.c
@@ -0,0 +1,1282 @@
+/*
+ * Copyright (C) 2016 American Civil Liberties Union (ACLU)
+ * 2016-2018 CZ.NIC, z.s.p.o
+ *
+ * Initial Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ * Ondřej Surý <ondrej@sury.org>
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gnutls/abstract.h>
+#include <gnutls/crypto.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <uv.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "contrib/ucw/lib.h"
+#include "contrib/base64.h"
+#include "daemon/io.h"
+#include "daemon/tls.h"
+#include "daemon/worker.h"
+#include "daemon/session.h"
+
+#define EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE 60*60*24*7
+#define GNUTLS_PIN_MIN_VERSION 0x030400
+
+/** @internal Debugging facility. */
+#ifdef DEBUG
+#define DEBUG_MSG(...) kr_log_verbose("[tls] " __VA_ARGS__)
+#else
+#define DEBUG_MSG(...)
+#endif
+
+struct async_write_ctx {
+ uv_write_t write_req;
+ struct tls_common_ctx *t;
+ char buf[];
+};
+
+static char const server_logstring[] = "tls";
+static char const client_logstring[] = "tls_client";
+
+static int client_verify_certificate(gnutls_session_t tls_session);
+
+/**
+ * Set mandatory security settings from
+ * https://tools.ietf.org/html/draft-ietf-dprive-dtls-and-tls-profiles-11#section-9
+ * Performance optimizations are not implemented at the moment.
+ */
+static int kres_gnutls_set_priority(gnutls_session_t session) {
+ static const char * const priorities =
+ "NORMAL:" /* GnuTLS defaults */
+ "-VERS-TLS1.0:-VERS-TLS1.1:" /* TLS 1.2 and higher */
+ /* Some distros by default allow features that are considered
+ * too insecure nowadays, so let's disable them explicitly. */
+ "-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
+ const char *errpos = NULL;
+ int err = gnutls_priority_set_direct(session, priorities, &errpos);
+ if (err != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] setting priority '%s' failed at character %zd (...'%s') with %s (%d)\n",
+ priorities, errpos - priorities, errpos, gnutls_strerror_name(err), err);
+ }
+ return err;
+}
+
+static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len)
+{
+ struct tls_common_ctx *t = (struct tls_common_ctx *)h;
+ assert(t != NULL);
+
+ ssize_t avail = t->nread - t->consumed;
+ DEBUG_MSG("[%s] pull wanted: %zu available: %zu\n",
+ t->client_side ? "tls_client" : "tls", len, avail);
+ if (t->nread <= t->consumed) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ ssize_t transfer = MIN(avail, len);
+ memcpy(buf, t->buf + t->consumed, transfer);
+ t->consumed += transfer;
+ return transfer;
+}
+
+static void on_write_complete(uv_write_t *req, int status)
+{
+ assert(req->data != NULL);
+ struct async_write_ctx *async_ctx = (struct async_write_ctx *)req->data;
+ struct tls_common_ctx *t = async_ctx->t;
+ assert(t->write_queue_size);
+ t->write_queue_size -= 1;
+ free(req->data);
+}
+
+static bool stream_queue_is_empty(struct tls_common_ctx *t)
+{
+ return (t->write_queue_size == 0);
+}
+
+static ssize_t kres_gnutls_vec_push(gnutls_transport_ptr_t h, const giovec_t * iov, int iovcnt)
+{
+ struct tls_common_ctx *t = (struct tls_common_ctx *)h;
+
+ if (t == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (iovcnt == 0) {
+ return 0;
+ }
+
+ assert(t->session);
+ uv_stream_t *handle = (uv_stream_t *)session_get_handle(t->session);
+ assert(handle && handle->type == UV_TCP);
+
+ /*
+ * This is a little bit complicated. There are two different writes:
+ * 1. Immediate, these don't need to own the buffered data and return immediately
+ * 2. Asynchronous, these need to own the buffers until the write completes
+ * In order to avoid copying the buffer, an immediate write is tried first if possible.
+ * If it isn't possible to write the data without queueing, an asynchronous write
+ * is created (with copied buffered data).
+ */
+
+ size_t total_len = 0;
+ uv_buf_t uv_buf[iovcnt];
+ for (int i = 0; i < iovcnt; ++i) {
+ uv_buf[i].base = iov[i].iov_base;
+ uv_buf[i].len = iov[i].iov_len;
+ total_len += iov[i].iov_len;
+ }
+
+ /* Try to perform the immediate write first to avoid copy */
+ int ret = 0;
+ if (stream_queue_is_empty(t)) {
+ ret = uv_try_write(handle, uv_buf, iovcnt);
+ DEBUG_MSG("[%s] push %zu <%p> = %d\n",
+ t->client_side ? "tls_client" : "tls", total_len, h, ret);
+ /* from libuv documentation -
+ uv_try_write will return either:
+ > 0: number of bytes written (can be less than the supplied buffer size).
+ < 0: negative error code (UV_EAGAIN is returned if no data can be sent immediately).
+ */
+ if (ret == total_len) {
+ /* All the data were buffered by libuv.
+ * Return. */
+ return ret;
+ }
+
+ if (ret < 0 && ret != UV_EAGAIN) {
+ /* uv_try_write() has returned error code other then UV_EAGAIN.
+ * Return. */
+ ret = -1;
+ errno = EIO;
+ return ret;
+ }
+ /* Since we are here expression below is true
+ * (ret != total_len) && (ret >= 0 || ret == UV_EAGAIN)
+ * or the same
+ * (ret != total_len && ret >= 0) || (ret != total_len && ret == UV_EAGAIN)
+ * i.e. either occurs partial write or UV_EAGAIN.
+ * Proceed and copy data amount to owned memory and perform async write.
+ */
+ if (ret == UV_EAGAIN) {
+ /* No data were buffered, so we must buffer all the data. */
+ ret = 0;
+ }
+ }
+
+ /* Fallback when the queue is full, and it's not possible to do an immediate write */
+ char *p = malloc(sizeof(struct async_write_ctx) + total_len - ret);
+ if (p != NULL) {
+ struct async_write_ctx *async_ctx = (struct async_write_ctx *)p;
+ /* Save pointer to session tls context */
+ async_ctx->t = t;
+ char *buf = async_ctx->buf;
+ /* Skip data written in the partial write */
+ size_t to_skip = ret;
+ /* Copy the buffer into owned memory */
+ size_t off = 0;
+ for (int i = 0; i < iovcnt; ++i) {
+ if (to_skip > 0) {
+ /* Ignore current buffer if it's all skipped */
+ if (to_skip >= uv_buf[i].len) {
+ to_skip -= uv_buf[i].len;
+ continue;
+ }
+ /* Skip only part of the buffer */
+ uv_buf[i].base += to_skip;
+ uv_buf[i].len -= to_skip;
+ to_skip = 0;
+ }
+ memcpy(buf + off, uv_buf[i].base, uv_buf[i].len);
+ off += uv_buf[i].len;
+ }
+ uv_buf[0].base = buf;
+ uv_buf[0].len = off;
+
+ /* Create an asynchronous write request */
+ uv_write_t *write_req = &async_ctx->write_req;
+ memset(write_req, 0, sizeof(uv_write_t));
+ write_req->data = p;
+
+ /* Perform an asynchronous write with a callback */
+ if (uv_write(write_req, handle, uv_buf, 1, on_write_complete) == 0) {
+ ret = total_len;
+ t->write_queue_size += 1;
+ } else {
+ free(p);
+ errno = EIO;
+ ret = -1;
+ }
+ } else {
+ errno = ENOMEM;
+ ret = -1;
+ }
+
+ DEBUG_MSG("[%s] queued %zu <%p> = %d\n",
+ t->client_side ? "tls_client" : "tls", total_len, h, ret);
+
+ return ret;
+}
+
+/** Perform TLS handshake and handle error codes according to the documentation.
+ * See See https://gnutls.org/manual/html_node/TLS-handshake.html#TLS-handshake
+ * The function returns kr_ok() or success or non fatal error, kr_error(EAGAIN) on blocking, or kr_error(EIO) on fatal error.
+ */
+static int tls_handshake(struct tls_common_ctx *ctx, tls_handshake_cb handshake_cb) {
+ struct session *session = ctx->session;
+ const char *logstring = ctx->client_side ? client_logstring : server_logstring;
+
+ int err = gnutls_handshake(ctx->tls_session);
+ if (err == GNUTLS_E_SUCCESS) {
+ /* Handshake finished, return success */
+ ctx->handshake_state = TLS_HS_DONE;
+ struct sockaddr *peer = session_get_peer(session);
+ kr_log_verbose("[%s] TLS handshake with %s has completed\n",
+ logstring, kr_straddr(peer));
+ if (handshake_cb) {
+ if (handshake_cb(session, 0) != kr_ok()) {
+ return kr_error(EIO);
+ }
+ }
+ } else if (err == GNUTLS_E_AGAIN) {
+ return kr_error(EAGAIN);
+ } else if (gnutls_error_is_fatal(err)) {
+ /* Fatal errors, return error as it's not recoverable */
+ kr_log_verbose("[%s] gnutls_handshake failed: %s (%d)\n",
+ logstring,
+ gnutls_strerror_name(err), err);
+ if (handshake_cb) {
+ handshake_cb(session, -1);
+ }
+ return kr_error(EIO);
+ } else if (err == GNUTLS_E_WARNING_ALERT_RECEIVED) {
+ /* Handle warning when in verbose mode */
+ const char *alert_name = gnutls_alert_get_name(gnutls_alert_get(ctx->tls_session));
+ if (alert_name != NULL) {
+ struct sockaddr *peer = session_get_peer(session);
+ kr_log_verbose("[%s] TLS alert from %s received: %s\n",
+ logstring, kr_straddr(peer), alert_name);
+ }
+ }
+ return kr_ok();
+}
+
+
+struct tls_ctx_t *tls_new(struct worker_ctx *worker)
+{
+ assert(worker != NULL);
+ assert(worker->engine != NULL);
+
+ struct network *net = &worker->engine->net;
+ if (!net->tls_credentials) {
+ net->tls_credentials = tls_get_ephemeral_credentials(worker->engine);
+ if (!net->tls_credentials) {
+ kr_log_error("[tls] X.509 credentials are missing, and ephemeral credentials failed; no TLS\n");
+ return NULL;
+ }
+ kr_log_info("[tls] Using ephemeral TLS credentials:\n");
+ tls_credentials_log_pins(net->tls_credentials);
+ }
+
+ time_t now = time(NULL);
+ if (net->tls_credentials->valid_until != GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION) {
+ if (net->tls_credentials->ephemeral_servicename) {
+ /* ephemeral cert: refresh if due to expire within a week */
+ if (now >= net->tls_credentials->valid_until - EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE) {
+ struct tls_credentials *newcreds = tls_get_ephemeral_credentials(worker->engine);
+ if (newcreds) {
+ tls_credentials_release(net->tls_credentials);
+ net->tls_credentials = newcreds;
+ kr_log_info("[tls] Renewed expiring ephemeral X.509 cert\n");
+ } else {
+ kr_log_error("[tls] Failed to renew expiring ephemeral X.509 cert, using existing one\n");
+ }
+ }
+ } else {
+ /* non-ephemeral cert: warn once when certificate expires */
+ if (now >= net->tls_credentials->valid_until) {
+ kr_log_error("[tls] X.509 certificate has expired!\n");
+ net->tls_credentials->valid_until = GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION;
+ }
+ }
+ }
+
+ struct tls_ctx_t *tls = calloc(1, sizeof(struct tls_ctx_t));
+ if (tls == NULL) {
+ kr_log_error("[tls] failed to allocate TLS context\n");
+ return NULL;
+ }
+
+ int err = gnutls_init(&tls->c.tls_session, GNUTLS_SERVER | GNUTLS_NONBLOCK);
+ if (err != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] gnutls_init(): %s (%d)\n", gnutls_strerror_name(err), err);
+ tls_free(tls);
+ return NULL;
+ }
+ tls->credentials = tls_credentials_reserve(net->tls_credentials);
+ err = gnutls_credentials_set(tls->c.tls_session, GNUTLS_CRD_CERTIFICATE,
+ tls->credentials->credentials);
+ if (err != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] gnutls_credentials_set(): %s (%d)\n", gnutls_strerror_name(err), err);
+ tls_free(tls);
+ return NULL;
+ }
+ if (kres_gnutls_set_priority(tls->c.tls_session) != GNUTLS_E_SUCCESS) {
+ tls_free(tls);
+ return NULL;
+ }
+
+ tls->c.worker = worker;
+ tls->c.client_side = false;
+
+ gnutls_transport_set_pull_function(tls->c.tls_session, kres_gnutls_pull);
+ gnutls_transport_set_vec_push_function(tls->c.tls_session, kres_gnutls_vec_push);
+ gnutls_transport_set_ptr(tls->c.tls_session, tls);
+
+ if (net->tls_session_ticket_ctx) {
+ tls_session_ticket_enable(net->tls_session_ticket_ctx,
+ tls->c.tls_session);
+ }
+
+ return tls;
+}
+
+void tls_close(struct tls_common_ctx *ctx)
+{
+ if (ctx == NULL || ctx->tls_session == NULL) {
+ return;
+ }
+
+ assert(ctx->session);
+
+ if (ctx->handshake_state == TLS_HS_DONE) {
+ const struct sockaddr *peer = session_get_peer(ctx->session);
+ kr_log_verbose("[%s] closing tls connection to `%s`\n",
+ ctx->client_side ? "tls_client" : "tls",
+ kr_straddr(peer));
+ ctx->handshake_state = TLS_HS_CLOSING;
+ gnutls_bye(ctx->tls_session, GNUTLS_SHUT_RDWR);
+ }
+}
+
+void tls_free(struct tls_ctx_t *tls)
+{
+ if (!tls) {
+ return;
+ }
+
+ if (tls->c.tls_session) {
+ /* Don't terminate TLS connection, just tear it down */
+ gnutls_deinit(tls->c.tls_session);
+ tls->c.tls_session = NULL;
+ }
+
+ tls_credentials_release(tls->credentials);
+ free(tls);
+}
+
+int tls_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, uv_write_cb cb)
+{
+ if (!pkt || !handle || !handle->data) {
+ return kr_error(EINVAL);
+ }
+
+ struct session *s = handle->data;
+ struct tls_common_ctx *tls_ctx = session_tls_get_common_ctx(s);
+
+ assert (tls_ctx);
+ assert (session_flags(s)->outgoing == tls_ctx->client_side);
+
+ const uint16_t pkt_size = htons(pkt->size);
+ const char *logstring = tls_ctx->client_side ? client_logstring : server_logstring;
+ gnutls_session_t tls_session = tls_ctx->tls_session;
+
+ gnutls_record_cork(tls_session);
+ ssize_t count = 0;
+ if ((count = gnutls_record_send(tls_session, &pkt_size, sizeof(pkt_size)) < 0) ||
+ (count = gnutls_record_send(tls_session, pkt->wire, pkt->size) < 0)) {
+ kr_log_error("[%s] gnutls_record_send failed: %s (%zd)\n",
+ logstring, gnutls_strerror_name(count), count);
+ return kr_error(EIO);
+ }
+
+ const ssize_t submitted = sizeof(pkt_size) + pkt->size;
+
+ int ret = gnutls_record_uncork(tls_session, GNUTLS_RECORD_WAIT);
+ if (ret < 0) {
+ if (!gnutls_error_is_fatal(ret)) {
+ return kr_error(EAGAIN);
+ } else {
+ kr_log_error("[%s] gnutls_record_uncork failed: %s (%d)\n",
+ logstring, gnutls_strerror_name(ret), ret);
+ return kr_error(EIO);
+ }
+ }
+
+ if (ret != submitted) {
+ kr_log_error("[%s] gnutls_record_uncork didn't send all data (%d of %zd)\n",
+ logstring, ret, submitted);
+ return kr_error(EIO);
+ }
+
+ /* The data is now accepted in gnutls internal buffers, the message can be treated as sent */
+ req->handle = (uv_stream_t *)handle;
+ cb(req, 0);
+
+ return kr_ok();
+}
+
+ssize_t tls_process_input_data(struct session *s, const uint8_t *buf, ssize_t nread)
+{
+ struct tls_common_ctx *tls_p = session_tls_get_common_ctx(s);
+ if (!tls_p) {
+ return kr_error(ENOSYS);
+ }
+
+ assert(tls_p->session == s);
+ const bool ok = tls_p->recv_buf == buf && nread <= sizeof(tls_p->recv_buf);
+ if (!ok) {
+ assert(false);
+ /* don't risk overflowing the buffer if we have a mistake somewhere */
+ return kr_error(EINVAL);
+ }
+
+ const char *logstring = tls_p->client_side ? client_logstring : server_logstring;
+
+ tls_p->buf = buf;
+ tls_p->nread = nread >= 0 ? nread : 0;
+ tls_p->consumed = 0;
+
+ /* Ensure TLS handshake is performed before receiving data.
+ * See https://www.gnutls.org/manual/html_node/TLS-handshake.html */
+ while (tls_p->handshake_state <= TLS_HS_IN_PROGRESS) {
+ int err = tls_handshake(tls_p, tls_p->handshake_cb);
+ if (err == kr_error(EAGAIN)) {
+ return 0; /* Wait for more data */
+ } else if (err != kr_ok()) {
+ return err;
+ }
+ }
+
+ /* See https://gnutls.org/manual/html_node/Data-transfer-and-termination.html#Data-transfer-and-termination */
+ ssize_t submitted = 0;
+ uint8_t *wire_buf = session_wirebuf_get_free_start(s);
+ size_t wire_buf_size = session_wirebuf_get_free_size(s);
+ while (true) {
+ ssize_t count = gnutls_record_recv(tls_p->tls_session, wire_buf, wire_buf_size);
+ if (count == GNUTLS_E_AGAIN) {
+ break; /* No data available */
+ } else if (count == GNUTLS_E_INTERRUPTED) {
+ continue;
+ } else if (count == GNUTLS_E_REHANDSHAKE) {
+ /* See https://www.gnutls.org/manual/html_node/Re_002dauthentication.html */
+ struct sockaddr *peer = session_get_peer(s);
+ kr_log_verbose("[%s] TLS rehandshake with %s has started\n",
+ logstring, kr_straddr(peer));
+ tls_set_hs_state(tls_p, TLS_HS_IN_PROGRESS);
+ int err = kr_ok();
+ while (tls_p->handshake_state <= TLS_HS_IN_PROGRESS) {
+ err = tls_handshake(tls_p, tls_p->handshake_cb);
+ if (err == kr_error(EAGAIN)) {
+ break;
+ } else if (err != kr_ok()) {
+ return err;
+ }
+ }
+ if (err == kr_error(EAGAIN)) {
+ /* pull function is out of data */
+ break;
+ }
+ /* There are can be data available, check it. */
+ continue;
+ } else if (count < 0) {
+ kr_log_verbose("[%s] gnutls_record_recv failed: %s (%zd)\n",
+ logstring, gnutls_strerror_name(count), count);
+ return kr_error(EIO);
+ } else if (count == 0) {
+ break;
+ }
+ DEBUG_MSG("[%s] received %zd data\n", logstring, count);
+ wire_buf += count;
+ wire_buf_size -= count;
+ submitted += count;
+ if (wire_buf_size == 0 && tls_p->consumed != tls_p->nread) {
+ /* session buffer is full
+ * whereas not all the data were consumed */
+ return kr_error(ENOSPC);
+ }
+ }
+ /* Here all data must be consumed. */
+ if (tls_p->consumed != tls_p->nread) {
+ /* Something went wrong, better return error.
+ * This is most probably due to gnutls_record_recv() did not
+ * consume all available network data by calling kres_gnutls_pull().
+ * TODO assess the need for buffering of data amount.
+ */
+ return kr_error(ENOSPC);
+ }
+ return submitted;
+}
+
+#if GNUTLS_VERSION_NUMBER >= GNUTLS_PIN_MIN_VERSION
+
+/*
+ DNS-over-TLS Out of band key-pinned authentication profile uses the
+ same form of pins as HPKP:
+
+ e.g. pin-sha256="FHkyLhvI0n70E47cJlRTamTrnYVcsYdjUGbr79CfAVI="
+
+ DNS-over-TLS OOB key-pins: https://tools.ietf.org/html/rfc7858#appendix-A
+ HPKP pin reference: https://tools.ietf.org/html/rfc7469#appendix-A
+*/
+#define PINLEN (((32) * 8 + 4)/6) + 3 + 1
+
+/* out must be at least PINLEN octets long */
+static int get_oob_key_pin(gnutls_x509_crt_t crt, char *outchar, ssize_t outchar_len)
+{
+ int err;
+ gnutls_pubkey_t key;
+ gnutls_datum_t datum = { .size = 0 };
+
+ if ((err = gnutls_pubkey_init(&key)) != GNUTLS_E_SUCCESS) {
+ return err;
+ }
+
+ if ((err = gnutls_pubkey_import_x509(key, crt, 0)) != GNUTLS_E_SUCCESS) {
+ goto leave;
+ } else {
+ if ((err = gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &datum)) != GNUTLS_E_SUCCESS) {
+ goto leave;
+ } else {
+ uint8_t raw_pin[32];
+ if ((err = gnutls_hash_fast(GNUTLS_DIG_SHA256, datum.data, datum.size, raw_pin)) != GNUTLS_E_SUCCESS) {
+ goto leave;
+ } else {
+ base64_encode(raw_pin, sizeof(raw_pin), (uint8_t *)outchar, outchar_len);
+ }
+ }
+ }
+leave:
+ gnutls_free(datum.data);
+ gnutls_pubkey_deinit(key);
+ return err;
+}
+
+void tls_credentials_log_pins(struct tls_credentials *tls_credentials)
+{
+ for (int index = 0;; index++) {
+ int err;
+ gnutls_x509_crt_t *certs = NULL;
+ unsigned int cert_count = 0;
+
+ if ((err = gnutls_certificate_get_x509_crt(tls_credentials->credentials, index, &certs, &cert_count)) != GNUTLS_E_SUCCESS) {
+ if (err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+ kr_log_error("[tls] could not get X.509 certificates (%d) %s\n", err, gnutls_strerror_name(err));
+ }
+ return;
+ }
+
+ for (int i = 0; i < cert_count; i++) {
+ char pin[PINLEN] = { 0 };
+ if ((err = get_oob_key_pin(certs[i], pin, sizeof(pin))) != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] could not calculate RFC 7858 OOB key-pin from cert %d (%d) %s\n", i, err, gnutls_strerror_name(err));
+ } else {
+ kr_log_info("[tls] RFC 7858 OOB key-pin (%d): pin-sha256=\"%s\"\n", i, pin);
+ }
+ gnutls_x509_crt_deinit(certs[i]);
+ }
+ gnutls_free(certs);
+ }
+}
+#else
+void tls_credentials_log_pins(struct tls_credentials *tls_credentials)
+{
+ kr_log_error("[tls] could not calculate RFC 7858 OOB key-pin; GnuTLS 3.4.0+ required\n");
+}
+#endif
+
+static int str_replace(char **where_ptr, const char *with)
+{
+ char *copy = with ? strdup(with) : NULL;
+ if (with && !copy) {
+ return kr_error(ENOMEM);
+ }
+
+ free(*where_ptr);
+ *where_ptr = copy;
+ return kr_ok();
+}
+
+static time_t _get_end_entity_expiration(gnutls_certificate_credentials_t creds)
+{
+ gnutls_datum_t data;
+ gnutls_x509_crt_t cert = NULL;
+ int err;
+ time_t ret = GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION;
+
+ if ((err = gnutls_certificate_get_crt_raw(creds, 0, 0, &data)) != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] failed to get cert to check expiration: (%d) %s\n",
+ err, gnutls_strerror_name(err));
+ goto done;
+ }
+ if ((err = gnutls_x509_crt_init(&cert)) != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] failed to initialize cert: (%d) %s\n",
+ err, gnutls_strerror_name(err));
+ goto done;
+ }
+ if ((err = gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] failed to construct cert while checking expiration: (%d) %s\n",
+ err, gnutls_strerror_name(err));
+ goto done;
+ }
+
+ ret = gnutls_x509_crt_get_expiration_time (cert);
+ done:
+ /* do not free data; g_c_get_crt_raw() says to treat it as
+ * constant. */
+ gnutls_x509_crt_deinit(cert);
+ return ret;
+}
+
+int tls_certificate_set(struct network *net, const char *tls_cert, const char *tls_key)
+{
+ if (!net) {
+ return kr_error(EINVAL);
+ }
+
+ struct tls_credentials *tls_credentials = calloc(1, sizeof(*tls_credentials));
+ if (tls_credentials == NULL) {
+ return kr_error(ENOMEM);
+ }
+
+ int err = 0;
+ if ((err = gnutls_certificate_allocate_credentials(&tls_credentials->credentials)) != GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls] gnutls_certificate_allocate_credentials() failed: (%d) %s\n",
+ err, gnutls_strerror_name(err));
+ tls_credentials_free(tls_credentials);
+ return kr_error(ENOMEM);
+ }
+ if ((err = gnutls_certificate_set_x509_system_trust(tls_credentials->credentials)) < 0) {
+ if (err != GNUTLS_E_UNIMPLEMENTED_FEATURE) {
+ kr_log_error("[tls] warning: gnutls_certificate_set_x509_system_trust() failed: (%d) %s\n",
+ err, gnutls_strerror_name(err));
+ tls_credentials_free(tls_credentials);
+ return err;
+ }
+ }
+
+ if ((str_replace(&tls_credentials->tls_cert, tls_cert) != 0) ||
+ (str_replace(&tls_credentials->tls_key, tls_key) != 0)) {
+ tls_credentials_free(tls_credentials);
+ return kr_error(ENOMEM);
+ }
+
+ if ((err = gnutls_certificate_set_x509_key_file(tls_credentials->credentials,
+ tls_cert, tls_key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) {
+ tls_credentials_free(tls_credentials);
+ kr_log_error("[tls] gnutls_certificate_set_x509_key_file(%s,%s) failed: %d (%s)\n",
+ tls_cert, tls_key, err, gnutls_strerror_name(err));
+ return kr_error(EINVAL);
+ }
+ /* record the expiration date: */
+ tls_credentials->valid_until = _get_end_entity_expiration(tls_credentials->credentials);
+
+ /* Exchange the x509 credentials */
+ struct tls_credentials *old_credentials = net->tls_credentials;
+
+ /* Start using the new x509_credentials */
+ net->tls_credentials = tls_credentials;
+ tls_credentials_log_pins(net->tls_credentials);
+
+ if (old_credentials) {
+ err = tls_credentials_release(old_credentials);
+ if (err != kr_error(EBUSY)) {
+ return err;
+ }
+ }
+
+ return kr_ok();
+}
+
+struct tls_credentials *tls_credentials_reserve(struct tls_credentials *tls_credentials) {
+ if (!tls_credentials) {
+ return NULL;
+ }
+ tls_credentials->count++;
+ return tls_credentials;
+}
+
+int tls_credentials_release(struct tls_credentials *tls_credentials) {
+ if (!tls_credentials) {
+ return kr_error(EINVAL);
+ }
+ if (--tls_credentials->count < 0) {
+ tls_credentials_free(tls_credentials);
+ } else {
+ return kr_error(EBUSY);
+ }
+ return kr_ok();
+}
+
+void tls_credentials_free(struct tls_credentials *tls_credentials) {
+ if (!tls_credentials) {
+ return;
+ }
+
+ if (tls_credentials->credentials) {
+ gnutls_certificate_free_credentials(tls_credentials->credentials);
+ }
+ if (tls_credentials->tls_cert) {
+ free(tls_credentials->tls_cert);
+ }
+ if (tls_credentials->tls_key) {
+ free(tls_credentials->tls_key);
+ }
+ if (tls_credentials->ephemeral_servicename) {
+ free(tls_credentials->ephemeral_servicename);
+ }
+ free(tls_credentials);
+}
+
+static int client_paramlist_entry_free(struct tls_client_paramlist_entry *entry)
+{
+ DEBUG_MSG("freeing TLS parameters %p\n", (void *)entry);
+
+ while (entry->ca_files.len > 0) {
+ if (entry->ca_files.at[0] != NULL) {
+ free((void *)entry->ca_files.at[0]);
+ }
+ array_del(entry->ca_files, 0);
+ }
+
+ while (entry->hostnames.len > 0) {
+ if (entry->hostnames.at[0] != NULL) {
+ free((void *)entry->hostnames.at[0]);
+ }
+ array_del(entry->hostnames, 0);
+ }
+
+ while (entry->pins.len > 0) {
+ if (entry->pins.at[0] != NULL) {
+ free((void *)entry->pins.at[0]);
+ }
+ array_del(entry->pins, 0);
+ }
+
+ array_clear(entry->ca_files);
+ array_clear(entry->hostnames);
+ array_clear(entry->pins);
+
+ if (entry->credentials) {
+ gnutls_certificate_free_credentials(entry->credentials);
+ }
+
+ if (entry->session_data.data) {
+ gnutls_free(entry->session_data.data);
+ }
+
+ free(entry);
+
+ return 0;
+}
+
+static void client_paramlist_entry_ref(struct tls_client_paramlist_entry *entry)
+{
+ if (entry != NULL) {
+ entry->refs += 1;
+ }
+}
+
+static void client_paramlist_entry_unref(struct tls_client_paramlist_entry *entry)
+{
+ if (entry != NULL) {
+ assert(entry->refs > 0);
+ entry->refs -= 1;
+
+ /* Last reference frees the object */
+ if (entry->refs == 0) {
+ client_paramlist_entry_free(entry);
+ }
+ }
+}
+
+static int client_paramlist_entry_clear(const char *k, void *v, void *baton)
+{
+ struct tls_client_paramlist_entry *entry = (struct tls_client_paramlist_entry *)v;
+ return client_paramlist_entry_free(entry);
+}
+
+struct tls_client_paramlist_entry *tls_client_try_upgrade(map_t *tls_client_paramlist,
+ const struct sockaddr *addr)
+{
+ /* Opportunistic upgrade from port 53 -> 853 */
+ if (kr_inaddr_port(addr) != KR_DNS_PORT) {
+ return NULL;
+ }
+
+ static char key[INET6_ADDRSTRLEN + 6];
+ size_t keylen = sizeof(key);
+ if (kr_inaddr_str(addr, key, &keylen) != 0) {
+ return NULL;
+ }
+
+ /* Rewrite 053 -> 853 */
+ memcpy(key + keylen - 4, "853", 3);
+
+ return map_get(tls_client_paramlist, key);
+}
+
+int tls_client_params_clear(map_t *tls_client_paramlist, const char *addr, uint16_t port)
+{
+ if (!tls_client_paramlist || !addr) {
+ return kr_error(EINVAL);
+ }
+
+ /* Parameters are OK */
+
+ char key[INET6_ADDRSTRLEN + 6];
+ size_t keylen = sizeof(key);
+ if (kr_straddr_join(addr, port, key, &keylen) != kr_ok()) {
+ return kr_error(EINVAL);
+ }
+
+ struct tls_client_paramlist_entry *entry = map_get(tls_client_paramlist, key);
+ if (entry != NULL) {
+ client_paramlist_entry_clear(NULL, (void *)entry, NULL);
+ map_del(tls_client_paramlist, key);
+ }
+
+ return kr_ok();
+}
+
+int tls_client_params_set(map_t *tls_client_paramlist,
+ const char *addr, uint16_t port,
+ const char *param, tls_client_param_t param_type)
+{
+ if (!tls_client_paramlist || !addr) {
+ return kr_error(EINVAL);
+ }
+
+ /* TLS_CLIENT_PARAM_CA can be empty */
+ if (param_type == TLS_CLIENT_PARAM_HOSTNAME ||
+ param_type == TLS_CLIENT_PARAM_PIN) {
+ if (param == NULL || param[0] == 0) {
+ return kr_error(EINVAL);
+ }
+ }
+
+ /* Parameters are OK */
+
+ char key[INET6_ADDRSTRLEN + 6];
+ size_t keylen = sizeof(key);
+ if (kr_straddr_join(addr, port, key, &keylen) != kr_ok()) {
+ kr_log_error("[tls_client] warning: '%s' is not a valid ip address, ignoring\n", addr);
+ return kr_ok();
+ }
+
+ bool is_first_entry = false;
+ struct tls_client_paramlist_entry *entry = map_get(tls_client_paramlist, key);
+ if (entry == NULL) {
+ entry = calloc(1, sizeof(struct tls_client_paramlist_entry));
+ if (entry == NULL) {
+ return kr_error(ENOMEM);
+ }
+ is_first_entry = true;
+ int ret = gnutls_certificate_allocate_credentials(&entry->credentials);
+ if (ret != GNUTLS_E_SUCCESS) {
+ free(entry);
+ kr_log_error("[tls_client] error: gnutls_certificate_allocate_credentials() fails (%s)\n",
+ gnutls_strerror_name(ret));
+ return kr_error(ENOMEM);
+ }
+ gnutls_certificate_set_verify_function(entry->credentials, client_verify_certificate);
+ client_paramlist_entry_ref(entry);
+ }
+
+ int ret = kr_ok();
+
+ if (param_type == TLS_CLIENT_PARAM_HOSTNAME) {
+ const char *hostname = param;
+ bool already_exists = false;
+ for (size_t i = 0; i < entry->hostnames.len; ++i) {
+ if (strcmp(entry->hostnames.at[i], hostname) == 0) {
+ kr_log_error("[tls_client] error: hostname '%s' for address '%s' already was set, ignoring\n", hostname, key);
+ already_exists = true;
+ break;
+ }
+ }
+ if (!already_exists) {
+ const char *value = strdup(hostname);
+ if (!value) {
+ ret = kr_error(ENOMEM);
+ } else if (array_push(entry->hostnames, value) < 0) {
+ free ((void *)value);
+ ret = kr_error(ENOMEM);
+ }
+ }
+ } else if (param_type == TLS_CLIENT_PARAM_CA) {
+ /* Import ca files only when hostname is already set */
+ if (entry->hostnames.len == 0) {
+ return kr_error(ENOENT);
+ }
+ const char *ca_file = param;
+ bool already_exists = false;
+ for (size_t i = 0; i < entry->ca_files.len; ++i) {
+ const char *imported_ca = entry->ca_files.at[i];
+ if (imported_ca[0] == 0 && (ca_file == NULL || ca_file[0] == 0)) {
+ kr_log_error("[tls_client] error: system ca for address '%s' already was set, ignoring\n", key);
+ already_exists = true;
+ break;
+ } else if (strcmp(imported_ca, ca_file) == 0) {
+ kr_log_error("[tls_client] error: ca file '%s' for address '%s' already was set, ignoring\n", ca_file, key);
+ already_exists = true;
+ break;
+ }
+ }
+ if (!already_exists) {
+ const char *value = strdup(ca_file != NULL ? ca_file : "");
+ if (!value) {
+ ret = kr_error(ENOMEM);
+ } else if (array_push(entry->ca_files, value) < 0) {
+ free ((void *)value);
+ ret = kr_error(ENOMEM);
+ } else if (value[0] == 0) {
+ int res = gnutls_certificate_set_x509_system_trust (entry->credentials);
+ if (res <= 0) {
+ kr_log_error("[tls_client] failed to import certs from system store (%s)\n",
+ gnutls_strerror_name(res));
+ /* value will be freed at cleanup */
+ ret = kr_error(EINVAL);
+ } else {
+ kr_log_verbose("[tls_client] imported %d certs from system store\n", res);
+ }
+ } else {
+ int res = gnutls_certificate_set_x509_trust_file(entry->credentials, value,
+ GNUTLS_X509_FMT_PEM);
+ if (res <= 0) {
+ kr_log_error("[tls_client] failed to import certificate file '%s' (%s)\n",
+ value, gnutls_strerror_name(res));
+ /* value will be freed at cleanup */
+ ret = kr_error(EINVAL);
+ } else {
+ kr_log_verbose("[tls_client] imported %d certs from file '%s'\n",
+ res, value);
+
+ }
+ }
+ }
+ } else if (param_type == TLS_CLIENT_PARAM_PIN) {
+ const char *pin = param;
+ for (size_t i = 0; i < entry->pins.len; ++i) {
+ if (strcmp(entry->pins.at[i], pin) == 0) {
+ kr_log_error("[tls_client] warning: pin '%s' for address '%s' already was set, ignoring\n", pin, key);
+ return kr_ok();
+ }
+ }
+ const void *value = strdup(pin);
+ if (!value) {
+ ret = kr_error(ENOMEM);
+ } else if (array_push(entry->pins, value) < 0) {
+ free ((void *)value);
+ ret = kr_error(ENOMEM);
+ }
+ }
+
+ if ((ret == kr_ok()) && is_first_entry) {
+ bool fail = (map_set(tls_client_paramlist, key, entry) != 0);
+ if (fail) {
+ ret = kr_error(ENOMEM);
+ }
+ }
+
+ if ((ret != kr_ok()) && is_first_entry) {
+ client_paramlist_entry_unref(entry);
+ }
+
+ return ret;
+}
+
+int tls_client_params_free(map_t *tls_client_paramlist)
+{
+ if (!tls_client_paramlist) {
+ return kr_error(EINVAL);
+ }
+
+ map_walk(tls_client_paramlist, client_paramlist_entry_clear, NULL);
+ map_clear(tls_client_paramlist);
+
+ return kr_ok();
+}
+
+static int client_verify_certificate(gnutls_session_t tls_session)
+{
+ struct tls_client_ctx_t *ctx = gnutls_session_get_ptr(tls_session);
+ assert(ctx->params != NULL);
+
+ if (ctx->params->pins.len == 0 && ctx->params->ca_files.len == 0) {
+ return GNUTLS_E_SUCCESS;
+ }
+
+ gnutls_certificate_type_t cert_type = gnutls_certificate_type_get(tls_session);
+ if (cert_type != GNUTLS_CRT_X509) {
+ kr_log_error("[tls_client] invalid certificate type %i has been received\n",
+ cert_type);
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+ unsigned int cert_list_size = 0;
+ const gnutls_datum_t *cert_list =
+ gnutls_certificate_get_peers(tls_session, &cert_list_size);
+ if (cert_list == NULL || cert_list_size == 0) {
+ kr_log_error("[tls_client] empty certificate list\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+#if GNUTLS_VERSION_NUMBER >= GNUTLS_PIN_MIN_VERSION
+ if (ctx->params->pins.len == 0) {
+ DEBUG_MSG("[tls_client] skipping certificate PIN check\n");
+ goto skip_pins;
+ }
+
+ for (int i = 0; i < cert_list_size; i++) {
+ gnutls_x509_crt_t cert;
+ int ret = gnutls_x509_crt_init(&cert);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return ret;
+ }
+
+ ret = gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_x509_crt_deinit(cert);
+ return ret;
+ }
+
+ char cert_pin[PINLEN] = { 0 };
+ ret = get_oob_key_pin(cert, cert_pin, sizeof(cert_pin));
+
+ gnutls_x509_crt_deinit(cert);
+
+ if (ret != GNUTLS_E_SUCCESS) {
+ return ret;
+ }
+
+ DEBUG_MSG("[tls_client] received pin : %s\n", cert_pin);
+ for (size_t j = 0; j < ctx->params->pins.len; ++j) {
+ const char *pin = ctx->params->pins.at[j];
+ bool match = (strcmp(cert_pin, pin) == 0);
+ DEBUG_MSG("[tls_client] configured pin: %s matches? %s\n",
+ pin, match ? "yes" : "no");
+ if (match) {
+ return GNUTLS_E_SUCCESS;
+ }
+ }
+ }
+
+ /* pins were set, but no one was not matched */
+ kr_log_error("[tls_client] certificate PIN check failed\n");
+#else
+ if (ctx->params->pins.len != 0) {
+ kr_log_error("[tls_client] newer gnutls is required to use PIN check\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+#endif
+
+skip_pins:
+
+ if (ctx->params->ca_files.len == 0) {
+ DEBUG_MSG("[tls_client] empty CA files list\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ if (ctx->params->hostnames.len == 0) {
+ DEBUG_MSG("[tls_client] empty hostname list\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ int ret;
+ unsigned int status;
+ for (size_t i = 0; i < ctx->params->hostnames.len; ++i) {
+ ret = gnutls_certificate_verify_peers3(
+ ctx->c.tls_session,
+ ctx->params->hostnames.at[i],
+ &status);
+ if ((ret == GNUTLS_E_SUCCESS) && (status == 0)) {
+ return GNUTLS_E_SUCCESS;
+ }
+ }
+
+ if (ret == GNUTLS_E_SUCCESS) {
+ gnutls_datum_t msg;
+ ret = gnutls_certificate_verification_status_print(
+ status, gnutls_certificate_type_get(ctx->c.tls_session), &msg, 0);
+ if (ret == GNUTLS_E_SUCCESS) {
+ kr_log_error("[tls_client] failed to verify peer certificate: "
+ "%s\n", msg.data);
+ gnutls_free(msg.data);
+ } else {
+ kr_log_error("[tls_client] failed to verify peer certificate: "
+ "unable to print reason: %s (%s)\n",
+ gnutls_strerror(ret), gnutls_strerror_name(ret));
+ } /* gnutls_certificate_verification_status_print end */
+ } else {
+ kr_log_error("[tls_client] failed to verify peer certificate: "
+ "gnutls_certificate_verify_peers3 error: %s (%s)\n",
+ gnutls_strerror(ret), gnutls_strerror_name(ret));
+ } /* gnutls_certificate_verify_peers3 end */
+ return GNUTLS_E_CERTIFICATE_ERROR;
+}
+
+struct tls_client_ctx_t *tls_client_ctx_new(struct tls_client_paramlist_entry *entry,
+ struct worker_ctx *worker)
+{
+ struct tls_client_ctx_t *ctx = calloc(1, sizeof (struct tls_client_ctx_t));
+ if (!ctx) {
+ return NULL;
+ }
+ unsigned int flags = GNUTLS_CLIENT | GNUTLS_NONBLOCK
+#ifdef GNUTLS_ENABLE_FALSE_START
+ | GNUTLS_ENABLE_FALSE_START
+#endif
+ ;
+ int ret = gnutls_init(&ctx->c.tls_session, flags);
+ if (ret != GNUTLS_E_SUCCESS) {
+ tls_client_ctx_free(ctx);
+ return NULL;
+ }
+
+ ret = kres_gnutls_set_priority(ctx->c.tls_session);
+ if (ret != GNUTLS_E_SUCCESS) {
+ tls_client_ctx_free(ctx);
+ return NULL;
+ }
+
+ /* Must take a reference on parameters as the credentials are owned by it
+ * and must not be freed while the session is active. */
+ client_paramlist_entry_ref(entry);
+ ctx->params = entry;
+
+ ret = gnutls_credentials_set(ctx->c.tls_session, GNUTLS_CRD_CERTIFICATE,
+ entry->credentials);
+ if (ret != GNUTLS_E_SUCCESS) {
+ tls_client_ctx_free(ctx);
+ return NULL;
+ }
+
+ ctx->c.worker = worker;
+ ctx->c.client_side = true;
+
+ gnutls_transport_set_pull_function(ctx->c.tls_session, kres_gnutls_pull);
+ gnutls_transport_set_vec_push_function(ctx->c.tls_session, kres_gnutls_vec_push);
+ gnutls_transport_set_ptr(ctx->c.tls_session, ctx);
+ return ctx;
+}
+
+void tls_client_ctx_free(struct tls_client_ctx_t *ctx)
+{
+ if (ctx == NULL) {
+ return;
+ }
+
+ if (ctx->c.tls_session != NULL) {
+ gnutls_deinit(ctx->c.tls_session);
+ ctx->c.tls_session = NULL;
+ }
+
+ /* Must decrease the refcount for referenced parameters */
+ client_paramlist_entry_unref(ctx->params);
+
+ free (ctx);
+}
+
+int tls_pull_timeout_func(gnutls_transport_ptr_t h, unsigned int ms)
+{
+ struct tls_common_ctx *t = (struct tls_common_ctx *)h;
+ assert(t != NULL);
+ ssize_t avail = t->nread - t->consumed;
+ DEBUG_MSG("[%s] timeout check: available: %zu\n",
+ t->client_side ? "tls_client" : "tls", avail);
+ if (avail <= 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+ return avail;
+}
+
+int tls_client_connect_start(struct tls_client_ctx_t *client_ctx,
+ struct session *session,
+ tls_handshake_cb handshake_cb)
+{
+ if (session == NULL || client_ctx == NULL) {
+ return kr_error(EINVAL);
+ }
+
+ assert(session_flags(session)->outgoing && session_get_handle(session)->type == UV_TCP);
+
+ struct tls_common_ctx *ctx = &client_ctx->c;
+
+ gnutls_session_set_ptr(ctx->tls_session, client_ctx);
+ gnutls_handshake_set_timeout(ctx->tls_session, ctx->worker->engine->net.tcp.tls_handshake_timeout);
+ gnutls_transport_set_pull_timeout_function(ctx->tls_session, tls_pull_timeout_func);
+ session_tls_set_client_ctx(session, client_ctx);
+ ctx->handshake_cb = handshake_cb;
+ ctx->handshake_state = TLS_HS_IN_PROGRESS;
+ ctx->session = session;
+
+ struct tls_client_paramlist_entry *tls_params = client_ctx->params;
+ if (tls_params->session_data.data != NULL) {
+ gnutls_session_set_data(ctx->tls_session, tls_params->session_data.data,
+ tls_params->session_data.size);
+ }
+
+ /* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */
+ while (ctx->handshake_state <= TLS_HS_IN_PROGRESS) {
+ int ret = tls_handshake(ctx, handshake_cb);
+ if (ret != kr_ok()) {
+ return ret;
+ }
+ }
+ return kr_ok();
+}
+
+tls_hs_state_t tls_get_hs_state(const struct tls_common_ctx *ctx)
+{
+ return ctx->handshake_state;
+}
+
+int tls_set_hs_state(struct tls_common_ctx *ctx, tls_hs_state_t state)
+{
+ if (state >= TLS_HS_LAST) {
+ return kr_error(EINVAL);
+ }
+ ctx->handshake_state = state;
+ return kr_ok();
+}
+
+int tls_client_ctx_set_session(struct tls_client_ctx_t *ctx, struct session *session)
+{
+ if (!ctx) {
+ return kr_error(EINVAL);
+ }
+ ctx->c.session = session;
+ return kr_ok();
+}
+
+#undef DEBUG_MSG
diff --git a/daemon/tls.h b/daemon/tls.h
new file mode 100644
index 0000000..2eb776c
--- /dev/null
+++ b/daemon/tls.h
@@ -0,0 +1,224 @@
+/* Copyright (C) 2016 American Civil Liberties Union (ACLU)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <uv.h>
+#include <gnutls/gnutls.h>
+#include <libknot/packet/pkt.h>
+#include "lib/defines.h"
+#include "lib/generic/array.h"
+#include "lib/generic/map.h"
+
+#define MAX_TLS_PADDING KR_EDNS_PAYLOAD
+#define TLS_MAX_UNCORK_RETRIES 100
+
+/* rfc 5476, 7.3 - handshake Protocol overview
+ * https://tools.ietf.org/html/rfc5246#page-33
+ * Message flow for a full handshake (only mandatory messages)
+ * ClientHello -------->
+ ServerHello
+ <-------- ServerHelloDone
+ ClientKeyExchange
+ Finished -------->
+ <-------- Finished
+ *
+ * See also https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/
+ * So it takes 2 RTT.
+ * As we use session tickets, there are additional messages, add one RTT mode.
+ */
+ #define TLS_MAX_HANDSHAKE_TIME (KR_CONN_RTT_MAX * 3)
+
+/** Transport session (opaque). */
+struct session;
+
+struct tls_ctx_t;
+struct tls_client_ctx_t;
+struct tls_credentials {
+ int count;
+ char *tls_cert;
+ char *tls_key;
+ gnutls_certificate_credentials_t credentials;
+ time_t valid_until;
+ char *ephemeral_servicename;
+};
+
+struct tls_client_paramlist_entry {
+ array_t(const char *) ca_files;
+ array_t(const char *) hostnames;
+ array_t(const char *) pins;
+ gnutls_certificate_credentials_t credentials;
+ gnutls_datum_t session_data;
+ uint32_t refs;
+};
+
+struct worker_ctx;
+struct qr_task;
+
+typedef enum tls_client_hs_state {
+ TLS_HS_NOT_STARTED = 0,
+ TLS_HS_IN_PROGRESS,
+ TLS_HS_DONE,
+ TLS_HS_CLOSING,
+ TLS_HS_LAST
+} tls_hs_state_t;
+
+typedef int (*tls_handshake_cb) (struct session *session, int status);
+
+typedef enum tls_client_param {
+ TLS_CLIENT_PARAM_NONE = 0,
+ TLS_CLIENT_PARAM_PIN,
+ TLS_CLIENT_PARAM_HOSTNAME,
+ TLS_CLIENT_PARAM_CA,
+} tls_client_param_t;
+
+struct tls_common_ctx {
+ bool client_side;
+ gnutls_session_t tls_session;
+ tls_hs_state_t handshake_state;
+ struct session *session;
+ /* for reading from the network */
+ const uint8_t *buf;
+ ssize_t nread;
+ ssize_t consumed;
+ uint8_t recv_buf[16384];
+ tls_handshake_cb handshake_cb;
+ struct worker_ctx *worker;
+ size_t write_queue_size;
+};
+
+struct tls_ctx_t {
+ /*
+ * Since pointer to tls_ctx_t needs to be casted
+ * to tls_ctx_common in some functions,
+ * this field must be always at first position
+ */
+ struct tls_common_ctx c;
+ struct tls_credentials *credentials;
+};
+
+struct tls_client_ctx_t {
+ /*
+ * Since pointer to tls_client_ctx_t needs to be casted
+ * to tls_ctx_common in some functions,
+ * this field must be always at first position
+ */
+ struct tls_common_ctx c;
+ struct tls_client_paramlist_entry *params;
+};
+
+/*! Create an empty TLS context in query context */
+struct tls_ctx_t* tls_new(struct worker_ctx *worker);
+
+/*! Close a TLS context (call gnutls_bye()) */
+void tls_close(struct tls_common_ctx *ctx);
+
+/*! Release a TLS context */
+void tls_free(struct tls_ctx_t* tls);
+
+/*! Push new data to TLS context for sending */
+int tls_write(uv_write_t *req, uv_handle_t* handle, knot_pkt_t * pkt, uv_write_cb cb);
+
+/*! Unwrap incoming data from a TLS stream and pass them to TCP session.
+ * @return the number of newly-completed requests (>=0) or an error code
+ */
+ssize_t tls_process_input_data(struct session *s, const uint8_t *buf, ssize_t nread);
+
+/*! Set TLS certificate and key from files. */
+int tls_certificate_set(struct network *net, const char *tls_cert, const char *tls_key);
+
+/*! Borrow TLS credentials for context. */
+struct tls_credentials *tls_credentials_reserve(struct tls_credentials *tls_credentials);
+
+/*! Release TLS credentials for context (decrements refcount or frees). */
+int tls_credentials_release(struct tls_credentials *tls_credentials);
+
+/*! Free TLS credentials, must not be called if it holds positive refcount. */
+void tls_credentials_free(struct tls_credentials *tls_credentials);
+
+/*! Log DNS-over-TLS OOB key-pin form of current credentials:
+ * https://tools.ietf.org/html/rfc7858#appendix-A */
+void tls_credentials_log_pins(struct tls_credentials *tls_credentials);
+
+/*! Generate new ephemeral TLS credentials. */
+struct tls_credentials * tls_get_ephemeral_credentials(struct engine *engine);
+
+/*! Get TLS handshake state. */
+tls_hs_state_t tls_get_hs_state(const struct tls_common_ctx *ctx);
+
+/*! Set TLS handshake state. */
+int tls_set_hs_state(struct tls_common_ctx *ctx, tls_hs_state_t state);
+
+/*! Find TLS parameters for given address. Attempt opportunistic upgrade for port 53 to 853,
+ * if the address is configured with a working DoT on port 853.
+ */
+struct tls_client_paramlist_entry *tls_client_try_upgrade(map_t *tls_client_paramlist,
+ const struct sockaddr *addr);
+
+/*! Clear (remove) TLS parameters for given address. */
+int tls_client_params_clear(map_t *tls_client_paramlist, const char *addr, uint16_t port);
+
+/*! Set TLS authentication parameters for given address.
+ * Note: hostnames must be imported before ca files,
+ * otherwise ca files will not be imported at all.
+ */
+int tls_client_params_set(map_t *tls_client_paramlist,
+ const char *addr, uint16_t port,
+ const char *param, tls_client_param_t param_type);
+
+/*! Free TLS authentication parameters. */
+int tls_client_params_free(map_t *tls_client_paramlist);
+
+/*! Allocate new client TLS context */
+struct tls_client_ctx_t *tls_client_ctx_new(struct tls_client_paramlist_entry *entry,
+ struct worker_ctx *worker);
+
+/*! Free client TLS context */
+void tls_client_ctx_free(struct tls_client_ctx_t *ctx);
+
+int tls_client_connect_start(struct tls_client_ctx_t *client_ctx,
+ struct session *session,
+ tls_handshake_cb handshake_cb);
+
+int tls_client_ctx_set_session(struct tls_client_ctx_t *ctx, struct session *session);
+
+
+/* Session tickets, server side. Implementation in ./tls_session_ticket-srv.c */
+
+/*! Opaque struct used by tls_session_ticket_* functions. */
+struct tls_session_ticket_ctx;
+
+/*! Suggested maximum reasonable secret length. */
+#define TLS_SESSION_TICKET_SECRET_MAX_LEN 1024
+
+/*! Create a session ticket context and initialize it (secret gets copied inside).
+ *
+ * Passing zero-length secret implies using a random key, i.e. not synchronized
+ * between multiple instances.
+ *
+ * Beware that knowledge of the secret (if nonempty) breaks forward secrecy,
+ * so you should rotate the secret regularly and securely erase all past secrets.
+ * With TLS < 1.3 it's probably too risky to set nonempty secret.
+ */
+struct tls_session_ticket_ctx * tls_session_ticket_ctx_create(
+ uv_loop_t *loop, const char *secret, size_t secret_len);
+
+/*! Try to enable session tickets for a server session. */
+void tls_session_ticket_enable(struct tls_session_ticket_ctx *ctx, gnutls_session_t session);
+
+/*! Free all resources of the session ticket context. NULL is accepted as well. */
+void tls_session_ticket_ctx_destroy(struct tls_session_ticket_ctx *ctx);
+
diff --git a/daemon/tls_ephemeral_credentials.c b/daemon/tls_ephemeral_credentials.c
new file mode 100644
index 0000000..a305eb1
--- /dev/null
+++ b/daemon/tls_ephemeral_credentials.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2016 American Civil Liberties Union (ACLU)
+ * Copyright (C) 2016-2017 CZ.NIC, z.s.p.o.
+ *
+ * Initial Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <sys/file.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gnutls/crypto.h>
+
+#include "daemon/worker.h"
+#include "daemon/tls.h"
+
+#define EPHEMERAL_PRIVKEY_FILENAME "ephemeral_key.pem"
+#define INVALID_HOSTNAME "dns-over-tls.invalid"
+#define EPHEMERAL_CERT_EXPIRATION_SECONDS 60*60*24*90
+
+/* This is an attempt to grab an exclusive, advisory, non-blocking
+ * lock based on a filename. At the moment it's POSIX-only, but it
+ * should be abstract enough of an interface to make an implementation
+ * for non-posix systems if anyone cares. */
+typedef int lock_t;
+static bool _lock_is_invalid(lock_t lock)
+{
+ return lock == -1;
+}
+/* a blocking lock on a given filename */
+static lock_t _lock_filename(const char *fname)
+{
+ lock_t lockfd = open(fname, O_RDONLY|O_CREAT, 0400);
+ if (lockfd == -1)
+ return lockfd;
+ /* this should be a non-blocking lock */
+ if (flock(lockfd, LOCK_EX | LOCK_NB) != 0) {
+ close(lockfd);
+ return -1;
+ }
+ return lockfd; /* for cleanup later */
+}
+static void _lock_unlock(lock_t *lock, const char *fname)
+{
+ if (lock && !_lock_is_invalid(*lock)) {
+ flock(*lock, LOCK_UN);
+ close(*lock);
+ *lock = -1;
+ unlink(fname); /* ignore errors */
+ }
+}
+
+static gnutls_x509_privkey_t get_ephemeral_privkey ()
+{
+ gnutls_x509_privkey_t privkey = NULL;
+ int err;
+ gnutls_datum_t data = { .data = NULL, .size = 0 };
+ lock_t lock;
+ int datafd = -1;
+
+ /* Take a lock to ensure that two daemons started concurrently
+ * with a shared cache don't both create the same privkey: */
+ lock = _lock_filename(EPHEMERAL_PRIVKEY_FILENAME ".lock");
+ if (_lock_is_invalid(lock)) {
+ kr_log_error("[tls] unable to lock lockfile " EPHEMERAL_PRIVKEY_FILENAME ".lock\n");
+ goto done;
+ }
+
+ if ((err = gnutls_x509_privkey_init (&privkey)) < 0) {
+ kr_log_error("[tls] gnutls_x509_privkey_init() failed: %d (%s)\n",
+ err, gnutls_strerror_name(err));
+ goto done;
+ }
+
+ /* read from cache file (we assume that we've chdir'ed
+ * already, so we're just looking for the file in the
+ * cachedir. */
+ datafd = open(EPHEMERAL_PRIVKEY_FILENAME, O_RDONLY);
+ if (datafd != -1) {
+ struct stat stat;
+ ssize_t bytes_read;
+ if (fstat(datafd, &stat)) {
+ kr_log_error("[tls] unable to stat ephemeral private key " EPHEMERAL_PRIVKEY_FILENAME "\n");
+ goto bad_data;
+ }
+ data.data = gnutls_malloc(stat.st_size);
+ if (data.data == NULL) {
+ kr_log_error("[tls] unable to allocate memory for reading ephemeral private key\n");
+ goto bad_data;
+ }
+ data.size = stat.st_size;
+ bytes_read = read(datafd, data.data, stat.st_size);
+ if (bytes_read != stat.st_size) {
+ kr_log_error("[tls] unable to read ephemeral private key\n");
+ goto bad_data;
+ }
+ if ((err = gnutls_x509_privkey_import (privkey, &data, GNUTLS_X509_FMT_PEM)) < 0) {
+ kr_log_error("[tls] gnutls_x509_privkey_import() failed: %d (%s)\n",
+ err, gnutls_strerror_name(err));
+ /* goto bad_data; */
+ bad_data:
+ close(datafd);
+ datafd = -1;
+ }
+ if (data.data != NULL) {
+ gnutls_free(data.data);
+ data.data = NULL;
+ }
+ }
+ if (datafd == -1) {
+ /* if loading failed, then generate ... */
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+ if ((err = gnutls_x509_privkey_generate(privkey, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0)) < 0) {
+#else
+ if ((err = gnutls_x509_privkey_generate(privkey, GNUTLS_PK_RSA, gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_MEDIUM), 0)) < 0) {
+#endif
+ kr_log_error("[tls] gnutls_x509_privkey_init() failed: %d (%s)\n",
+ err, gnutls_strerror_name(err));
+ gnutls_x509_privkey_deinit(privkey);
+ goto done;
+ }
+ /* ... and save */
+ kr_log_info("[tls] Stashing ephemeral private key in " EPHEMERAL_PRIVKEY_FILENAME "\n");
+ if ((err = gnutls_x509_privkey_export2(privkey, GNUTLS_X509_FMT_PEM, &data)) < 0) {
+ kr_log_error("[tls] gnutls_x509_privkey_export2() failed: %d (%s), not storing\n",
+ err, gnutls_strerror_name(err));
+ } else {
+ datafd = open(EPHEMERAL_PRIVKEY_FILENAME, O_WRONLY|O_CREAT, 0600);
+ if (datafd == -1) {
+ kr_log_error("[tls] failed to open " EPHEMERAL_PRIVKEY_FILENAME " to store the ephemeral key\n");
+ } else {
+ ssize_t bytes_written;
+ bytes_written = write(datafd, data.data, data.size);
+ if (bytes_written != data.size)
+ kr_log_error("[tls] failed to write %d octets to "
+ EPHEMERAL_PRIVKEY_FILENAME
+ " (%zd written)\n",
+ data.size, bytes_written);
+ }
+ }
+ }
+ done:
+ _lock_unlock(&lock, EPHEMERAL_PRIVKEY_FILENAME ".lock");
+ if (datafd != -1) {
+ close(datafd);
+ }
+ if (data.data != NULL) {
+ gnutls_free(data.data);
+ }
+ return privkey;
+}
+
+static gnutls_x509_crt_t get_ephemeral_cert(gnutls_x509_privkey_t privkey, const char *servicename, time_t invalid_before, time_t valid_until)
+{
+ gnutls_x509_crt_t cert = NULL;
+ int err;
+ /* need a random buffer of bytes */
+ uint8_t serial[16];
+ gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial));
+ /* clear the left-most bit to avoid signedness confusion: */
+ serial[0] &= 0x8f;
+ size_t namelen = strlen(servicename);
+
+#define gtx(fn, ...) \
+ if ((err = fn ( __VA_ARGS__ )) != GNUTLS_E_SUCCESS) { \
+ kr_log_error("[tls] " #fn "() failed: %d (%s)\n", \
+ err, gnutls_strerror_name(err)); \
+ goto bad; }
+
+ gtx(gnutls_x509_crt_init, &cert);
+ gtx(gnutls_x509_crt_set_activation_time, cert, invalid_before);
+ gtx(gnutls_x509_crt_set_ca_status, cert, 0);
+ gtx(gnutls_x509_crt_set_expiration_time, cert, valid_until);
+ gtx(gnutls_x509_crt_set_key, cert, privkey);
+ gtx(gnutls_x509_crt_set_key_purpose_oid, cert, GNUTLS_KP_TLS_WWW_CLIENT, 0);
+ gtx(gnutls_x509_crt_set_key_purpose_oid, cert, GNUTLS_KP_TLS_WWW_SERVER, 0);
+ gtx(gnutls_x509_crt_set_key_usage, cert, GNUTLS_KEY_DIGITAL_SIGNATURE);
+ gtx(gnutls_x509_crt_set_serial, cert, serial, sizeof(serial));
+ gtx(gnutls_x509_crt_set_subject_alt_name, cert, GNUTLS_SAN_DNSNAME, servicename, namelen, GNUTLS_FSAN_SET);
+ gtx(gnutls_x509_crt_set_dn_by_oid,cert, GNUTLS_OID_X520_COMMON_NAME, 0, servicename, namelen);
+ gtx(gnutls_x509_crt_set_version, cert, 3);
+ gtx(gnutls_x509_crt_sign2,cert, cert, privkey, GNUTLS_DIG_SHA256, 0); /* self-sign, since it doesn't look like we can just stub-sign */
+#undef gtx
+
+ return cert;
+bad:
+ gnutls_x509_crt_deinit(cert);
+ return NULL;
+}
+
+struct tls_credentials * tls_get_ephemeral_credentials(struct engine *engine)
+{
+ struct tls_credentials *creds = NULL;
+ gnutls_x509_privkey_t privkey = NULL;
+ gnutls_x509_crt_t cert = NULL;
+ int err;
+ time_t now = time(NULL);
+
+ creds = calloc(1, sizeof(*creds));
+ if (!creds) {
+ kr_log_error("[tls] failed to allocate memory for ephemeral credentials\n");
+ return NULL;
+ }
+ if ((err = gnutls_certificate_allocate_credentials(&(creds->credentials))) < 0) {
+ kr_log_error("[tls] failed to allocate memory for ephemeral credentials\n");
+ goto failure;
+ }
+
+ creds->valid_until = now + EPHEMERAL_CERT_EXPIRATION_SECONDS;
+ creds->ephemeral_servicename = strdup(engine_get_hostname(engine));
+ if (creds->ephemeral_servicename == NULL) {
+ kr_log_error("[tls] could not get server's hostname, using '" INVALID_HOSTNAME "' instead\n");
+ if ((creds->ephemeral_servicename = strdup(INVALID_HOSTNAME)) == NULL) {
+ kr_log_error("[tls] failed to allocate memory for ephemeral credentials\n");
+ goto failure;
+ }
+ }
+ if ((privkey = get_ephemeral_privkey()) == NULL) {
+ goto failure;
+ }
+ if ((cert = get_ephemeral_cert(privkey, creds->ephemeral_servicename, now - 60*15, creds->valid_until)) == NULL) {
+ goto failure;
+ }
+ if ((err = gnutls_certificate_set_x509_key(creds->credentials, &cert, 1, privkey)) < 0) {
+ kr_log_error("[tls] failed to set up ephemeral credentials\n");
+ goto failure;
+ }
+ gnutls_x509_privkey_deinit(privkey);
+ gnutls_x509_crt_deinit(cert);
+ return creds;
+ failure:
+ gnutls_x509_privkey_deinit(privkey);
+ gnutls_x509_crt_deinit(cert);
+ tls_credentials_free(creds);
+ return NULL;
+}
diff --git a/daemon/tls_session_ticket-srv.c b/daemon/tls_session_ticket-srv.c
new file mode 100644
index 0000000..ff1471b
--- /dev/null
+++ b/daemon/tls_session_ticket-srv.c
@@ -0,0 +1,262 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <uv.h>
+
+#include "lib/utils.h"
+
+/* Style: "local/static" identifiers are usually named tst_* */
+
+/** The number of seconds between synchronized rotation of TLS session ticket key. */
+#define TST_KEY_LIFETIME 4096
+
+/** Value from gnutls:lib/ext/session_ticket.c
+ * Beware: changing this needs to change the hashing implementation. */
+#define SESSION_KEY_SIZE 64
+
+/** Compile-time support for setting the secret. */
+/* This is not secure with TLS <= 1.2 but TLS 1.3 and secure configuration
+ * is not available in GnuTLS yet. See https://gitlab.com/gnutls/gnutls/issues/477
+#ifndef TLS_SESSION_RESUMPTION_SYNC
+ #define TLS_SESSION_RESUMPTION_SYNC (GNUTLS_VERSION_NUMBER >= 0x030603)
+#endif
+*/
+
+#if GNUTLS_VERSION_NUMBER < 0x030400
+ /* It's of little use anyway. We may get the secret through lua,
+ * which creates a copy outside of our control. */
+ #define gnutls_memset memset
+#endif
+
+#ifdef GNUTLS_DIG_SHA3_512
+ #define TST_HASH GNUTLS_DIG_SHA3_512
+#else
+ #define TST_HASH abort()
+#endif
+
+/** Fields are internal to tst_key_* functions. */
+typedef struct tls_session_ticket_ctx {
+ uv_timer_t timer; /**< timer for rotation of the key */
+ unsigned char key[SESSION_KEY_SIZE]; /**< the key itself */
+ bool has_secret; /**< false -> key is random for each epoch */
+ uint16_t hash_len; /**< length of `hash_data` */
+ char hash_data[]; /**< data to hash to obtain `key`;
+ * it's `time_t epoch` and then the secret string */
+} tst_ctx_t;
+
+/** Check invariants, based on gnutls version. */
+static bool tst_key_invariants(void)
+{
+ static int result = 0; /*< cache for multiple invocations */
+ if (result) return result > 0;
+ bool ok = true;
+ #if TLS_SESSION_RESUMPTION_SYNC
+ /* SHA3-512 output size may never change, but let's check it anyway :-) */
+ ok = ok && gnutls_hash_get_len(TST_HASH) == SESSION_KEY_SIZE;
+ #endif
+ /* The ticket key size might change in a different gnutls version. */
+ gnutls_datum_t key = { 0, 0 };
+ ok = ok && gnutls_session_ticket_key_generate(&key) == 0
+ && key.size == SESSION_KEY_SIZE;
+ free(key.data);
+ result = ok ? 1 : -1;
+ return ok;
+}
+
+/** Create the internal structures and copy the secret. Beware: secret must be kept secure. */
+static tst_ctx_t * tst_key_create(const char *secret, size_t secret_len, uv_loop_t *loop)
+{
+ const size_t hash_len = sizeof(time_t) + secret_len;
+ if (secret_len &&
+ (!secret || hash_len > UINT16_MAX || hash_len < secret_len)) {
+ assert(!EINVAL);
+ return NULL;
+ /* reasonable secret_len is best enforced in config API */
+ }
+ if (!tst_key_invariants()) {
+ assert(!EFAULT);
+ return NULL;
+ }
+ #if !TLS_SESSION_RESUMPTION_SYNC
+ if (secret_len) {
+ kr_log_error("[tls] session ticket: secrets were not enabled at compile-time (your GnuTLS version is not supported)\n");
+ return NULL; /* ENOTSUP */
+ }
+ #endif
+
+ tst_ctx_t *ctx = malloc(sizeof(*ctx) + hash_len); /* can be slightly longer */
+ if (!ctx) return NULL;
+ ctx->has_secret = secret_len > 0;
+ ctx->hash_len = hash_len;
+ if (secret_len) {
+ memcpy(ctx->hash_data + sizeof(time_t), secret, secret_len);
+ }
+
+ if (uv_timer_init(loop, &ctx->timer) != 0) {
+ free(ctx);
+ return NULL;
+ }
+ ctx->timer.data = ctx;
+ return ctx;
+}
+
+/** Random variant of secret rotation: generate into key_tmp and copy. */
+static int tst_key_get_random(tst_ctx_t *ctx)
+{
+ gnutls_datum_t key_tmp = { NULL, 0 };
+ int err = gnutls_session_ticket_key_generate(&key_tmp);
+ if (err) return kr_error(err);
+ if (key_tmp.size != SESSION_KEY_SIZE) {
+ assert(!EFAULT);
+ return kr_error(EFAULT);
+ }
+ memcpy(ctx->key, key_tmp.data, SESSION_KEY_SIZE);
+ gnutls_memset(key_tmp.data, 0, SESSION_KEY_SIZE);
+ free(key_tmp.data);
+ return kr_ok();
+}
+
+/** Recompute the session ticket key, if epoch has changed or forced. */
+static int tst_key_update(tst_ctx_t *ctx, time_t epoch, bool force_update)
+{
+ if (!ctx || ctx->hash_len < sizeof(epoch)) {
+ assert(!EINVAL);
+ return kr_error(EINVAL);
+ }
+ /* documented limitation: time_t and endianess must match
+ * on instances sharing a secret */
+ if (!force_update && memcmp(ctx->hash_data, &epoch, sizeof(epoch)) == 0) {
+ return kr_ok(); /* we are up to date */
+ }
+ memcpy(ctx->hash_data, &epoch, sizeof(epoch));
+
+ if (!ctx->has_secret) {
+ return tst_key_get_random(ctx);
+ }
+ /* Otherwise, deterministic variant of secret rotation, if supported. */
+ #if !TLS_SESSION_RESUMPTION_SYNC
+ assert(false);
+ return kr_error(ENOTSUP);
+ #else
+ int err = gnutls_hash_fast(TST_HASH, ctx->hash_data,
+ ctx->hash_len, ctx->key);
+ return err == 0 ? kr_ok() : kr_error(err);
+ #endif
+}
+
+/** Free all resources of the key (securely). */
+static void tst_key_destroy(uv_handle_t *timer)
+{
+ assert(timer);
+ tst_ctx_t *ctx = timer->data;
+ assert(ctx);
+ gnutls_memset(ctx, 0, offsetof(tst_ctx_t, hash_data) + ctx->hash_len);
+ free(ctx);
+}
+
+static void tst_key_check(uv_timer_t *timer, bool force_update);
+static void tst_timer_callback(uv_timer_t *timer)
+{
+ tst_key_check(timer, false);
+}
+
+/** Update the ST key if needed and reschedule itself via the timer. */
+static void tst_key_check(uv_timer_t *timer, bool force_update)
+{
+ tst_ctx_t *stst = (tst_ctx_t *)timer->data;
+ /* Compute the current epoch. */
+ struct timeval now;
+ if (gettimeofday(&now, NULL)) {
+ kr_log_error("[tls] session ticket: gettimeofday failed, %s\n",
+ strerror(errno));
+ return;
+ }
+ uv_update_time(timer->loop); /* to have sync. between real and mono time */
+ const time_t epoch = now.tv_sec / TST_KEY_LIFETIME;
+ /* Update the key; new sessions will fetch it from the location.
+ * Old ones hopefully can't get broken by that; documentation
+ * for gnutls_session_ticket_enable_server() doesn't say. */
+ int err = tst_key_update(stst, epoch, force_update);
+ if (err) {
+ assert(err != kr_error(EINVAL));
+ kr_log_error("[tls] session ticket: failed rotation, err = %d\n", err);
+ }
+ /* Reschedule. */
+ const time_t tv_sec_next = (epoch + 1) * TST_KEY_LIFETIME;
+ const uint64_t ms_until_second = 1000 - (now.tv_usec + 501) / 1000;
+ const uint64_t remain_ms = (tv_sec_next - now.tv_sec - 1) * (uint64_t)1000
+ + ms_until_second + 1;
+ /* ^ +1 because we don't want to wake up half a millisecond before the epoch! */
+ assert(remain_ms < (TST_KEY_LIFETIME + 1 /*rounding tolerance*/) * 1000);
+ kr_log_verbose("[tls] session ticket: epoch %"PRIu64
+ ", scheduling rotation check in %"PRIu64" ms\n",
+ (uint64_t)epoch, remain_ms);
+ err = uv_timer_start(timer, &tst_timer_callback, remain_ms, 0);
+ if (err) {
+ assert(false);
+ kr_log_error("[tls] session ticket: failed to schedule, err = %d\n", err);
+ }
+}
+
+/* Implementation for prototypes from ./tls.h */
+
+void tls_session_ticket_enable(struct tls_session_ticket_ctx *ctx, gnutls_session_t session)
+{
+ assert(ctx && session);
+ const gnutls_datum_t gd = {
+ .size = SESSION_KEY_SIZE,
+ .data = ctx->key,
+ };
+ int err = gnutls_session_ticket_enable_server(session, &gd);
+ if (err) {
+ kr_log_error("[tls] failed to enable session tickets: %s (%d)\n",
+ gnutls_strerror_name(err), err);
+ /* but continue without tickets */
+ }
+}
+
+tst_ctx_t * tls_session_ticket_ctx_create(uv_loop_t *loop, const char *secret,
+ size_t secret_len)
+{
+ assert(loop && (!secret_len || secret));
+ #if GNUTLS_VERSION_NUMBER < 0x030500
+ /* We would need different SESSION_KEY_SIZE; avoid assert. */
+ return NULL;
+ #endif
+ tst_ctx_t *ctx = tst_key_create(secret, secret_len, loop);
+ if (ctx) {
+ tst_key_check(&ctx->timer, true);
+ }
+ return ctx;
+}
+
+void tls_session_ticket_ctx_destroy(tst_ctx_t *ctx)
+{
+ if (ctx == NULL) {
+ return;
+ }
+ uv_close((uv_handle_t *)&ctx->timer, &tst_key_destroy);
+}
+
diff --git a/daemon/worker.c b/daemon/worker.c
new file mode 100644
index 0000000..117cc91
--- /dev/null
+++ b/daemon/worker.c
@@ -0,0 +1,2010 @@
+/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <uv.h>
+#include <lua.h>
+#include <libknot/packet/pkt.h>
+#include <libknot/descriptor.h>
+#include <contrib/ucw/lib.h>
+#include <contrib/ucw/mempool.h>
+#include <contrib/wire.h>
+#if defined(__GLIBC__) && defined(_GNU_SOURCE)
+#include <malloc.h>
+#endif
+#include <assert.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+#include "lib/utils.h"
+#include "lib/layer.h"
+#include "daemon/worker.h"
+#include "daemon/bindings.h"
+#include "daemon/engine.h"
+#include "daemon/io.h"
+#include "daemon/tls.h"
+#include "daemon/zimport.h"
+#include "daemon/session.h"
+
+
+/* Magic defaults for the worker. */
+#ifndef MP_FREELIST_SIZE
+# ifdef __clang_analyzer__
+# define MP_FREELIST_SIZE 0
+# else
+# define MP_FREELIST_SIZE 64 /**< Maximum length of the worker mempool freelist */
+# endif
+#endif
+#ifndef QUERY_RATE_THRESHOLD
+#define QUERY_RATE_THRESHOLD (2 * MP_FREELIST_SIZE) /**< Nr of parallel queries considered as high rate */
+#endif
+#ifndef MAX_PIPELINED
+#define MAX_PIPELINED 100
+#endif
+
+#define VERBOSE_MSG(qry, ...) QRVERBOSE(qry, "wrkr", __VA_ARGS__)
+
+/** Client request state. */
+struct request_ctx
+{
+ struct kr_request req;
+ struct {
+ union inaddr addr;
+ union inaddr dst_addr;
+ /* uv_handle_t *handle; */
+
+ /** NULL if the request didn't come over network. */
+ struct session *session;
+ } source;
+ struct worker_ctx *worker;
+ struct qr_task *task;
+};
+
+/** Query resolution task. */
+struct qr_task
+{
+ struct request_ctx *ctx;
+ knot_pkt_t *pktbuf;
+ qr_tasklist_t waiting;
+ struct session *pending[MAX_PENDING];
+ uint16_t pending_count;
+ uint16_t addrlist_count;
+ uint16_t addrlist_turn;
+ uint16_t timeouts;
+ uint16_t iter_count;
+ struct sockaddr *addrlist;
+ uint32_t refs;
+ bool finished : 1;
+ bool leading : 1;
+ uint64_t creation_time;
+};
+
+
+/* Convenience macros */
+#define qr_task_ref(task) \
+ do { ++(task)->refs; } while(0)
+#define qr_task_unref(task) \
+ do { if (task && --(task)->refs == 0) { qr_task_free(task); } } while (0)
+
+/** @internal get key for tcp session
+ * @note kr_straddr() return pointer to static string
+ */
+#define tcpsess_key(addr) kr_straddr(addr)
+
+/* Forward decls */
+static void qr_task_free(struct qr_task *task);
+static int qr_task_step(struct qr_task *task,
+ const struct sockaddr *packet_source,
+ knot_pkt_t *packet);
+static int qr_task_send(struct qr_task *task, struct session *session,
+ struct sockaddr *addr, knot_pkt_t *pkt);
+static int qr_task_finalize(struct qr_task *task, int state);
+static void qr_task_complete(struct qr_task *task);
+static struct session* worker_find_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr *addr);
+static int worker_add_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr *addr,
+ struct session *session);
+static struct session* worker_find_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr *addr);
+static void on_tcp_connect_timeout(uv_timer_t *timer);
+
+/** @internal Get singleton worker. */
+static inline struct worker_ctx *get_worker(void)
+{
+ return uv_default_loop()->data;
+}
+
+/*! @internal Create a UDP/TCP handle for an outgoing AF_INET* connection.
+ * socktype is SOCK_* */
+static uv_handle_t *ioreq_spawn(struct worker_ctx *worker,
+ int socktype, sa_family_t family, bool has_tls)
+{
+ bool precond = (socktype == SOCK_DGRAM || socktype == SOCK_STREAM)
+ && (family == AF_INET || family == AF_INET6);
+ if (!precond) {
+ assert(false);
+ kr_log_verbose("[work] ioreq_spawn: pre-condition failed\n");
+ return NULL;
+ }
+
+ /* Create connection for iterative query */
+ uv_handle_t *handle = malloc(socktype == SOCK_DGRAM
+ ? sizeof(uv_udp_t) : sizeof(uv_tcp_t));
+ if (!handle) {
+ return NULL;
+ }
+ int ret = io_create(worker->loop, handle, socktype, family, has_tls);
+ if (ret) {
+ if (ret == UV_EMFILE) {
+ worker->too_many_open = true;
+ worker->rconcurrent_highwatermark = worker->stats.rconcurrent;
+ }
+ free(handle);
+ return NULL;
+ }
+
+ /* Bind to outgoing address, according to IP v4/v6. */
+ union inaddr *addr;
+ if (family == AF_INET) {
+ addr = (union inaddr *)&worker->out_addr4;
+ } else {
+ addr = (union inaddr *)&worker->out_addr6;
+ }
+ if (addr->ip.sa_family != AF_UNSPEC) {
+ assert(addr->ip.sa_family == family);
+ if (socktype == SOCK_DGRAM) {
+ uv_udp_t *udp = (uv_udp_t *)handle;
+ ret = uv_udp_bind(udp, &addr->ip, 0);
+ } else if (socktype == SOCK_STREAM){
+ uv_tcp_t *tcp = (uv_tcp_t *)handle;
+ ret = uv_tcp_bind(tcp, &addr->ip, 0);
+ }
+ }
+
+ if (ret != 0) {
+ io_deinit(handle);
+ free(handle);
+ return NULL;
+ }
+
+ /* Set current handle as a subrequest type. */
+ struct session *session = handle->data;
+ session_flags(session)->outgoing = true;
+ /* Connect or issue query datagram */
+ return handle;
+}
+
+static void ioreq_kill_pending(struct qr_task *task)
+{
+ for (uint16_t i = 0; i < task->pending_count; ++i) {
+ session_kill_ioreq(task->pending[i], task);
+ }
+ task->pending_count = 0;
+}
+
+/** @cond This memory layout is internal to mempool.c, use only for debugging. */
+#if defined(__SANITIZE_ADDRESS__)
+struct mempool_chunk {
+ struct mempool_chunk *next;
+ size_t size;
+};
+static void mp_poison(struct mempool *mp, bool poison)
+{
+ if (!poison) { /* @note mempool is part of the first chunk, unpoison it first */
+ kr_asan_unpoison(mp, sizeof(*mp));
+ }
+ struct mempool_chunk *chunk = mp->state.last[0];
+ void *chunk_off = (uint8_t *)chunk - chunk->size;
+ if (poison) {
+ kr_asan_poison(chunk_off, chunk->size);
+ } else {
+ kr_asan_unpoison(chunk_off, chunk->size);
+ }
+}
+#else
+#define mp_poison(mp, enable)
+#endif
+/** @endcond */
+
+/** Get a mempool. (Recycle if possible.) */
+static inline struct mempool *pool_borrow(struct worker_ctx *worker)
+{
+ struct mempool *mp = NULL;
+ if (worker->pool_mp.len > 0) {
+ mp = array_tail(worker->pool_mp);
+ array_pop(worker->pool_mp);
+ mp_poison(mp, 0);
+ } else { /* No mempool on the freelist, create new one */
+ mp = mp_new (4 * CPU_PAGE_SIZE);
+ }
+ return mp;
+}
+
+/** Return a mempool. (Cache them up to some count.) */
+static inline void pool_release(struct worker_ctx *worker, struct mempool *mp)
+{
+ if (worker->pool_mp.len < MP_FREELIST_SIZE) {
+ mp_flush(mp);
+ array_push(worker->pool_mp, mp);
+ mp_poison(mp, 1);
+ } else {
+ mp_delete(mp);
+ }
+}
+
+/** Create a key for an outgoing subrequest: qname, qclass, qtype.
+ * @param key Destination buffer for key size, MUST be SUBREQ_KEY_LEN or larger.
+ * @return key length if successful or an error
+ */
+static const size_t SUBREQ_KEY_LEN = KR_RRKEY_LEN;
+static int subreq_key(char *dst, knot_pkt_t *pkt)
+{
+ assert(pkt);
+ return kr_rrkey(dst, knot_pkt_qclass(pkt), knot_pkt_qname(pkt),
+ knot_pkt_qtype(pkt), knot_pkt_qtype(pkt));
+}
+
+/** Create and initialize a request_ctx (on a fresh mempool).
+ *
+ * handle and addr point to the source of the request, and they are NULL
+ * in case the request didn't come from network.
+ */
+static struct request_ctx *request_create(struct worker_ctx *worker,
+ uv_handle_t *handle,
+ const struct sockaddr *addr,
+ uint32_t uid)
+{
+ knot_mm_t pool = {
+ .ctx = pool_borrow(worker),
+ .alloc = (knot_mm_alloc_t) mp_alloc
+ };
+
+ /* Create request context */
+ struct request_ctx *ctx = mm_alloc(&pool, sizeof(*ctx));
+ if (!ctx) {
+ pool_release(worker, pool.ctx);
+ return NULL;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ /* TODO Relocate pool to struct request */
+ ctx->worker = worker;
+ struct session *s = handle ? handle->data : NULL;
+ if (s) {
+ assert(session_flags(s)->outgoing == false);
+ }
+ ctx->source.session = s;
+
+ struct kr_request *req = &ctx->req;
+ req->pool = pool;
+ req->vars_ref = LUA_NOREF;
+ req->uid = uid;
+ req->daemon_context = worker;
+
+ /* Remember query source addr */
+ if (!addr || (addr->sa_family != AF_INET && addr->sa_family != AF_INET6)) {
+ ctx->source.addr.ip.sa_family = AF_UNSPEC;
+ } else {
+ memcpy(&ctx->source.addr, addr, kr_sockaddr_len(addr));
+ ctx->req.qsource.addr = &ctx->source.addr.ip;
+ }
+
+ worker->stats.rconcurrent += 1;
+
+ if (!handle) {
+ return ctx;
+ }
+
+ /* Remember the destination address. */
+ int addr_len = sizeof(ctx->source.dst_addr);
+ struct sockaddr *dst_addr = &ctx->source.dst_addr.ip;
+ ctx->source.dst_addr.ip.sa_family = AF_UNSPEC;
+ if (handle->type == UV_UDP) {
+ if (uv_udp_getsockname((uv_udp_t *)handle, dst_addr, &addr_len) == 0) {
+ req->qsource.dst_addr = dst_addr;
+ }
+ req->qsource.flags.tcp = false;
+ req->qsource.flags.tls = false;
+ } else if (handle->type == UV_TCP) {
+ if (uv_tcp_getsockname((uv_tcp_t *)handle, dst_addr, &addr_len) == 0) {
+ req->qsource.dst_addr = dst_addr;
+ }
+ req->qsource.flags.tcp = true;
+ req->qsource.flags.tls = s && session_flags(s)->has_tls;
+ }
+
+ return ctx;
+}
+
+/** More initialization, related to the particular incoming query/packet. */
+static int request_start(struct request_ctx *ctx, knot_pkt_t *query)
+{
+ assert(query && ctx);
+ size_t answer_max = KNOT_WIRE_MIN_PKTSIZE;
+ struct kr_request *req = &ctx->req;
+
+ /* source.session can be empty if request was generated by kresd itself */
+ struct session *s = ctx->source.session;
+ if (!s || session_get_handle(s)->type == UV_TCP) {
+ answer_max = KNOT_WIRE_MAX_PKTSIZE;
+ } else if (knot_pkt_has_edns(query)) { /* EDNS */
+ answer_max = MAX(knot_edns_get_payload(query->opt_rr),
+ KNOT_WIRE_MIN_PKTSIZE);
+ }
+ req->qsource.size = query->size;
+ if (knot_pkt_has_tsig(query)) {
+ req->qsource.size += query->tsig_wire.len;
+ }
+
+ knot_pkt_t *answer = knot_pkt_new(NULL, answer_max, &req->pool);
+ if (!answer) { /* Failed to allocate answer */
+ return kr_error(ENOMEM);
+ }
+
+ knot_pkt_t *pkt = knot_pkt_new(NULL, req->qsource.size, &req->pool);
+ if (!pkt) {
+ return kr_error(ENOMEM);
+ }
+
+ int ret = knot_pkt_copy(pkt, query);
+ if (ret != KNOT_EOK && ret != KNOT_ETRAIL) {
+ return kr_error(ENOMEM);
+ }
+ req->qsource.packet = pkt;
+
+ /* Start resolution */
+ struct worker_ctx *worker = ctx->worker;
+ struct engine *engine = worker->engine;
+ kr_resolve_begin(req, &engine->resolver, answer);
+ worker->stats.queries += 1;
+ /* Throttle outbound queries only when high pressure */
+ if (worker->stats.concurrent < QUERY_RATE_THRESHOLD) {
+ req->options.NO_THROTTLE = true;
+ }
+ return kr_ok();
+}
+
+static void request_free(struct request_ctx *ctx)
+{
+ struct worker_ctx *worker = ctx->worker;
+ /* Dereference any Lua vars table if exists */
+ if (ctx->req.vars_ref != LUA_NOREF) {
+ lua_State *L = worker->engine->L;
+ /* Get worker variables table */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, worker->vars_table_ref);
+ /* Get next free element (position 0) and store it under current reference (forming a list) */
+ lua_rawgeti(L, -1, 0);
+ lua_rawseti(L, -2, ctx->req.vars_ref);
+ /* Set current reference as the next free element */
+ lua_pushinteger(L, ctx->req.vars_ref);
+ lua_rawseti(L, -2, 0);
+ lua_pop(L, 1);
+ ctx->req.vars_ref = LUA_NOREF;
+ }
+ /* Return mempool to ring or free it if it's full */
+ pool_release(worker, ctx->req.pool.ctx);
+ /* @note The 'task' is invalidated from now on. */
+ /* Decommit memory every once in a while */
+ static int mp_delete_count = 0;
+ if (++mp_delete_count == 100000) {
+ lua_gc(worker->engine->L, LUA_GCCOLLECT, 0);
+#if defined(__GLIBC__) && defined(_GNU_SOURCE)
+ malloc_trim(0);
+#endif
+ mp_delete_count = 0;
+ }
+ worker->stats.rconcurrent -= 1;
+}
+
+static struct qr_task *qr_task_create(struct request_ctx *ctx)
+{
+ /* How much can client handle? */
+ struct engine *engine = ctx->worker->engine;
+ size_t pktbuf_max = KR_EDNS_PAYLOAD;
+ if (engine->resolver.opt_rr) {
+ pktbuf_max = MAX(knot_edns_get_payload(engine->resolver.opt_rr),
+ pktbuf_max);
+ }
+
+ /* Create resolution task */
+ struct qr_task *task = mm_alloc(&ctx->req.pool, sizeof(*task));
+ if (!task) {
+ return NULL;
+ }
+ memset(task, 0, sizeof(*task)); /* avoid accidentally unintialized fields */
+
+ /* Create packet buffers for answer and subrequests */
+ knot_pkt_t *pktbuf = knot_pkt_new(NULL, pktbuf_max, &ctx->req.pool);
+ if (!pktbuf) {
+ mm_free(&ctx->req.pool, task);
+ return NULL;
+ }
+ pktbuf->size = 0;
+
+ task->ctx = ctx;
+ task->pktbuf = pktbuf;
+ array_init(task->waiting);
+ task->refs = 0;
+ assert(ctx->task == NULL);
+ ctx->task = task;
+ /* Make the primary reference to task. */
+ qr_task_ref(task);
+ task->creation_time = kr_now();
+ ctx->worker->stats.concurrent += 1;
+ return task;
+}
+
+/* This is called when the task refcount is zero, free memory. */
+static void qr_task_free(struct qr_task *task)
+{
+ struct request_ctx *ctx = task->ctx;
+
+ assert(ctx);
+
+ struct worker_ctx *worker = ctx->worker;
+
+ if (ctx->task == NULL) {
+ request_free(ctx);
+ }
+
+ /* Update stats */
+ worker->stats.concurrent -= 1;
+}
+
+/*@ Register new qr_task within session. */
+static int qr_task_register(struct qr_task *task, struct session *session)
+{
+ assert(!session_flags(session)->outgoing && session_get_handle(session)->type == UV_TCP);
+
+ session_tasklist_add(session, task);
+
+ struct request_ctx *ctx = task->ctx;
+ assert(ctx && (ctx->source.session == NULL || ctx->source.session == session));
+ ctx->source.session = session;
+ /* Soft-limit on parallel queries, there is no "slow down" RCODE
+ * that we could use to signalize to client, but we can stop reading,
+ * an in effect shrink TCP window size. To get more precise throttling,
+ * we would need to copy remainder of the unread buffer and reassemble
+ * when resuming reading. This is NYI. */
+ if (session_tasklist_get_len(session) >= task->ctx->worker->tcp_pipeline_max &&
+ !session_flags(session)->throttled && !session_flags(session)->closing) {
+ session_stop_read(session);
+ session_flags(session)->throttled = true;
+ }
+
+ return 0;
+}
+
+static void qr_task_complete(struct qr_task *task)
+{
+ struct request_ctx *ctx = task->ctx;
+
+ /* Kill pending I/O requests */
+ ioreq_kill_pending(task);
+ assert(task->waiting.len == 0);
+ assert(task->leading == false);
+
+ struct session *s = ctx->source.session;
+ if (s) {
+ assert(!session_flags(s)->outgoing && session_waitinglist_is_empty(s));
+ ctx->source.session = NULL;
+ session_tasklist_del(s, task);
+ }
+
+ /* Release primary reference to task. */
+ if (ctx->task == task) {
+ ctx->task = NULL;
+ qr_task_unref(task);
+ }
+}
+
+/* This is called when we send subrequest / answer */
+static int qr_task_on_send(struct qr_task *task, uv_handle_t *handle, int status)
+{
+
+ if (task->finished) {
+ assert(task->leading == false);
+ qr_task_complete(task);
+ }
+
+ if (!handle || handle->type != UV_TCP) {
+ return status;
+ }
+
+ struct session* s = handle->data;
+ assert(s);
+ if (status != 0) {
+ session_tasklist_del(s, task);
+ }
+
+ if (session_flags(s)->outgoing || session_flags(s)->closing) {
+ return status;
+ }
+
+ struct worker_ctx *worker = task->ctx->worker;
+ if (session_flags(s)->throttled &&
+ session_tasklist_get_len(s) < worker->tcp_pipeline_max/2) {
+ /* Start reading again if the session is throttled and
+ * the number of outgoing requests is below watermark. */
+ session_start_read(s);
+ session_flags(s)->throttled = false;
+ }
+
+ return status;
+}
+
+static void on_send(uv_udp_send_t *req, int status)
+{
+ struct qr_task *task = req->data;
+ uv_handle_t *h = (uv_handle_t *)req->handle;
+ qr_task_on_send(task, h, status);
+ qr_task_unref(task);
+ free(req);
+}
+
+static void on_write(uv_write_t *req, int status)
+{
+ struct qr_task *task = req->data;
+ uv_handle_t *h = (uv_handle_t *)req->handle;
+ qr_task_on_send(task, h, status);
+ qr_task_unref(task);
+ free(req);
+}
+
+static int qr_task_send(struct qr_task *task, struct session *session,
+ struct sockaddr *addr, knot_pkt_t *pkt)
+{
+ if (!session) {
+ return qr_task_on_send(task, NULL, kr_error(EIO));
+ }
+
+ int ret = 0;
+ struct request_ctx *ctx = task->ctx;
+
+ uv_handle_t *handle = session_get_handle(session);
+ assert(handle && handle->data == session);
+ const bool is_stream = handle->type == UV_TCP;
+ if (!is_stream && handle->type != UV_UDP) abort();
+
+ if (addr == NULL) {
+ addr = session_get_peer(session);
+ }
+
+ if (pkt == NULL) {
+ pkt = worker_task_get_pktbuf(task);
+ }
+
+ if (session_flags(session)->outgoing && handle->type == UV_TCP) {
+ size_t try_limit = session_tasklist_get_len(session) + 1;
+ uint16_t msg_id = knot_wire_get_id(pkt->wire);
+ size_t try_count = 0;
+ while (session_tasklist_find_msgid(session, msg_id) &&
+ try_count <= try_limit) {
+ ++msg_id;
+ ++try_count;
+ }
+ if (try_count > try_limit) {
+ return kr_error(ENOENT);
+ }
+ worker_task_pkt_set_msgid(task, msg_id);
+ }
+
+ uv_handle_t *ioreq = malloc(is_stream ? sizeof(uv_write_t) : sizeof(uv_udp_send_t));
+ if (!ioreq) {
+ return qr_task_on_send(task, handle, kr_error(ENOMEM));
+ }
+
+ /* Pending ioreq on current task */
+ qr_task_ref(task);
+
+ struct worker_ctx *worker = ctx->worker;
+ /* Send using given protocol */
+ assert(!session_flags(session)->closing);
+ if (session_flags(session)->has_tls) {
+ uv_write_t *write_req = (uv_write_t *)ioreq;
+ write_req->data = task;
+ ret = tls_write(write_req, handle, pkt, &on_write);
+ } else if (handle->type == UV_UDP) {
+ uv_udp_send_t *send_req = (uv_udp_send_t *)ioreq;
+ uv_buf_t buf = { (char *)pkt->wire, pkt->size };
+ send_req->data = task;
+ ret = uv_udp_send(send_req, (uv_udp_t *)handle, &buf, 1, addr, &on_send);
+ } else if (handle->type == UV_TCP) {
+ uv_write_t *write_req = (uv_write_t *)ioreq;
+ uint16_t pkt_size = htons(pkt->size);
+ uv_buf_t buf[2] = {
+ { (char *)&pkt_size, sizeof(pkt_size) },
+ { (char *)pkt->wire, pkt->size }
+ };
+ write_req->data = task;
+ ret = uv_write(write_req, (uv_stream_t *)handle, buf, 2, &on_write);
+ } else {
+ assert(false);
+ }
+
+ if (ret == 0) {
+ session_touch(session);
+ if (session_flags(session)->outgoing) {
+ session_tasklist_add(session, task);
+ }
+ if (worker->too_many_open &&
+ worker->stats.rconcurrent <
+ worker->rconcurrent_highwatermark - 10) {
+ worker->too_many_open = false;
+ }
+ } else {
+ free(ioreq);
+ qr_task_unref(task);
+ if (ret == UV_EMFILE) {
+ worker->too_many_open = true;
+ worker->rconcurrent_highwatermark = worker->stats.rconcurrent;
+ ret = kr_error(UV_EMFILE);
+ }
+ }
+
+ /* Update statistics */
+ if (session_flags(session)->outgoing && addr) {
+ if (session_flags(session)->has_tls)
+ worker->stats.tls += 1;
+ else if (handle->type == UV_UDP)
+ worker->stats.udp += 1;
+ else
+ worker->stats.tcp += 1;
+
+ if (addr->sa_family == AF_INET6)
+ worker->stats.ipv6 += 1;
+ else if (addr->sa_family == AF_INET)
+ worker->stats.ipv4 += 1;
+ }
+ return ret;
+}
+
+static struct kr_query *task_get_last_pending_query(struct qr_task *task)
+{
+ if (!task || task->ctx->req.rplan.pending.len == 0) {
+ return NULL;
+ }
+
+ return array_tail(task->ctx->req.rplan.pending);
+}
+
+static int session_tls_hs_cb(struct session *session, int status)
+{
+ assert(session_flags(session)->outgoing);
+ uv_handle_t *handle = session_get_handle(session);
+ uv_loop_t *loop = handle->loop;
+ struct worker_ctx *worker = loop->data;
+ struct sockaddr *peer = session_get_peer(session);
+ int deletion_res = worker_del_tcp_waiting(worker, peer);
+ int ret = kr_ok();
+
+ if (status) {
+ struct qr_task *task = session_waitinglist_get(session);
+ if (task) {
+ struct kr_qflags *options = &task->ctx->req.options;
+ unsigned score = options->FORWARD || options->STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD;
+ kr_nsrep_update_rtt(NULL, peer, score,
+ worker->engine->resolver.cache_rtt,
+ KR_NS_UPDATE_NORESET);
+ }
+#ifndef NDEBUG
+ else {
+ /* Task isn't in the list of tasks
+ * waiting for connection to upstream.
+ * So that it MUST be unsuccessful rehandshake.
+ * Check it. */
+ assert(deletion_res != 0);
+ const char *key = tcpsess_key(peer);
+ assert(key);
+ assert(map_contains(&worker->tcp_connected, key) != 0);
+ }
+#endif
+ return ret;
+ }
+
+ /* handshake was completed successfully */
+ struct tls_client_ctx_t *tls_client_ctx = session_tls_get_client_ctx(session);
+ struct tls_client_paramlist_entry *tls_params = tls_client_ctx->params;
+ gnutls_session_t tls_session = tls_client_ctx->c.tls_session;
+ if (gnutls_session_is_resumed(tls_session) != 0) {
+ kr_log_verbose("[tls_client] TLS session has resumed\n");
+ } else {
+ kr_log_verbose("[tls_client] TLS session has not resumed\n");
+ /* session wasn't resumed, delete old session data ... */
+ if (tls_params->session_data.data != NULL) {
+ gnutls_free(tls_params->session_data.data);
+ tls_params->session_data.data = NULL;
+ tls_params->session_data.size = 0;
+ }
+ /* ... and get the new session data */
+ gnutls_datum_t tls_session_data = { NULL, 0 };
+ ret = gnutls_session_get_data2(tls_session, &tls_session_data);
+ if (ret == 0) {
+ tls_params->session_data = tls_session_data;
+ }
+ }
+
+ struct session *s = worker_find_tcp_connected(worker, peer);
+ ret = kr_ok();
+ if (deletion_res == kr_ok()) {
+ /* peer was in the waiting list, add to the connected list. */
+ if (s) {
+ /* Something went wrong,
+ * peer already is in the connected list. */
+ ret = kr_error(EINVAL);
+ } else {
+ ret = worker_add_tcp_connected(worker, peer, session);
+ }
+ } else {
+ /* peer wasn't in the waiting list.
+ * It can be
+ * 1) either successful rehandshake; in this case peer
+ * must be already in the connected list.
+ * 2) or successful handshake with session, which was timeouted
+ * by on_tcp_connect_timeout(); after successful tcp connection;
+ * in this case peer isn't in the connected list.
+ **/
+ if (!s || s != session) {
+ ret = kr_error(EINVAL);
+ }
+ }
+ if (ret == kr_ok()) {
+ while (!session_waitinglist_is_empty(session)) {
+ struct qr_task *t = session_waitinglist_get(session);
+ ret = qr_task_send(t, session, NULL, NULL);
+ if (ret != 0) {
+ break;
+ }
+ session_waitinglist_pop(session, true);
+ }
+ } else {
+ ret = kr_error(EINVAL);
+ }
+
+ if (ret != kr_ok()) {
+ /* Something went wrong.
+ * Either addition to the list of connected sessions
+ * or write to upstream failed. */
+ worker_del_tcp_connected(worker, peer);
+ session_waitinglist_finalize(session, KR_STATE_FAIL);
+ assert(session_tasklist_is_empty(session));
+ session_close(session);
+ } else {
+ session_timer_stop(session);
+ session_timer_start(session, tcp_timeout_trigger,
+ MAX_TCP_INACTIVITY, MAX_TCP_INACTIVITY);
+ }
+ return kr_ok();
+}
+
+static int send_waiting(struct session *session)
+{
+ int ret = 0;
+ while (!session_waitinglist_is_empty(session)) {
+ struct qr_task *t = session_waitinglist_get(session);
+ ret = qr_task_send(t, session, NULL, NULL);
+ if (ret != 0) {
+ struct worker_ctx *worker = t->ctx->worker;
+ struct sockaddr *peer = session_get_peer(session);
+ session_waitinglist_finalize(session, KR_STATE_FAIL);
+ session_tasklist_finalize(session, KR_STATE_FAIL);
+ worker_del_tcp_connected(worker, peer);
+ session_close(session);
+ break;
+ }
+ session_waitinglist_pop(session, true);
+ }
+ return ret;
+}
+
+static void on_connect(uv_connect_t *req, int status)
+{
+ struct worker_ctx *worker = get_worker();
+ uv_stream_t *handle = req->handle;
+ struct session *session = handle->data;
+ struct sockaddr *peer = session_get_peer(session);
+ free(req);
+
+ assert(session_flags(session)->outgoing);
+
+ if (session_flags(session)->closing) {
+ worker_del_tcp_waiting(worker, peer);
+ assert(session_is_empty(session));
+ return;
+ }
+
+ /* Check if the connection is in the waiting list.
+ * If no, most likely this is timeouted connection
+ * which was removed from waiting list by
+ * on_tcp_connect_timeout() callback. */
+ struct session *s = worker_find_tcp_waiting(worker, peer);
+ if (!s || s != session) {
+ /* session isn't on the waiting list.
+ * it's timeouted session. */
+ if (VERBOSE_STATUS) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_verbose( "[wrkr]=> connected to '%s', but session "
+ "is already timeouted, close\n",
+ peer_str ? peer_str : "");
+ }
+ assert(session_tasklist_is_empty(session));
+ session_waitinglist_retry(session, false);
+ session_close(session);
+ return;
+ }
+
+ s = worker_find_tcp_connected(worker, peer);
+ if (s) {
+ /* session already in the connected list.
+ * Something went wrong, it can be due to races when kresd has tried
+ * to reconnect to upstream after unsuccessful attempt. */
+ if (VERBOSE_STATUS) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_verbose( "[wrkr]=> connected to '%s', but peer "
+ "is already connected, close\n",
+ peer_str ? peer_str : "");
+ }
+ assert(session_tasklist_is_empty(session));
+ session_waitinglist_retry(session, false);
+ session_close(session);
+ return;
+ }
+
+ if (status != 0) {
+ if (VERBOSE_STATUS) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_verbose( "[wrkr]=> connection to '%s' failed (%s), flagged as 'bad'\n",
+ peer_str ? peer_str : "", uv_strerror(status));
+ }
+ worker_del_tcp_waiting(worker, peer);
+ struct qr_task *task = session_waitinglist_get(session);
+ if (task && status != UV_ETIMEDOUT) {
+ /* Penalize upstream.
+ * In case of UV_ETIMEDOUT upstream has been
+ * already penalized in on_tcp_connect_timeout() */
+ struct kr_qflags *options = &task->ctx->req.options;
+ unsigned score = options->FORWARD || options->STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD;
+ kr_nsrep_update_rtt(NULL, peer, score,
+ worker->engine->resolver.cache_rtt,
+ KR_NS_UPDATE_NORESET);
+ }
+ assert(session_tasklist_is_empty(session));
+ session_waitinglist_retry(session, false);
+ session_close(session);
+ return;
+ }
+
+ if (!session_flags(session)->has_tls) {
+ /* if there is a TLS, session still waiting for handshake,
+ * otherwise remove it from waiting list */
+ if (worker_del_tcp_waiting(worker, peer) != 0) {
+ /* session isn't in list of waiting queries, *
+ * something gone wrong */
+ session_waitinglist_finalize(session, KR_STATE_FAIL);
+ assert(session_tasklist_is_empty(session));
+ session_close(session);
+ return;
+ }
+ }
+
+ if (VERBOSE_STATUS) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_verbose( "[wrkr]=> connected to '%s'\n", peer_str ? peer_str : "");
+ }
+
+ session_flags(session)->connected = true;
+ session_start_read(session);
+
+ int ret = kr_ok();
+ if (session_flags(session)->has_tls) {
+ struct tls_client_ctx_t *tls_ctx = session_tls_get_client_ctx(session);
+ ret = tls_client_connect_start(tls_ctx, session, session_tls_hs_cb);
+ if (ret == kr_error(EAGAIN)) {
+ session_timer_stop(session);
+ session_timer_start(session, tcp_timeout_trigger,
+ MAX_TCP_INACTIVITY, MAX_TCP_INACTIVITY);
+ return;
+ }
+ } else {
+ worker_add_tcp_connected(worker, peer, session);
+ }
+
+ ret = send_waiting(session);
+ if (ret != 0) {
+ return;
+ }
+
+ session_timer_stop(session);
+ session_timer_start(session, tcp_timeout_trigger,
+ MAX_TCP_INACTIVITY, MAX_TCP_INACTIVITY);
+}
+
+static void on_tcp_connect_timeout(uv_timer_t *timer)
+{
+ struct session *session = timer->data;
+
+ uv_timer_stop(timer);
+ struct worker_ctx *worker = get_worker();
+
+ assert (session_tasklist_is_empty(session));
+
+ struct sockaddr *peer = session_get_peer(session);
+ worker_del_tcp_waiting(worker, peer);
+
+ struct qr_task *task = session_waitinglist_get(session);
+ if (!task) {
+ /* Normally shouldn't happen. */
+ const char *peer_str = kr_straddr(peer);
+ VERBOSE_MSG(NULL, "=> connection to '%s' failed (internal timeout), empty waitinglist\n",
+ peer_str ? peer_str : "");
+ return;
+ }
+
+ struct kr_query *qry = task_get_last_pending_query(task);
+ WITH_VERBOSE (qry) {
+ const char *peer_str = kr_straddr(peer);
+ VERBOSE_MSG(qry, "=> connection to '%s' failed (internal timeout)\n",
+ peer_str ? peer_str : "");
+ }
+
+ unsigned score = qry->flags.FORWARD || qry->flags.STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD;
+ kr_nsrep_update_rtt(NULL, peer, score,
+ worker->engine->resolver.cache_rtt,
+ KR_NS_UPDATE_NORESET);
+
+ worker->stats.timeout += session_waitinglist_get_len(session);
+ session_waitinglist_retry(session, true);
+ assert (session_tasklist_is_empty(session));
+ /* uv_cancel() doesn't support uv_connect_t request,
+ * so that we can't cancel it.
+ * There still exists possibility of successful connection
+ * for this request.
+ * So connection callback (on_connect()) must check
+ * if connection is in the list of waiting connection.
+ * If no, most likely this is timeouted connection even if
+ * it was successful. */
+}
+
+/* This is called when I/O timeouts */
+static void on_udp_timeout(uv_timer_t *timer)
+{
+ struct session *session = timer->data;
+ assert(session_get_handle(session)->data == session);
+ assert(session_tasklist_get_len(session) == 1);
+ assert(session_waitinglist_is_empty(session));
+
+ uv_timer_stop(timer);
+
+ /* Penalize all tried nameservers with a timeout. */
+ struct qr_task *task = session_tasklist_get_first(session);
+ struct worker_ctx *worker = task->ctx->worker;
+ if (task->leading && task->pending_count > 0) {
+ struct kr_query *qry = array_tail(task->ctx->req.rplan.pending);
+ struct sockaddr_in6 *addrlist = (struct sockaddr_in6 *)task->addrlist;
+ for (uint16_t i = 0; i < MIN(task->pending_count, task->addrlist_count); ++i) {
+ struct sockaddr *choice = (struct sockaddr *)(&addrlist[i]);
+ WITH_VERBOSE(qry) {
+ char *addr_str = kr_straddr(choice);
+ VERBOSE_MSG(qry, "=> server: '%s' flagged as 'bad'\n", addr_str ? addr_str : "");
+ }
+ unsigned score = qry->flags.FORWARD || qry->flags.STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD;
+ kr_nsrep_update_rtt(&qry->ns, choice, score,
+ worker->engine->resolver.cache_rtt,
+ KR_NS_UPDATE_NORESET);
+ }
+ }
+ task->timeouts += 1;
+ worker->stats.timeout += 1;
+ qr_task_step(task, NULL, NULL);
+}
+
+static uv_handle_t *retransmit(struct qr_task *task)
+{
+ uv_handle_t *ret = NULL;
+ if (task && task->addrlist && task->addrlist_count > 0) {
+ struct sockaddr_in6 *choice = &((struct sockaddr_in6 *)task->addrlist)[task->addrlist_turn];
+ if (!choice) {
+ return ret;
+ }
+ if (task->pending_count >= MAX_PENDING) {
+ return ret;
+ }
+ /* Checkout answer before sending it */
+ struct request_ctx *ctx = task->ctx;
+ if (kr_resolve_checkout(&ctx->req, NULL, (struct sockaddr *)choice, SOCK_DGRAM, task->pktbuf) != 0) {
+ return ret;
+ }
+ ret = ioreq_spawn(ctx->worker, SOCK_DGRAM, choice->sin6_family, false);
+ if (!ret) {
+ return ret;
+ }
+ struct sockaddr *addr = (struct sockaddr *)choice;
+ struct session *session = ret->data;
+ struct sockaddr *peer = session_get_peer(session);
+ assert (peer->sa_family == AF_UNSPEC && session_flags(session)->outgoing);
+ memcpy(peer, addr, kr_sockaddr_len(addr));
+ if (qr_task_send(task, session, (struct sockaddr *)choice,
+ task->pktbuf) != 0) {
+ session_close(session);
+ ret = NULL;
+ } else {
+ task->pending[task->pending_count] = session;
+ task->pending_count += 1;
+ task->addrlist_turn = (task->addrlist_turn + 1) %
+ task->addrlist_count; /* Round robin */
+ session_start_read(session); /* Start reading answer */
+ }
+ }
+ return ret;
+}
+
+static void on_retransmit(uv_timer_t *req)
+{
+ struct session *session = req->data;
+ assert(session_tasklist_get_len(session) == 1);
+
+ uv_timer_stop(req);
+ struct qr_task *task = session_tasklist_get_first(session);
+ if (retransmit(task) == NULL) {
+ /* Not possible to spawn request, start timeout timer with remaining deadline. */
+ struct kr_qflags *options = &task->ctx->req.options;
+ uint64_t timeout = options->FORWARD || options->STUB ? KR_NS_FWD_TIMEOUT / 2 :
+ KR_CONN_RTT_MAX - task->pending_count * KR_CONN_RETRY;
+ uv_timer_start(req, on_udp_timeout, timeout, 0);
+ } else {
+ uv_timer_start(req, on_retransmit, KR_CONN_RETRY, 0);
+ }
+}
+
+static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *pkt)
+{
+ if (!task || task->finished) {
+ return;
+ }
+ /* Close pending timer */
+ ioreq_kill_pending(task);
+ /* Clear from outgoing table. */
+ if (!task->leading)
+ return;
+ char key[SUBREQ_KEY_LEN];
+ const int klen = subreq_key(key, task->pktbuf);
+ if (klen > 0) {
+ void *val_deleted;
+ int ret = trie_del(task->ctx->worker->subreq_out, key, klen, &val_deleted);
+ assert(ret == KNOT_EOK && val_deleted == task); (void)ret;
+ }
+ /* Notify waiting tasks. */
+ struct kr_query *leader_qry = array_tail(task->ctx->req.rplan.pending);
+ for (size_t i = task->waiting.len; i > 0; i--) {
+ struct qr_task *follower = task->waiting.at[i - 1];
+ /* Reuse MSGID and 0x20 secret */
+ if (follower->ctx->req.rplan.pending.len > 0) {
+ struct kr_query *qry = array_tail(follower->ctx->req.rplan.pending);
+ qry->id = leader_qry->id;
+ qry->secret = leader_qry->secret;
+ leader_qry->secret = 0; /* Next will be already decoded */
+ }
+ qr_task_step(follower, packet_source, pkt);
+ qr_task_unref(follower);
+ }
+ task->waiting.len = 0;
+ task->leading = false;
+}
+
+static void subreq_lead(struct qr_task *task)
+{
+ assert(task);
+ char key[SUBREQ_KEY_LEN];
+ const int klen = subreq_key(key, task->pktbuf);
+ if (klen < 0)
+ return;
+ struct qr_task **tvp = (struct qr_task **)
+ trie_get_ins(task->ctx->worker->subreq_out, key, klen);
+ if (unlikely(!tvp))
+ return; /*ENOMEM*/
+ if (unlikely(*tvp != NULL)) {
+ assert(false);
+ return;
+ }
+ *tvp = task;
+ task->leading = true;
+}
+
+static bool subreq_enqueue(struct qr_task *task)
+{
+ assert(task);
+ char key[SUBREQ_KEY_LEN];
+ const int klen = subreq_key(key, task->pktbuf);
+ if (klen < 0)
+ return false;
+ struct qr_task **leader = (struct qr_task **)
+ trie_get_try(task->ctx->worker->subreq_out, key, klen);
+ if (!leader /*ENOMEM*/ || !*leader)
+ return false;
+ /* Enqueue itself to leader for this subrequest. */
+ int ret = array_push_mm((*leader)->waiting, task,
+ kr_memreserve, &(*leader)->ctx->req.pool);
+ if (unlikely(ret < 0)) /*ENOMEM*/
+ return false;
+ qr_task_ref(task);
+ return true;
+}
+
+static int qr_task_finalize(struct qr_task *task, int state)
+{
+ assert(task && task->leading == false);
+ if (task->finished) {
+ return 0;
+ }
+ struct request_ctx *ctx = task->ctx;
+ struct session *source_session = ctx->source.session;
+ kr_resolve_finish(&ctx->req, state);
+
+ task->finished = true;
+ if (source_session == NULL) {
+ (void) qr_task_on_send(task, NULL, kr_error(EIO));
+ return state == KR_STATE_DONE ? 0 : kr_error(EIO);
+ }
+
+ /* Reference task as the callback handler can close it */
+ qr_task_ref(task);
+
+ /* Send back answer */
+ assert(!session_flags(source_session)->closing);
+ assert(ctx->source.addr.ip.sa_family != AF_UNSPEC);
+ int res = qr_task_send(task, source_session,
+ (struct sockaddr *)&ctx->source.addr,
+ ctx->req.answer);
+ if (res != kr_ok()) {
+ (void) qr_task_on_send(task, NULL, kr_error(EIO));
+ /* Since source session is erroneous detach all tasks. */
+ while (!session_tasklist_is_empty(source_session)) {
+ struct qr_task *t = session_tasklist_del_first(source_session, false);
+ struct request_ctx *c = t->ctx;
+ assert(c->source.session == source_session);
+ c->source.session = NULL;
+ /* Don't finalize them as there can be other tasks
+ * waiting for answer to this particular task.
+ * (ie. task->leading is true) */
+ worker_task_unref(t);
+ }
+ session_close(source_session);
+ }
+
+ qr_task_unref(task);
+
+ return state == KR_STATE_DONE ? 0 : kr_error(EIO);
+}
+
+static int udp_task_step(struct qr_task *task,
+ const struct sockaddr *packet_source, knot_pkt_t *packet)
+{
+ struct request_ctx *ctx = task->ctx;
+ struct kr_request *req = &ctx->req;
+
+ /* If there is already outgoing query, enqueue to it. */
+ if (subreq_enqueue(task)) {
+ return kr_ok(); /* Will be notified when outgoing query finishes. */
+ }
+ /* Start transmitting */
+ uv_handle_t *handle = retransmit(task);
+ if (handle == NULL) {
+ subreq_finalize(task, packet_source, packet);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ /* Check current query NSLIST */
+ struct kr_query *qry = array_tail(req->rplan.pending);
+ assert(qry != NULL);
+ /* Retransmit at default interval, or more frequently if the mean
+ * RTT of the server is better. If the server is glued, use default rate. */
+ size_t timeout = qry->ns.score;
+ if (timeout > KR_NS_GLUED) {
+ /* We don't have information about variance in RTT, expect +10ms */
+ timeout = MIN(qry->ns.score + 10, KR_CONN_RETRY);
+ } else {
+ timeout = KR_CONN_RETRY;
+ }
+ /* Announce and start subrequest.
+ * @note Only UDP can lead I/O as it doesn't touch 'task->pktbuf' for reassembly.
+ */
+ subreq_lead(task);
+ struct session *session = handle->data;
+ assert(session_get_handle(session) == handle && (handle->type == UV_UDP));
+ int ret = session_timer_start(session, on_retransmit, timeout, 0);
+ /* Start next step with timeout, fatal if can't start a timer. */
+ if (ret != 0) {
+ subreq_finalize(task, packet_source, packet);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ return kr_ok();
+}
+
+static int tcp_task_waiting_connection(struct session *session, struct qr_task *task)
+{
+ assert(session_flags(session)->outgoing);
+ if (session_flags(session)->closing) {
+ /* Something went wrong. Better answer with KR_STATE_FAIL.
+ * TODO: normally should not happen,
+ * consider possibility to transform this into
+ * assert(!session_flags(session)->closing). */
+ return kr_error(EINVAL);
+ }
+ /* Add task to the end of list of waiting tasks.
+ * It will be notified in on_connect() or qr_task_on_send(). */
+ int ret = session_waitinglist_push(session, task);
+ if (ret < 0) {
+ return kr_error(EINVAL);
+ }
+ return kr_ok();
+}
+
+static int tcp_task_existing_connection(struct session *session, struct qr_task *task)
+{
+ assert(session_flags(session)->outgoing);
+ struct request_ctx *ctx = task->ctx;
+ struct worker_ctx *worker = ctx->worker;
+
+ if (session_flags(session)->closing) {
+ /* Something went wrong. Better answer with KR_STATE_FAIL.
+ * TODO: normally should not happen,
+ * consider possibility to transform this into
+ * assert(!session_flags(session)->closing). */
+ return kr_error(EINVAL);
+ }
+
+ /* If there are any unsent queries, send it first. */
+ int ret = send_waiting(session);
+ if (ret != 0) {
+ return kr_error(EINVAL);
+ }
+
+ /* No unsent queries at that point. */
+ if (session_tasklist_get_len(session) >= worker->tcp_pipeline_max) {
+ /* Too many outstanding queries, answer with SERFVAIL, */
+ return kr_error(EINVAL);
+ }
+
+ /* Send query to upstream. */
+ ret = qr_task_send(task, session, NULL, NULL);
+ if (ret != 0) {
+ /* Error, finalize task with SERVFAIL and
+ * close connection to upstream. */
+ session_tasklist_finalize(session, KR_STATE_FAIL);
+ worker_del_tcp_connected(worker, session_get_peer(session));
+ session_close(session);
+ return kr_error(EINVAL);
+ }
+
+ return kr_ok();
+}
+
+static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr *addr)
+{
+ struct request_ctx *ctx = task->ctx;
+ struct worker_ctx *worker = ctx->worker;
+
+ /* Check if there must be TLS */
+ struct engine *engine = worker->engine;
+ struct network *net = &engine->net;
+ const char *key = tcpsess_key(addr);
+ struct tls_client_ctx_t *tls_ctx = NULL;
+ struct tls_client_paramlist_entry *entry = map_get(&net->tls_client_params, key);
+ if (entry) {
+ /* Address is configured to be used with TLS.
+ * We need to allocate auxiliary data structure. */
+ tls_ctx = tls_client_ctx_new(entry, worker);
+ if (!tls_ctx) {
+ return kr_error(EINVAL);
+ }
+ }
+
+ uv_connect_t *conn = malloc(sizeof(uv_connect_t));
+ if (!conn) {
+ tls_client_ctx_free(tls_ctx);
+ return kr_error(EINVAL);
+ }
+ bool has_tls = (tls_ctx != NULL);
+ uv_handle_t *client = ioreq_spawn(worker, SOCK_STREAM, addr->sa_family, has_tls);
+ if (!client) {
+ tls_client_ctx_free(tls_ctx);
+ free(conn);
+ return kr_error(EINVAL);
+ }
+ struct session *session = client->data;
+ assert(session_flags(session)->has_tls == has_tls);
+ if (has_tls) {
+ tls_client_ctx_set_session(tls_ctx, session);
+ session_tls_set_client_ctx(session, tls_ctx);
+ }
+
+ /* Add address to the waiting list.
+ * Now it "is waiting to be connected to." */
+ int ret = worker_add_tcp_waiting(ctx->worker, addr, session);
+ if (ret < 0) {
+ free(conn);
+ session_close(session);
+ return kr_error(EINVAL);
+ }
+
+ conn->data = session;
+ /* Store peer address for the session. */
+ struct sockaddr *peer = session_get_peer(session);
+ memcpy(peer, addr, kr_sockaddr_len(addr));
+
+ /* Start watchdog to catch eventual connection timeout. */
+ ret = session_timer_start(session, on_tcp_connect_timeout,
+ KR_CONN_RTT_MAX, 0);
+ if (ret != 0) {
+ worker_del_tcp_waiting(ctx->worker, addr);
+ free(conn);
+ session_close(session);
+ return kr_error(EINVAL);
+ }
+
+ struct kr_query *qry = task_get_last_pending_query(task);
+ WITH_VERBOSE (qry) {
+ const char *peer_str = kr_straddr(peer);
+ VERBOSE_MSG(qry, "=> connecting to: '%s'\n", peer_str ? peer_str : "");
+ }
+
+ /* Start connection process to upstream. */
+ ret = uv_tcp_connect(conn, (uv_tcp_t *)client, addr , on_connect);
+ if (ret != 0) {
+ session_timer_stop(session);
+ worker_del_tcp_waiting(ctx->worker, addr);
+ free(conn);
+ session_close(session);
+ unsigned score = qry->flags.FORWARD || qry->flags.STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD;
+ kr_nsrep_update_rtt(NULL, peer, score,
+ worker->engine->resolver.cache_rtt,
+ KR_NS_UPDATE_NORESET);
+ WITH_VERBOSE (qry) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_verbose( "[wrkr]=> connect to '%s' failed (%s), flagged as 'bad'\n",
+ peer_str ? peer_str : "", uv_strerror(ret));
+ }
+ return kr_error(EAGAIN);
+ }
+
+ /* Add task to the end of list of waiting tasks.
+ * Will be notified either in on_connect() or in qr_task_on_send(). */
+ ret = session_waitinglist_push(session, task);
+ if (ret < 0) {
+ session_timer_stop(session);
+ worker_del_tcp_waiting(ctx->worker, addr);
+ free(conn);
+ session_close(session);
+ return kr_error(EINVAL);
+ }
+
+ return kr_ok();
+}
+
+static int tcp_task_step(struct qr_task *task,
+ const struct sockaddr *packet_source, knot_pkt_t *packet)
+{
+ assert(task->pending_count == 0);
+
+ /* target */
+ const struct sockaddr *addr = task->addrlist;
+ if (addr->sa_family == AF_UNSPEC) {
+ /* Target isn't defined. Finalize task with SERVFAIL.
+ * Although task->pending_count is zero, there are can be followers,
+ * so we need to call subreq_finalize() to handle them properly. */
+ subreq_finalize(task, packet_source, packet);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ /* Checkout task before connecting */
+ struct request_ctx *ctx = task->ctx;
+ if (kr_resolve_checkout(&ctx->req, NULL, (struct sockaddr *)addr,
+ SOCK_STREAM, task->pktbuf) != 0) {
+ subreq_finalize(task, packet_source, packet);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ int ret;
+ struct session* session = NULL;
+ if ((session = worker_find_tcp_waiting(ctx->worker, addr)) != NULL) {
+ /* Connection is in the list of waiting connections.
+ * It means that connection establishing is coming right now. */
+ ret = tcp_task_waiting_connection(session, task);
+ } else if ((session = worker_find_tcp_connected(ctx->worker, addr)) != NULL) {
+ /* Connection has been already established. */
+ ret = tcp_task_existing_connection(session, task);
+ } else {
+ /* Make connection. */
+ ret = tcp_task_make_connection(task, addr);
+ }
+
+ if (ret != kr_ok()) {
+ subreq_finalize(task, addr, packet);
+ if (ret == kr_error(EAGAIN)) {
+ ret = qr_task_step(task, addr, NULL);
+ } else {
+ ret = qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ }
+
+ return ret;
+}
+
+static int qr_task_step(struct qr_task *task,
+ const struct sockaddr *packet_source, knot_pkt_t *packet)
+{
+ /* No more steps after we're finished. */
+ if (!task || task->finished) {
+ return kr_error(ESTALE);
+ }
+
+ /* Close pending I/O requests */
+ subreq_finalize(task, packet_source, packet);
+ if ((kr_now() - worker_task_creation_time(task)) >= KR_RESOLVE_TIME_LIMIT) {
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+
+ /* Consume input and produce next query */
+ struct request_ctx *ctx = task->ctx;
+ assert(ctx);
+ struct kr_request *req = &ctx->req;
+ struct worker_ctx *worker = ctx->worker;
+ int sock_type = -1;
+ task->addrlist = NULL;
+ task->addrlist_count = 0;
+ task->addrlist_turn = 0;
+
+ if (worker->too_many_open) {
+ /* */
+ struct kr_rplan *rplan = &req->rplan;
+ if (worker->stats.rconcurrent <
+ worker->rconcurrent_highwatermark - 10) {
+ worker->too_many_open = false;
+ } else {
+ if (packet && kr_rplan_empty(rplan)) {
+ /* new query; TODO - make this detection more obvious */
+ kr_resolve_consume(req, packet_source, packet);
+ }
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ }
+
+ int state = kr_resolve_consume(req, packet_source, packet);
+ while (state == KR_STATE_PRODUCE) {
+ state = kr_resolve_produce(req, &task->addrlist,
+ &sock_type, task->pktbuf);
+ if (unlikely(++task->iter_count > KR_ITER_LIMIT ||
+ task->timeouts >= KR_TIMEOUT_LIMIT)) {
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ }
+
+ /* We're done, no more iterations needed */
+ if (state & (KR_STATE_DONE|KR_STATE_FAIL)) {
+ return qr_task_finalize(task, state);
+ } else if (!task->addrlist || sock_type < 0) {
+ return qr_task_step(task, NULL, NULL);
+ }
+
+ /* Count available address choices */
+ struct sockaddr_in6 *choice = (struct sockaddr_in6 *)task->addrlist;
+ for (size_t i = 0; i < KR_NSREP_MAXADDR && choice->sin6_family != AF_UNSPEC; ++i) {
+ task->addrlist_count += 1;
+ choice += 1;
+ }
+
+ /* Upgrade to TLS if the upstream address is configured as DoT capable. */
+ if (task->addrlist_count > 0 && kr_inaddr_port(task->addrlist) == KR_DNS_PORT) {
+ /* TODO if there are multiple addresses (task->addrlist_count > 1)
+ * check all of them. */
+ struct engine *engine = worker->engine;
+ struct network *net = &engine->net;
+ struct tls_client_paramlist_entry *tls_entry =
+ tls_client_try_upgrade(&net->tls_client_params, task->addrlist);
+ if (tls_entry != NULL) {
+ kr_inaddr_set_port(task->addrlist, KR_DNS_TLS_PORT);
+ packet_source = NULL;
+ sock_type = SOCK_STREAM;
+ /* TODO in this case in tcp_task_make_connection() will be performed
+ * redundant map_get() call. */
+ }
+ }
+
+ int ret = 0;
+ if (sock_type == SOCK_DGRAM) {
+ /* Start fast retransmit with UDP. */
+ ret = udp_task_step(task, packet_source, packet);
+ } else {
+ /* TCP. Connect to upstream or send the query if connection already exists. */
+ assert (sock_type == SOCK_STREAM);
+ ret = tcp_task_step(task, packet_source, packet);
+ }
+ return ret;
+}
+
+static int parse_packet(knot_pkt_t *query)
+{
+ if (!query){
+ return kr_error(EINVAL);
+ }
+
+ /* Parse query packet. */
+ int ret = knot_pkt_parse(query, 0);
+ if (ret == KNOT_ETRAIL) {
+ /* Extra data after message end. */
+ ret = kr_error(EMSGSIZE);
+ } else if (ret != KNOT_EOK) {
+ /* Malformed query. */
+ ret = kr_error(EPROTO);
+ } else {
+ ret = kr_ok();
+ }
+
+ return ret;
+}
+
+int worker_submit(struct session *session, knot_pkt_t *query)
+{
+ if (!session) {
+ assert(false);
+ return kr_error(EINVAL);
+ }
+
+ uv_handle_t *handle = session_get_handle(session);
+ bool OK = handle && handle->loop->data;
+ if (!OK) {
+ assert(false);
+ return kr_error(EINVAL);
+ }
+
+ struct worker_ctx *worker = handle->loop->data;
+
+ /* Parse packet */
+ int ret = parse_packet(query);
+
+ const bool is_query = (knot_wire_get_qr(query->wire) == 0);
+ const bool is_outgoing = session_flags(session)->outgoing;
+ /* Ignore badly formed queries. */
+ if (!query ||
+ (ret != kr_ok() && ret != kr_error(EMSGSIZE)) ||
+ (is_query == is_outgoing)) {
+ if (query && !is_outgoing) worker->stats.dropped += 1;
+ return kr_error(EILSEQ);
+ }
+
+ /* Start new task on listening sockets,
+ * or resume if this is subrequest */
+ struct qr_task *task = NULL;
+ struct sockaddr *addr = NULL;
+ if (!is_outgoing) { /* request from a client */
+ struct request_ctx *ctx = request_create(worker, handle,
+ session_get_peer(session),
+ knot_wire_get_id(query->wire));
+ if (!ctx) {
+ return kr_error(ENOMEM);
+ }
+
+ ret = request_start(ctx, query);
+ if (ret != 0) {
+ request_free(ctx);
+ return kr_error(ENOMEM);
+ }
+
+ task = qr_task_create(ctx);
+ if (!task) {
+ request_free(ctx);
+ return kr_error(ENOMEM);
+ }
+
+ if (handle->type == UV_TCP && qr_task_register(task, session)) {
+ return kr_error(ENOMEM);
+ }
+ } else if (query) { /* response from upstream */
+ task = session_tasklist_del_msgid(session, knot_wire_get_id(query->wire));
+ if (task == NULL) {
+ return kr_error(ENOENT);
+ }
+ assert(!session_flags(session)->closing);
+ addr = session_get_peer(session);
+ }
+ assert(uv_is_closing(session_get_handle(session)) == false);
+
+ /* Packet was successfully parsed.
+ * Task was created (found). */
+ session_touch(session);
+ /* Consume input and produce next message */
+ return qr_task_step(task, addr, query);
+}
+
+static int map_add_tcp_session(map_t *map, const struct sockaddr* addr,
+ struct session *session)
+{
+ assert(map && addr);
+ const char *key = tcpsess_key(addr);
+ assert(key);
+ assert(map_contains(map, key) == 0);
+ int ret = map_set(map, key, session);
+ return ret ? kr_error(EINVAL) : kr_ok();
+}
+
+static int map_del_tcp_session(map_t *map, const struct sockaddr* addr)
+{
+ assert(map && addr);
+ const char *key = tcpsess_key(addr);
+ assert(key);
+ int ret = map_del(map, key);
+ return ret ? kr_error(ENOENT) : kr_ok();
+}
+
+static struct session* map_find_tcp_session(map_t *map,
+ const struct sockaddr *addr)
+{
+ assert(map && addr);
+ const char *key = tcpsess_key(addr);
+ assert(key);
+ struct session* ret = map_get(map, key);
+ return ret;
+}
+
+int worker_add_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr* addr,
+ struct session *session)
+{
+#ifndef NDEBUG
+ assert(addr);
+ const char *key = tcpsess_key(addr);
+ assert(key);
+ assert(map_contains(&worker->tcp_connected, key) == 0);
+#endif
+ return map_add_tcp_session(&worker->tcp_connected, addr, session);
+}
+
+int worker_del_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ assert(addr && tcpsess_key(addr));
+ return map_del_tcp_session(&worker->tcp_connected, addr);
+}
+
+static struct session* worker_find_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ return map_find_tcp_session(&worker->tcp_connected, addr);
+}
+
+static int worker_add_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr,
+ struct session *session)
+{
+#ifndef NDEBUG
+ assert(addr);
+ const char *key = tcpsess_key(addr);
+ assert(key);
+ assert(map_contains(&worker->tcp_waiting, key) == 0);
+#endif
+ return map_add_tcp_session(&worker->tcp_waiting, addr, session);
+}
+
+int worker_del_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ assert(addr && tcpsess_key(addr));
+ return map_del_tcp_session(&worker->tcp_waiting, addr);
+}
+
+static struct session* worker_find_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ return map_find_tcp_session(&worker->tcp_waiting, addr);
+}
+
+int worker_end_tcp(struct session *session)
+{
+ if (!session) {
+ return kr_error(EINVAL);
+ }
+
+ session_timer_stop(session);
+
+ uv_handle_t *handle = session_get_handle(session);
+ struct worker_ctx *worker = handle->loop->data;
+ struct sockaddr *peer = session_get_peer(session);
+
+ worker_del_tcp_waiting(worker, peer);
+ worker_del_tcp_connected(worker, peer);
+ session_flags(session)->connected = false;
+
+ struct tls_client_ctx_t *tls_client_ctx = session_tls_get_client_ctx(session);
+ if (tls_client_ctx) {
+ /* Avoid gnutls_bye() call */
+ tls_set_hs_state(&tls_client_ctx->c, TLS_HS_NOT_STARTED);
+ }
+
+ struct tls_ctx_t *tls_ctx = session_tls_get_server_ctx(session);
+ if (tls_ctx) {
+ /* Avoid gnutls_bye() call */
+ tls_set_hs_state(&tls_ctx->c, TLS_HS_NOT_STARTED);
+ }
+
+ while (!session_waitinglist_is_empty(session)) {
+ struct qr_task *task = session_waitinglist_pop(session, false);
+ assert(task->refs > 1);
+ session_tasklist_del(session, task);
+ if (session_flags(session)->outgoing) {
+ if (task->ctx->req.options.FORWARD) {
+ /* We are in TCP_FORWARD mode.
+ * To prevent failing at kr_resolve_consume()
+ * qry.flags.TCP must be cleared.
+ * TODO - refactoring is needed. */
+ struct kr_request *req = &task->ctx->req;
+ struct kr_rplan *rplan = &req->rplan;
+ struct kr_query *qry = array_tail(rplan->pending);
+ qry->flags.TCP = false;
+ }
+ qr_task_step(task, NULL, NULL);
+ } else {
+ assert(task->ctx->source.session == session);
+ task->ctx->source.session = NULL;
+ }
+ worker_task_unref(task);
+ }
+ while (!session_tasklist_is_empty(session)) {
+ struct qr_task *task = session_tasklist_del_first(session, false);
+ if (session_flags(session)->outgoing) {
+ if (task->ctx->req.options.FORWARD) {
+ struct kr_request *req = &task->ctx->req;
+ struct kr_rplan *rplan = &req->rplan;
+ struct kr_query *qry = array_tail(rplan->pending);
+ qry->flags.TCP = false;
+ }
+ qr_task_step(task, NULL, NULL);
+ } else {
+ assert(task->ctx->source.session == session);
+ task->ctx->source.session = NULL;
+ }
+ worker_task_unref(task);
+ }
+ session_close(session);
+ return kr_ok();
+}
+
+struct qr_task *worker_resolve_start(struct worker_ctx *worker, knot_pkt_t *query, struct kr_qflags options)
+{
+ if (!worker || !query) {
+ assert(!EINVAL);
+ return NULL;
+ }
+
+
+ struct request_ctx *ctx = request_create(worker, NULL, NULL, worker->next_request_uid);
+ if (!ctx) {
+ return NULL;
+ }
+
+ /* Create task */
+ struct qr_task *task = qr_task_create(ctx);
+ if (!task) {
+ request_free(ctx);
+ return NULL;
+ }
+
+ /* Start task */
+ int ret = request_start(ctx, query);
+ if (ret != 0) {
+ /* task is attached to request context,
+ * so dereference (and deallocate) it first */
+ ctx->task = NULL;
+ qr_task_unref(task);
+ request_free(ctx);
+ return NULL;
+ }
+
+ worker->next_request_uid += 1;
+ if (worker->next_request_uid == 0) {
+ worker->next_request_uid = UINT16_MAX + 1;
+ }
+
+ /* Set options late, as qr_task_start() -> kr_resolve_begin() rewrite it. */
+ kr_qflags_set(&task->ctx->req.options, options);
+ return task;
+}
+
+int worker_resolve_exec(struct qr_task *task, knot_pkt_t *query)
+{
+ if (!task) {
+ return kr_error(EINVAL);
+ }
+ return qr_task_step(task, NULL, query);
+}
+
+int worker_task_numrefs(const struct qr_task *task)
+{
+ return task->refs;
+}
+
+struct kr_request *worker_task_request(struct qr_task *task)
+{
+ if (!task || !task->ctx) {
+ return NULL;
+ }
+
+ return &task->ctx->req;
+}
+
+int worker_task_finalize(struct qr_task *task, int state)
+{
+ return qr_task_finalize(task, state);
+}
+
+ int worker_task_step(struct qr_task *task, const struct sockaddr *packet_source,
+ knot_pkt_t *packet)
+ {
+ return qr_task_step(task, packet_source, packet);
+ }
+
+void worker_task_complete(struct qr_task *task)
+{
+ qr_task_complete(task);
+}
+
+void worker_task_ref(struct qr_task *task)
+{
+ qr_task_ref(task);
+}
+
+void worker_task_unref(struct qr_task *task)
+{
+ qr_task_unref(task);
+}
+
+void worker_task_timeout_inc(struct qr_task *task)
+{
+ task->timeouts += 1;
+}
+
+knot_pkt_t *worker_task_get_pktbuf(const struct qr_task *task)
+{
+ return task->pktbuf;
+}
+
+struct request_ctx *worker_task_get_request(struct qr_task *task)
+{
+ return task->ctx;
+}
+
+struct session *worker_request_get_source_session(struct request_ctx *ctx)
+{
+ return ctx->source.session;
+}
+
+void worker_request_set_source_session(struct request_ctx *ctx, struct session *session)
+{
+ ctx->source.session = session;
+}
+
+uint16_t worker_task_pkt_get_msgid(struct qr_task *task)
+{
+ knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
+ uint16_t msg_id = knot_wire_get_id(pktbuf->wire);
+ return msg_id;
+}
+
+void worker_task_pkt_set_msgid(struct qr_task *task, uint16_t msgid)
+{
+ knot_pkt_t *pktbuf = worker_task_get_pktbuf(task);
+ knot_wire_set_id(pktbuf->wire, msgid);
+ struct kr_query *q = task_get_last_pending_query(task);
+ q->id = msgid;
+}
+
+uint64_t worker_task_creation_time(struct qr_task *task)
+{
+ return task->creation_time;
+}
+
+void worker_task_subreq_finalize(struct qr_task *task)
+{
+ subreq_finalize(task, NULL, NULL);
+}
+
+bool worker_task_finished(struct qr_task *task)
+{
+ return task->finished;
+}
+/** Reserve worker buffers */
+static int worker_reserve(struct worker_ctx *worker, size_t ring_maxlen)
+{
+ array_init(worker->pool_mp);
+ if (array_reserve(worker->pool_mp, ring_maxlen)) {
+ return kr_error(ENOMEM);
+ }
+ memset(&worker->pkt_pool, 0, sizeof(worker->pkt_pool));
+ worker->pkt_pool.ctx = mp_new (4 * sizeof(knot_pkt_t));
+ worker->pkt_pool.alloc = (knot_mm_alloc_t) mp_alloc;
+ worker->subreq_out = trie_create(NULL);
+ worker->tcp_connected = map_make(NULL);
+ worker->tcp_waiting = map_make(NULL);
+ worker->tcp_pipeline_max = MAX_PIPELINED;
+ memset(&worker->stats, 0, sizeof(worker->stats));
+ return kr_ok();
+}
+
+static inline void reclaim_mp_freelist(mp_freelist_t *list)
+{
+ for (unsigned i = 0; i < list->len; ++i) {
+ struct mempool *e = list->at[i];
+ kr_asan_unpoison(e, sizeof(*e));
+ mp_delete(e);
+ }
+ array_clear(*list);
+}
+
+void worker_reclaim(struct worker_ctx *worker)
+{
+ reclaim_mp_freelist(&worker->pool_mp);
+ mp_delete(worker->pkt_pool.ctx);
+ worker->pkt_pool.ctx = NULL;
+ trie_free(worker->subreq_out);
+ worker->subreq_out = NULL;
+ map_clear(&worker->tcp_connected);
+ map_clear(&worker->tcp_waiting);
+ if (worker->z_import != NULL) {
+ zi_free(worker->z_import);
+ worker->z_import = NULL;
+ }
+}
+
+struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
+ int worker_id, int worker_count)
+{
+ /* Load bindings */
+ engine_lualib(engine, "modules", lib_modules);
+ engine_lualib(engine, "net", lib_net);
+ engine_lualib(engine, "cache", lib_cache);
+ engine_lualib(engine, "event", lib_event);
+ engine_lualib(engine, "worker", lib_worker);
+
+ /* Create main worker. */
+ struct worker_ctx *worker = mm_alloc(pool, sizeof(*worker));
+ if (!worker) {
+ return NULL;
+ }
+ memset(worker, 0, sizeof(*worker));
+ worker->id = worker_id;
+ worker->count = worker_count;
+ worker->engine = engine;
+ worker->next_request_uid = UINT16_MAX + 1;
+ worker_reserve(worker, MP_FREELIST_SIZE);
+ worker->out_addr4.sin_family = AF_UNSPEC;
+ worker->out_addr6.sin6_family = AF_UNSPEC;
+ /* Register worker in Lua thread */
+ lua_pushlightuserdata(engine->L, worker);
+ lua_setglobal(engine->L, "__worker");
+ lua_getglobal(engine->L, "worker");
+ lua_pushnumber(engine->L, worker_id);
+ lua_setfield(engine->L, -2, "id");
+ lua_pushnumber(engine->L, getpid());
+ lua_setfield(engine->L, -2, "pid");
+ lua_pushnumber(engine->L, worker_count);
+ lua_setfield(engine->L, -2, "count");
+ /* Register table for worker per-request variables */
+ lua_newtable(engine->L);
+ lua_setfield(engine->L, -2, "vars");
+ lua_getfield(engine->L, -1, "vars");
+ worker->vars_table_ref = luaL_ref(engine->L, LUA_REGISTRYINDEX);
+ lua_pop(engine->L, 1);
+ return worker;
+}
+
+#undef VERBOSE_MSG
diff --git a/daemon/worker.h b/daemon/worker.h
new file mode 100644
index 0000000..f56e10d
--- /dev/null
+++ b/daemon/worker.h
@@ -0,0 +1,171 @@
+/* Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "daemon/engine.h"
+#include "lib/generic/array.h"
+#include "lib/generic/map.h"
+
+
+/** Query resolution task (opaque). */
+struct qr_task;
+/** Worker state (opaque). */
+struct worker_ctx;
+/** Transport session (opaque). */
+struct session;
+/** Zone import context (opaque). */
+struct zone_import_ctx;
+
+/** Create and initialize the worker. */
+struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
+ int worker_id, int worker_count);
+
+/**
+ * Process an incoming packet (query from a client or answer from upstream).
+ *
+ * @param session session the where packet came from
+ * @param query the packet, or NULL on an error from the transport layer
+ * @return 0 or an error code
+ */
+int worker_submit(struct session *session, knot_pkt_t *query);
+
+/**
+ * End current DNS/TCP session, this disassociates pending tasks from this session
+ * which may be freely closed afterwards.
+ */
+int worker_end_tcp(struct session *session);
+
+/**
+ * Start query resolution with given query.
+ *
+ * @return task or NULL
+ */
+struct qr_task *worker_resolve_start(struct worker_ctx *worker, knot_pkt_t *query, struct kr_qflags options);
+
+/**
+ * Execute a request with given query.
+ * It expects task to be created with \fn worker_resolve_start.
+ *
+ * @return 0 or an error code
+ */
+int worker_resolve_exec(struct qr_task *task, knot_pkt_t *query);
+
+/** @return struct kr_request associated with opaque task */
+struct kr_request *worker_task_request(struct qr_task *task);
+
+/** Collect worker mempools */
+void worker_reclaim(struct worker_ctx *worker);
+
+int worker_task_step(struct qr_task *task, const struct sockaddr *packet_source,
+ knot_pkt_t *packet);
+
+int worker_task_numrefs(const struct qr_task *task);
+
+/** Finalize given task */
+int worker_task_finalize(struct qr_task *task, int state);
+
+void worker_task_complete(struct qr_task *task);
+
+void worker_task_ref(struct qr_task *task);
+
+void worker_task_unref(struct qr_task *task);
+
+void worker_task_timeout_inc(struct qr_task *task);
+
+int worker_add_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr *addr,
+ struct session *session);
+int worker_del_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr *addr);
+int worker_del_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr);
+knot_pkt_t *worker_task_get_pktbuf(const struct qr_task *task);
+
+struct request_ctx *worker_task_get_request(struct qr_task *task);
+
+struct session *worker_request_get_source_session(struct request_ctx *);
+
+void worker_request_set_source_session(struct request_ctx *, struct session *session);
+
+uint16_t worker_task_pkt_get_msgid(struct qr_task *task);
+void worker_task_pkt_set_msgid(struct qr_task *task, uint16_t msgid);
+uint64_t worker_task_creation_time(struct qr_task *task);
+void worker_task_subreq_finalize(struct qr_task *task);
+bool worker_task_finished(struct qr_task *task);
+
+/** @cond internal */
+
+/** Number of request within timeout window. */
+#define MAX_PENDING KR_NSREP_MAXADDR
+
+/** Maximum response time from TCP upstream, milliseconds */
+#define MAX_TCP_INACTIVITY (KR_RESOLVE_TIME_LIMIT + KR_CONN_RTT_MAX)
+
+#ifndef RECVMMSG_BATCH /* see check_bufsize() */
+#define RECVMMSG_BATCH 1
+#endif
+
+/** Freelist of available mempools. */
+typedef array_t(struct mempool *) mp_freelist_t;
+
+/** List of query resolution tasks. */
+typedef array_t(struct qr_task *) qr_tasklist_t;
+
+/** \details Worker state is meant to persist during the whole life of daemon. */
+struct worker_ctx {
+ struct engine *engine;
+ uv_loop_t *loop;
+ int id;
+ int count;
+ int vars_table_ref;
+ unsigned tcp_pipeline_max;
+
+ /** Addresses to bind for outgoing connections or AF_UNSPEC. */
+ struct sockaddr_in out_addr4;
+ struct sockaddr_in6 out_addr6;
+
+ uint8_t wire_buf[RECVMMSG_BATCH * KNOT_WIRE_MAX_PKTSIZE];
+
+ struct {
+ size_t concurrent;
+ size_t rconcurrent;
+ size_t udp;
+ size_t tcp;
+ size_t tls;
+ size_t ipv4;
+ size_t ipv6;
+ size_t queries;
+ size_t dropped;
+ size_t timeout;
+ } stats;
+
+ struct zone_import_ctx* z_import;
+ bool too_many_open;
+ size_t rconcurrent_highwatermark;
+ /** List of active outbound TCP sessions */
+ map_t tcp_connected;
+ /** List of outbound TCP sessions waiting to be accepted */
+ map_t tcp_waiting;
+ /** Subrequest leaders (struct qr_task*), indexed by qname+qtype+qclass. */
+ trie_t *subreq_out;
+ mp_freelist_t pool_mp;
+ knot_mm_t pkt_pool;
+ unsigned int next_request_uid;
+};
+
+/** @endcond */
+
diff --git a/daemon/zimport.c b/daemon/zimport.c
new file mode 100644
index 0000000..ede0dbf
--- /dev/null
+++ b/daemon/zimport.c
@@ -0,0 +1,818 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* Module is intended to import resource records from file into resolver's cache.
+ * File supposed to be a standard DNS zone file
+ * which contains text representations of resource records.
+ * For now only root zone import is supported.
+ *
+ * Import process consists of two stages.
+ * 1) Zone file parsing.
+ * 2) Import of parsed entries into the cache.
+ *
+ * These stages are implemented as two separate functions
+ * (zi_zone_import and zi_zone_process) which runs sequentially with the
+ * pause between them. This is done because resolver is a single-threaded
+ * application, so it can't process user's requests during the whole import
+ * process. Separation into two stages allows to reduce the
+ * continuous time interval when resolver can't serve user requests.
+ * Since root zone isn't large it is imported as single
+ * chunk. If it would be considered as necessary, import stage can be
+ * split into shorter stages.
+ *
+ * zi_zone_import() uses libzscanner to parse zone file.
+ * Parsed records are stored to internal storage from where they are imported to
+ * cache during the second stage.
+ *
+ * zi_zone_process() imports parsed resource records to cache.
+ * It imports rrset by creating request that will never be sent to upstream.
+ * After request creation resolver creates pseudo-answer which must contain
+ * all necessary data for validation. Then resolver process answer as if he had
+ * been received from network.
+ */
+
+#include <inttypes.h> /* PRIu64 */
+#include <stdlib.h>
+#include <uv.h>
+#include <ucw/mempool.h>
+#include <libknot/rrset.h>
+#include <libzscanner/scanner.h>
+
+#include "lib/utils.h"
+#include "lib/dnssec/ta.h"
+#include "daemon/worker.h"
+#include "daemon/zimport.h"
+#include "lib/generic/map.h"
+#include "lib/generic/array.h"
+
+#define VERBOSE_MSG(qry, ...) QRVERBOSE(qry, "zimport", __VA_ARGS__)
+
+/* Pause between parse and import stages, milliseconds.
+ * See comment in zi_zone_import() */
+#define ZONE_IMPORT_PAUSE 100
+
+typedef array_t(knot_rrset_t *) qr_rrsetlist_t;
+
+struct zone_import_ctx {
+ struct worker_ctx *worker;
+ bool started;
+ knot_dname_t *origin;
+ knot_rrset_t *ta;
+ knot_rrset_t *key;
+ uint64_t start_timestamp;
+ size_t rrset_idx;
+ uv_timer_t timer;
+ map_t rrset_indexed;
+ qr_rrsetlist_t rrset_sorted;
+ knot_mm_t pool;
+ zi_callback cb;
+ void *cb_param;
+};
+
+typedef struct zone_import_ctx zone_import_ctx_t;
+
+static int RRSET_IS_ALREADY_IMPORTED = 1;
+
+/** @internal Allocate zone import context.
+ * @return pointer to zone import context or NULL. */
+static zone_import_ctx_t *zi_ctx_alloc()
+{
+ return (zone_import_ctx_t *)malloc(sizeof(zone_import_ctx_t));
+}
+
+/** @internal Free zone import context. */
+static void zi_ctx_free(zone_import_ctx_t *z_import)
+{
+ if (z_import != NULL) {
+ free(z_import);
+ }
+}
+
+/** @internal Reset all fields in the zone import context to their default values.
+ * Flushes memory pool, but doesn't reallocate memory pool buffer.
+ * Doesn't affect timer handle, pointers to callback and callback parameter.
+ * @return 0 if success; -1 if failed. */
+static int zi_reset(struct zone_import_ctx *z_import, size_t rrset_sorted_list_size)
+{
+ mp_flush(z_import->pool.ctx);
+
+ z_import->started = false;
+ z_import->start_timestamp = 0;
+ z_import->rrset_idx = 0;
+ z_import->pool.alloc = (knot_mm_alloc_t) mp_alloc;
+ z_import->rrset_indexed = map_make(&z_import->pool);
+
+ array_init(z_import->rrset_sorted);
+
+ int ret = 0;
+ if (rrset_sorted_list_size) {
+ ret = array_reserve_mm(z_import->rrset_sorted, rrset_sorted_list_size,
+ kr_memreserve, &z_import->pool);
+ }
+
+ return ret;
+}
+
+/** @internal Close callback for timer handle.
+ * @note Actually frees zone import context. */
+static void on_timer_close(uv_handle_t *handle)
+{
+ zone_import_ctx_t *z_import = (zone_import_ctx_t *)handle->data;
+ if (z_import != NULL) {
+ zi_ctx_free(z_import);
+ }
+}
+
+zone_import_ctx_t *zi_allocate(struct worker_ctx *worker,
+ zi_callback cb, void *param)
+{
+ if (worker->loop == NULL) {
+ return NULL;
+ }
+ zone_import_ctx_t *z_import = zi_ctx_alloc();
+ if (!z_import) {
+ return NULL;
+ }
+ void *mp = mp_new (8192);
+ if (!mp) {
+ zi_ctx_free(z_import);
+ return NULL;
+ }
+ memset(z_import, 0, sizeof(*z_import));
+ z_import->pool.ctx = mp;
+ z_import->worker = worker;
+ int ret = zi_reset(z_import, 0);
+ if (ret < 0) {
+ mp_delete(mp);
+ zi_ctx_free(z_import);
+ return NULL;
+ }
+ uv_timer_init(z_import->worker->loop, &z_import->timer);
+ z_import->timer.data = z_import;
+ z_import->cb = cb;
+ z_import->cb_param = param;
+ return z_import;
+}
+
+void zi_free(zone_import_ctx_t *z_import)
+{
+ z_import->started = false;
+ z_import->start_timestamp = 0;
+ z_import->rrset_idx = 0;
+ mp_delete(z_import->pool.ctx);
+ z_import->pool.ctx = NULL;
+ z_import->pool.alloc = NULL;
+ z_import->worker = NULL;
+ z_import->cb = NULL;
+ z_import->cb_param = NULL;
+ uv_close((uv_handle_t *)&z_import->timer, on_timer_close);
+}
+
+/** @internal Mark rrset that has been already imported
+ * to avoid repeated import. */
+static inline void zi_rrset_mark_as_imported(knot_rrset_t *rr)
+{
+ rr->additional = (void *)&RRSET_IS_ALREADY_IMPORTED;
+}
+
+/** @internal Check if rrset is marked as "already imported".
+ * @return true if marked, false if isn't */
+static inline bool zi_rrset_is_marked_as_imported(knot_rrset_t *rr)
+{
+ return (rr->additional == &RRSET_IS_ALREADY_IMPORTED);
+}
+
+/** @internal Try to find rrset with given requisites amongst parsed rrsets
+ * and put it to given packet. If there is RRSIG which covers that rrset, it
+ * will be added as well. If rrset found and successfully put, it marked as
+ * "already imported" to avoid repeated import. The same is true for RRSIG.
+ * @return -1 if failed
+ * 0 if required record been actually put into the packet
+ * 1 if required record could not be found */
+static int zi_rrset_find_put(struct zone_import_ctx *z_import,
+ knot_pkt_t *pkt, const knot_dname_t *owner,
+ uint16_t class, uint16_t type, uint16_t additional)
+{
+ if (type != KNOT_RRTYPE_RRSIG) {
+ /* If required rrset isn't rrsig, these must be the same values */
+ additional = type;
+ }
+
+ char key[KR_RRKEY_LEN];
+ int err = kr_rrkey(key, class, owner, type, additional);
+ if (err <= 0) {
+ return -1;
+ }
+ knot_rrset_t *rr = map_get(&z_import->rrset_indexed, key);
+ if (!rr) {
+ return 1;
+ }
+ err = knot_pkt_put(pkt, 0, rr, 0);
+ if (err != KNOT_EOK) {
+ return -1;
+ }
+ zi_rrset_mark_as_imported(rr);
+
+ if (type != KNOT_RRTYPE_RRSIG) {
+ /* Try to find corresponding rrsig */
+ err = zi_rrset_find_put(z_import, pkt, owner,
+ class, KNOT_RRTYPE_RRSIG, type);
+ if (err < 0) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/** @internal Try to put given rrset to the given packet.
+ * If there is RRSIG which covers that rrset, it will be added as well.
+ * If rrset successfully put in the packet, it marked as
+ * "already imported" to avoid repeated import.
+ * The same is true for RRSIG.
+ * @return -1 if failed
+ * 0 if required record been actually put into the packet */
+static int zi_rrset_put(struct zone_import_ctx *z_import, knot_pkt_t *pkt,
+ knot_rrset_t *rr)
+{
+ assert(rr);
+ assert(rr->type != KNOT_RRTYPE_RRSIG);
+ int err = knot_pkt_put(pkt, 0, rr, 0);
+ if (err != KNOT_EOK) {
+ return -1;
+ }
+ zi_rrset_mark_as_imported(rr);
+ /* Try to find corresponding RRSIG */
+ err = zi_rrset_find_put(z_import, pkt, rr->owner, rr->rclass,
+ KNOT_RRTYPE_RRSIG, rr->type);
+ return (err < 0) ? err : 0;
+}
+
+/** @internal Try to put DS & NSEC* for rset->owner to given packet.
+ * @return -1 if failed;
+ * 0 if no errors occurred (it doesn't mean
+ * that records were actually added). */
+static int zi_put_delegation(zone_import_ctx_t *z_import, knot_pkt_t *pkt,
+ knot_rrset_t *rr)
+{
+ int err = zi_rrset_find_put(z_import, pkt, rr->owner,
+ rr->rclass, KNOT_RRTYPE_DS, 0);
+ if (err == 1) {
+ /* DS not found, maybe there are NSEC* */
+ err = zi_rrset_find_put(z_import, pkt, rr->owner,
+ rr->rclass, KNOT_RRTYPE_NSEC, 0);
+ if (err >= 0) {
+ err = zi_rrset_find_put(z_import, pkt, rr->owner,
+ rr->rclass, KNOT_RRTYPE_NSEC3, 0);
+ }
+ }
+ return err < 0 ? err : 0;
+}
+
+/** @internal Try to put A & AAAA records for rset->owner to given packet.
+ * @return -1 if failed;
+ * 0 if no errors occurred (it doesn't mean
+ * that records were actually added). */
+static int zi_put_glue(zone_import_ctx_t *z_import, knot_pkt_t *pkt,
+ knot_rrset_t *rr)
+{
+ int err = 0;
+ knot_rdata_t *rdata_i = rr->rrs.rdata;
+ for (uint16_t 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);
+ err = zi_rrset_find_put(z_import, pkt, ns_name,
+ rr->rclass, KNOT_RRTYPE_A, 0);
+ if (err < 0) {
+ break;
+ }
+
+ err = zi_rrset_find_put(z_import, pkt, ns_name,
+ rr->rclass, KNOT_RRTYPE_AAAA, 0);
+ if (err < 0) {
+ break;
+ }
+ }
+ return err < 0 ? err : 0;
+}
+
+/** @internal Create query. */
+static knot_pkt_t *zi_query_create(zone_import_ctx_t *z_import, knot_rrset_t *rr)
+{
+ knot_mm_t *pool = &z_import->pool;
+
+ uint32_t msgid = kr_rand_bytes(2);
+
+ knot_pkt_t *query = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool);
+ if (!query) {
+ return NULL;
+ }
+
+ knot_pkt_put_question(query, rr->owner, rr->rclass, rr->type);
+ knot_pkt_begin(query, KNOT_ANSWER);
+ knot_wire_set_rd(query->wire);
+ knot_wire_set_id(query->wire, msgid);
+ int err = knot_pkt_parse(query, 0);
+ if (err != KNOT_EOK) {
+ knot_pkt_free(query);
+ return NULL;
+ }
+
+ return query;
+}
+
+/** @internal Import given rrset to cache.
+ * @return -1 if failed; 0 if success */
+static int zi_rrset_import(zone_import_ctx_t *z_import, knot_rrset_t *rr)
+{
+ struct worker_ctx *worker = z_import->worker;
+
+ assert(worker);
+
+ /* Create "pseudo query" which asks for given rrset. */
+ knot_pkt_t *query = zi_query_create(z_import, rr);
+ if (!query) {
+ return -1;
+ }
+
+ knot_mm_t *pool = &z_import->pool;
+ uint8_t *dname = rr->owner;
+ uint16_t rrtype = rr->type;
+ uint16_t rrclass = rr->rclass;
+
+ /* Create "pseudo answer". */
+ knot_pkt_t *answer = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool);
+ if (!answer) {
+ knot_pkt_free(query);
+ return -1;
+ }
+ knot_pkt_put_question(answer, dname, rrclass, rrtype);
+ knot_pkt_begin(answer, KNOT_ANSWER);
+
+ struct kr_qflags options;
+ memset(&options, 0, sizeof(options));
+ options.DNSSEC_WANT = true;
+ options.NO_MINIMIZE = true;
+
+ /* This call creates internal structures which necessary for
+ * resolving - qr_task & request_ctx. */
+ struct qr_task *task = worker_resolve_start(worker, query, options);
+ if (!task) {
+ knot_pkt_free(query);
+ knot_pkt_free(answer);
+ return -1;
+ }
+
+ /* Push query to the request resolve plan.
+ * Actually query will never been sent to upstream. */
+ struct kr_request *request = worker_task_request(task);
+ struct kr_rplan *rplan = &request->rplan;
+ struct kr_query *qry = kr_rplan_push(rplan, NULL, dname, rrclass, rrtype);
+ int state = KR_STATE_FAIL;
+ bool origin_is_owner = knot_dname_is_equal(rr->owner, z_import->origin);
+ bool is_referral = (rrtype == KNOT_RRTYPE_NS && !origin_is_owner);
+ uint32_t msgid = knot_wire_get_id(query->wire);
+
+ qry->id = msgid;
+
+ /* Prepare zonecut. It must have all the necessary requisites for
+ * successful validation - matched zone name & keys & trust-anchors. */
+ kr_zonecut_init(&qry->zone_cut, z_import->origin, pool);
+ qry->zone_cut.key = z_import->key;
+ qry->zone_cut.trust_anchor = z_import->ta;
+
+ if (knot_pkt_init_response(request->answer, query) != 0) {
+ goto cleanup;
+ }
+
+ /* Since "pseudo" query asks for NS for subzone,
+ * "pseudo" answer must simulate referral. */
+ if (is_referral) {
+ knot_pkt_begin(answer, KNOT_AUTHORITY);
+ }
+
+ /* Put target rrset to ANSWER\AUTHORIRY as well as corresponding RRSIG */
+ int err = zi_rrset_put(z_import, answer, rr);
+ if (err != 0) {
+ goto cleanup;
+ }
+
+ if (!is_referral) {
+ knot_wire_set_aa(answer->wire);
+ } else {
+ /* Type is KNOT_RRTYPE_NS and owner is not equal to origin.
+ * It will be "referral" answer and must contain delegation. */
+ err = zi_put_delegation(z_import, answer, rr);
+ if (err < 0) {
+ goto cleanup;
+ }
+ }
+
+ knot_pkt_begin(answer, KNOT_ADDITIONAL);
+
+ if (rrtype == KNOT_RRTYPE_NS) {
+ /* Try to find glue addresses. */
+ err = zi_put_glue(z_import, answer, rr);
+ if (err < 0) {
+ goto cleanup;
+ }
+ }
+
+ knot_wire_set_id(answer->wire, msgid);
+ answer->parsed = answer->size;
+ err = knot_pkt_parse(answer, 0);
+ if (err != KNOT_EOK) {
+ goto cleanup;
+ }
+
+ /* Importing doesn't imply communication with upstream at all.
+ * "answer" contains pseudo-answer from upstream and must be successfully
+ * validated in CONSUME stage. If not, something gone wrong. */
+ state = kr_resolve_consume(request, NULL, answer);
+
+cleanup:
+
+ knot_pkt_free(query);
+ knot_pkt_free(answer);
+ worker_task_finalize(task, state);
+ return state == (is_referral ? KR_STATE_PRODUCE : KR_STATE_DONE) ? 0 : -1;
+}
+
+/** @internal Create element in qr_rrsetlist_t rrset_list for
+ * given node of map_t rrset_sorted. */
+static int zi_mapwalk_preprocess(const char *k, void *v, void *baton)
+{
+ zone_import_ctx_t *z_import = (zone_import_ctx_t *)baton;
+
+ int ret = array_push_mm(z_import->rrset_sorted, v, kr_memreserve, &z_import->pool);
+
+ return (ret < 0);
+}
+
+/** @internal Iterate over parsed rrsets and try to import each of them. */
+static void zi_zone_process(uv_timer_t* handle)
+{
+ zone_import_ctx_t *z_import = (zone_import_ctx_t *)handle->data;
+
+ assert(z_import->worker);
+
+ size_t failed = 0;
+ size_t ns_imported = 0;
+ size_t other_imported = 0;
+
+ /* At the moment import of root zone only is supported.
+ * Check the name of the parsed zone.
+ * TODO - implement importing of arbitrary zone. */
+ KR_DNAME_GET_STR(zone_name_str, z_import->origin);
+
+ if (strcmp(".", zone_name_str) != 0) {
+ kr_log_error("[zimport] unexpected zone name `%s` (root zone expected), fail\n",
+ zone_name_str);
+ failed = 1;
+ goto finish;
+ }
+
+ if (z_import->rrset_sorted.len <= 0) {
+ VERBOSE_MSG(NULL, "zone is empty\n");
+ goto finish;
+ }
+
+ /* TA have been found, zone is secured.
+ * DNSKEY must be somewhere amongst the imported records. Find it.
+ * TODO - For those zones that provenly do not have TA this step must be skipped. */
+ char key[KR_RRKEY_LEN];
+ int err = kr_rrkey(key, KNOT_CLASS_IN, z_import->origin,
+ KNOT_RRTYPE_DNSKEY, KNOT_RRTYPE_DNSKEY);
+ if (err <= 0) {
+ failed = 1;
+ goto finish;
+ }
+
+ knot_rrset_t *rr_key = map_get(&z_import->rrset_indexed, key);
+ if (!rr_key) {
+ /* DNSKEY MUST be here. If not found - fail. */
+ kr_log_error("[zimport] DNSKEY not found for `%s`, fail\n", zone_name_str);
+ failed = 1;
+ goto finish;
+ }
+ z_import->key = rr_key;
+
+ VERBOSE_MSG(NULL, "started: zone: '%s'\n", zone_name_str);
+
+ z_import->start_timestamp = kr_now();
+
+ /* Import DNSKEY at first step. If any validation problems will appear,
+ * cancel import of whole zone. */
+ KR_DNAME_GET_STR(kname_str, rr_key->owner);
+ KR_RRTYPE_GET_STR(ktype_str, rr_key->type);
+
+ VERBOSE_MSG(NULL, "importing: name: '%s' type: '%s'\n",
+ kname_str, ktype_str);
+
+ int res = zi_rrset_import(z_import, rr_key);
+ if (res != 0) {
+ VERBOSE_MSG(NULL, "import failed: qname: '%s' type: '%s'\n",
+ kname_str, ktype_str);
+ failed = 1;
+ goto finish;
+ }
+
+ /* Import all NS records */
+ for (size_t i = 0; i < z_import->rrset_sorted.len; ++i) {
+ knot_rrset_t *rr = z_import->rrset_sorted.at[i];
+
+ if (rr->type != KNOT_RRTYPE_NS) {
+ continue;
+ }
+
+ KR_DNAME_GET_STR(name_str, rr->owner);
+ KR_RRTYPE_GET_STR(type_str, rr->type);
+ VERBOSE_MSG(NULL, "importing: name: '%s' type: '%s'\n",
+ name_str, type_str);
+ int ret = zi_rrset_import(z_import, rr);
+ if (ret == 0) {
+ ++ns_imported;
+ } else {
+ VERBOSE_MSG(NULL, "import failed: name: '%s' type: '%s'\n",
+ name_str, type_str);
+ ++failed;
+ }
+ z_import->rrset_sorted.at[i] = NULL;
+ }
+
+ /* NS records have been imported as well as relative DS, NSEC* and glue.
+ * Now import what's left. */
+ for (size_t i = 0; i < z_import->rrset_sorted.len; ++i) {
+
+ knot_rrset_t *rr = z_import->rrset_sorted.at[i];
+ if (rr == NULL) {
+ continue;
+ }
+
+ if (zi_rrset_is_marked_as_imported(rr)) {
+ continue;
+ }
+
+ if (rr->type == KNOT_RRTYPE_DNSKEY || rr->type == KNOT_RRTYPE_RRSIG) {
+ continue;
+ }
+
+ KR_DNAME_GET_STR(name_str, rr->owner);
+ KR_RRTYPE_GET_STR(type_str, rr->type);
+ VERBOSE_MSG(NULL, "importing: name: '%s' type: '%s'\n",
+ name_str, type_str);
+ res = zi_rrset_import(z_import, rr);
+ if (res == 0) {
+ ++other_imported;
+ } else {
+ VERBOSE_MSG(NULL, "import failed: name: '%s' type: '%s'\n",
+ name_str, type_str);
+ ++failed;
+ }
+ }
+
+ uint64_t elapsed = kr_now() - z_import->start_timestamp;
+ elapsed = elapsed > UINT_MAX ? UINT_MAX : elapsed;
+
+ VERBOSE_MSG(NULL, "finished in %"PRIu64" ms; zone: `%s`; ns: %zd"
+ "; other: %zd; failed: %zd\n",
+ elapsed, zone_name_str, ns_imported, other_imported, failed);
+
+finish:
+
+ uv_timer_stop(&z_import->timer);
+ z_import->started = false;
+
+ int import_state = 0;
+
+ if (failed != 0) {
+ if (ns_imported == 0 && other_imported == 0) {
+ import_state = -1;
+ VERBOSE_MSG(NULL, "import failed; zone `%s` \n", zone_name_str);
+ } else {
+ import_state = 1;
+ }
+ } else {
+ import_state = 0;
+ }
+
+ if (z_import->cb != NULL) {
+ z_import->cb(import_state, z_import->cb_param);
+ }
+}
+
+/** @internal Store rrset that has been imported to zone import context memory pool.
+ * @return -1 if failed; 0 if success. */
+static int zi_record_store(zs_scanner_t *s)
+{
+ if (s->r_data_length > UINT16_MAX) {
+ /* Due to knot_rrset_add_rdata(..., const uint16_t size, ...); */
+ kr_log_error("[zscanner] line %"PRIu64": rdata is too long\n",
+ s->line_counter);
+ return -1;
+ }
+
+ if (knot_dname_size(s->r_owner) != strlen((const char *)(s->r_owner)) + 1) {
+ kr_log_error("[zscanner] line %"PRIu64
+ ": owner name contains zero byte, skip\n",
+ s->line_counter);
+ return 0;
+ }
+
+ zone_import_ctx_t *z_import = (zone_import_ctx_t *)s->process.data;
+
+ knot_rrset_t *new_rr = knot_rrset_new(s->r_owner, s->r_type, s->r_class,
+ s->r_ttl, &z_import->pool);
+ if (!new_rr) {
+ kr_log_error("[zscanner] line %"PRIu64": error creating rrset\n",
+ s->line_counter);
+ return -1;
+ }
+ int res = knot_rrset_add_rdata(new_rr, s->r_data, s->r_data_length,
+ &z_import->pool);
+ if (res != KNOT_EOK) {
+ kr_log_error("[zscanner] line %"PRIu64": error adding rdata to rrset\n",
+ s->line_counter);
+ return -1;
+ }
+
+ /* Records in zone file may not be grouped by name and RR type.
+ * Use map to create search key and
+ * avoid ineffective searches across all the imported records. */
+ char key[KR_RRKEY_LEN];
+ uint16_t additional_key_field = kr_rrset_type_maysig(new_rr);
+
+ res = kr_rrkey(key, new_rr->rclass, new_rr->owner, new_rr->type,
+ additional_key_field);
+ if (res <= 0) {
+ kr_log_error("[zscanner] line %"PRIu64": error constructing rrkey\n",
+ s->line_counter);
+ return -1;
+ }
+
+ knot_rrset_t *saved_rr = map_get(&z_import->rrset_indexed, key);
+ if (saved_rr) {
+ res = knot_rdataset_merge(&saved_rr->rrs, &new_rr->rrs,
+ &z_import->pool);
+ } else {
+ res = map_set(&z_import->rrset_indexed, key, new_rr);
+ }
+ if (res != 0) {
+ kr_log_error("[zscanner] line %"PRIu64": error saving parsed rrset\n",
+ s->line_counter);
+ return -1;
+ }
+
+ return 0;
+}
+
+/** @internal zscanner callback. */
+static int zi_state_parsing(zs_scanner_t *s)
+{
+ while (zs_parse_record(s) == 0) {
+ switch (s->state) {
+ case ZS_STATE_DATA:
+ if (zi_record_store(s) != 0) {
+ return -1;
+ }
+ zone_import_ctx_t *z_import = (zone_import_ctx_t *) s->process.data;
+ if (z_import->origin == 0) {
+ z_import->origin = knot_dname_copy(s->zone_origin,
+ &z_import->pool);
+ } else if (!knot_dname_is_equal(z_import->origin, s->zone_origin)) {
+ kr_log_error("[zscanner] line: %"PRIu64
+ ": zone origin changed unexpectedly\n",
+ s->line_counter);
+ return -1;
+ }
+ break;
+ case ZS_STATE_ERROR:
+ kr_log_error("[zscanner] line: %"PRIu64
+ ": parse error; code: %i ('%s')\n",
+ s->line_counter, s->error.code,
+ zs_strerror(s->error.code));
+ return -1;
+ case ZS_STATE_INCLUDE:
+ kr_log_error("[zscanner] line: %"PRIu64
+ ": INCLUDE is not supported\n",
+ s->line_counter);
+ return -1;
+ case ZS_STATE_EOF:
+ case ZS_STATE_STOP:
+ return (s->error.counter == 0) ? 0 : -1;
+ default:
+ kr_log_error("[zscanner] line: %"PRIu64
+ ": unexpected parse state: %i\n",
+ s->line_counter, s->state);
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+int zi_zone_import(struct zone_import_ctx *z_import,
+ const char *zone_file, const char *origin,
+ uint16_t rclass, uint32_t ttl)
+{
+ assert (z_import != NULL && "[zimport] empty <z_import> parameter");
+ assert (z_import->worker != NULL && "[zimport] invalid <z_import> parameter\n");
+ assert (zone_file != NULL && "[zimport] empty <zone_file> parameter\n");
+
+ zs_scanner_t *s = malloc(sizeof(zs_scanner_t));
+ if (s == NULL) {
+ kr_log_error("[zscanner] error creating instance of zone scanner (malloc() fails)\n");
+ return -1;
+ }
+
+ /* zs_init(), zs_set_input_file(), zs_set_processing() returns -1 in case of error,
+ * so don't print error code as it meaningless. */
+ int res = zs_init(s, origin, rclass, ttl);
+ if (res != 0) {
+ kr_log_error("[zscanner] error initializing zone scanner instance, error: %i (%s)\n",
+ s->error.code, zs_strerror(s->error.code));
+ free(s);
+ return -1;
+ }
+
+ res = zs_set_input_file(s, zone_file);
+ if (res != 0) {
+ kr_log_error("[zscanner] error opening zone file `%s`, error: %i (%s)\n",
+ zone_file, s->error.code, zs_strerror(s->error.code));
+ zs_deinit(s);
+ free(s);
+ return -1;
+ }
+
+ /* Don't set processing and error callbacks as we don't use automatic parsing.
+ * Parsing as well error processing will be performed in zi_state_parsing().
+ * Store pointer to zone import context for further use. */
+ if (zs_set_processing(s, NULL, NULL, (void *)z_import) != 0) {
+ kr_log_error("[zscanner] zs_set_processing() failed for zone file `%s`, "
+ "error: %i (%s)\n",
+ zone_file, s->error.code, zs_strerror(s->error.code));
+ zs_deinit(s);
+ free(s);
+ return -1;
+ }
+
+ uint64_t elapsed = 0;
+ int ret = zi_reset(z_import, 4096);
+ if (ret == 0) {
+ z_import->started = true;
+ z_import->start_timestamp = kr_now();
+ VERBOSE_MSG(NULL, "[zscanner] started; zone file `%s`\n",
+ zone_file);
+ ret = zi_state_parsing(s);
+ /* Try to find TA for worker->z_import.origin. */
+ map_t *trust_anchors = &z_import->worker->engine->resolver.trust_anchors;
+ knot_rrset_t *rr = kr_ta_get(trust_anchors, z_import->origin);
+ if (rr) {
+ z_import->ta = rr;
+ } else {
+ /* For now - fail.
+ * TODO - query DS and continue after answer had been obtained. */
+ KR_DNAME_GET_STR(zone_name_str, z_import->origin);
+ kr_log_error("[zimport] no TA found for `%s`, fail\n", zone_name_str);
+ ret = 1;
+ }
+ elapsed = kr_now() - z_import->start_timestamp;
+ elapsed = elapsed > UINT_MAX ? UINT_MAX : elapsed;
+ }
+ zs_deinit(s);
+ free(s);
+
+ if (ret != 0) {
+ kr_log_error("[zscanner] error parsing zone file `%s`\n", zone_file);
+ z_import->started = false;
+ return ret;
+ }
+
+ VERBOSE_MSG(NULL, "[zscanner] finished in %"PRIu64" ms; zone file `%s`\n",
+ elapsed, zone_file);
+ map_walk(&z_import->rrset_indexed, zi_mapwalk_preprocess, z_import);
+
+ /* Zone have been parsed already, so start the import. */
+ uv_timer_start(&z_import->timer, zi_zone_process,
+ ZONE_IMPORT_PAUSE, ZONE_IMPORT_PAUSE);
+
+ return 0;
+}
+
+bool zi_import_started(struct zone_import_ctx *z_import)
+{
+ return z_import ? z_import->started : false;
+}
diff --git a/daemon/zimport.h b/daemon/zimport.h
new file mode 100644
index 0000000..57b246e
--- /dev/null
+++ b/daemon/zimport.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+struct worker_ctx;
+/** Zone import context (opaque). */
+struct zone_import_ctx;
+
+/**
+ * Completion callback
+ *
+ * @param state -1 - fail
+ * 0 - success
+ * 1 - success, but there are non-critical errors
+ * @param pointer to user data
+ */
+typedef void (*zi_callback)(int state, void *param);
+
+/**
+ * Allocate and initialize zone import context.
+ *
+ * @param worker pointer to worker state
+ * @return NULL or pointer to zone import context.
+ */
+struct zone_import_ctx *zi_allocate(struct worker_ctx *worker,
+ zi_callback cb, void *param);
+
+/** Free zone import context. */
+void zi_free(struct zone_import_ctx *z_import);
+
+/**
+ * Import zone from file.
+ *
+ * @note only root zone import is supported; origin must be NULL or "."
+ * @param z_import pointer to zone import context
+ * @param zone_file zone file name
+ * @param origin default origin
+ * @param rclass default class
+ * @param ttl default ttl
+ * @return 0 or an error code
+ */
+int zi_zone_import(struct zone_import_ctx *z_import,
+ const char *zone_file, const char *origin,
+ uint16_t rclass, uint32_t ttl);
+
+/**
+ * Check if import already in process.
+ *
+ * @param z_import pointer to zone import context.
+ * @return true if import already in process; false otherwise.
+ */
+bool zi_import_started(struct zone_import_ctx *z_import);