summaryrefslogtreecommitdiffstats
path: root/daemon
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-08 20:37:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-08 20:37:50 +0000
commitc1f743ab2e4a7046d5500875a47d1f62c8624603 (patch)
tree709946d52f5f3bbaeb38be9e3f1d56d11f058237 /daemon
parentInitial commit. (diff)
downloadknot-resolver-913a96f00351e791fb56b1cd60642cb5fc7f7683.tar.xz
knot-resolver-913a96f00351e791fb56b1cd60642cb5fc7f7683.zip
Adding upstream version 5.7.1.upstream/5.7.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'daemon')
-rw-r--r--daemon/.packaging/centos/7/builddeps13
-rwxr-xr-xdaemon/.packaging/centos/7/pre-build.sh9
-rwxr-xr-xdaemon/.packaging/centos/7/pre-run.sh8
-rw-r--r--daemon/.packaging/centos/7/rundeps6
-rw-r--r--daemon/.packaging/centos/8/builddeps14
-rwxr-xr-xdaemon/.packaging/centos/8/pre-build.sh9
-rwxr-xr-xdaemon/.packaging/centos/8/pre-run.sh7
-rw-r--r--daemon/.packaging/centos/8/rundeps6
-rw-r--r--daemon/.packaging/debian/10/builddeps12
-rwxr-xr-xdaemon/.packaging/debian/10/pre-build.sh11
-rwxr-xr-xdaemon/.packaging/debian/10/pre-run.sh11
-rw-r--r--daemon/.packaging/debian/10/rundeps15
-rw-r--r--daemon/.packaging/debian/9/builddeps12
-rwxr-xr-xdaemon/.packaging/debian/9/pre-build.sh11
-rwxr-xr-xdaemon/.packaging/debian/9/pre-run.sh11
-rw-r--r--daemon/.packaging/debian/9/rundeps15
-rw-r--r--daemon/.packaging/fedora/31/builddeps14
-rwxr-xr-xdaemon/.packaging/fedora/31/pre-build.sh7
-rwxr-xr-xdaemon/.packaging/fedora/31/pre-run.sh6
-rw-r--r--daemon/.packaging/fedora/31/rundeps7
-rw-r--r--daemon/.packaging/fedora/32/builddeps14
-rwxr-xr-xdaemon/.packaging/fedora/32/pre-build.sh7
-rwxr-xr-xdaemon/.packaging/fedora/32/pre-run.sh6
-rw-r--r--daemon/.packaging/fedora/32/rundeps7
-rw-r--r--daemon/.packaging/leap/15.2/builddeps14
-rwxr-xr-xdaemon/.packaging/leap/15.2/pre-build.sh6
-rwxr-xr-xdaemon/.packaging/leap/15.2/pre-run.sh3
-rw-r--r--daemon/.packaging/leap/15.2/rundeps4
-rw-r--r--daemon/.packaging/leap/docker-image-name1
-rw-r--r--daemon/.packaging/test.config2
-rw-r--r--daemon/.packaging/ubuntu/16.04/builddeps16
-rwxr-xr-xdaemon/.packaging/ubuntu/16.04/pre-build.sh12
-rwxr-xr-xdaemon/.packaging/ubuntu/16.04/pre-run.sh12
-rw-r--r--daemon/.packaging/ubuntu/16.04/rundeps15
-rw-r--r--daemon/.packaging/ubuntu/18.04/builddeps16
-rwxr-xr-xdaemon/.packaging/ubuntu/18.04/pre-build.sh12
-rwxr-xr-xdaemon/.packaging/ubuntu/18.04/pre-run.sh12
-rw-r--r--daemon/.packaging/ubuntu/18.04/rundeps15
-rw-r--r--daemon/.packaging/ubuntu/20.04/builddeps16
-rwxr-xr-xdaemon/.packaging/ubuntu/20.04/pre-build.sh12
-rwxr-xr-xdaemon/.packaging/ubuntu/20.04/pre-run.sh12
-rw-r--r--daemon/.packaging/ubuntu/20.04/rundeps15
-rw-r--r--daemon/bindings/api.h12
-rw-r--r--daemon/bindings/cache.c382
-rw-r--r--daemon/bindings/cache.rst338
-rw-r--r--daemon/bindings/event.c209
-rw-r--r--daemon/bindings/event.rst139
-rw-r--r--daemon/bindings/impl.c95
-rw-r--r--daemon/bindings/impl.h90
-rw-r--r--daemon/bindings/modules.c77
-rw-r--r--daemon/bindings/modules.rst43
-rw-r--r--daemon/bindings/net.c1260
-rw-r--r--daemon/bindings/net_client.rst34
-rw-r--r--daemon/bindings/net_dns_tweaks.rst35
-rw-r--r--daemon/bindings/net_server.rst225
-rw-r--r--daemon/bindings/net_tlssrv.rst188
-rw-r--r--daemon/bindings/net_xdpsrv.rst140
-rw-r--r--daemon/bindings/worker.c81
-rw-r--r--daemon/bindings/worker.rst35
-rw-r--r--daemon/cache.test/clear.test.lua215
-rw-r--r--daemon/cache.test/insert_ns.test.integr/deckard.yaml14
-rw-r--r--daemon/cache.test/insert_ns.test.integr/kresd_config.j289
-rw-r--r--daemon/cache.test/insert_ns.test.integr/nondelegated_auth.rpl59
-rw-r--r--daemon/cache.test/testroot.zone1257
-rw-r--r--daemon/cache.test/testroot.zone.unsigned216
-rw-r--r--daemon/engine.c896
-rw-r--r--daemon/engine.h84
-rw-r--r--daemon/ffimodule.c304
-rw-r--r--daemon/ffimodule.h36
-rw-r--r--daemon/http.c953
-rw-r--r--daemon/http.h85
-rw-r--r--daemon/io.c1151
-rw-r--r--daemon/io.h80
-rw-r--r--daemon/lua/controlsock.test.lua169
-rw-r--r--daemon/lua/distro-preconfig.lua.in19
-rw-r--r--daemon/lua/kluautil.lua94
-rw-r--r--daemon/lua/kres-gen-30.lua641
-rw-r--r--daemon/lua/kres-gen-31.lua650
-rw-r--r--daemon/lua/kres-gen-32.lua651
-rwxr-xr-xdaemon/lua/kres-gen.sh353
-rw-r--r--daemon/lua/kres.lua1143
-rw-r--r--daemon/lua/krprint.lua340
-rw-r--r--daemon/lua/krprint.test.lua292
-rw-r--r--daemon/lua/log.test.lua42
-rw-r--r--daemon/lua/map.test.integr/deckard.yaml38
-rw-r--r--daemon/lua/map.test.integr/kresd_config.j2193
-rw-r--r--daemon/lua/map.test.integr/query-while-map-is-running.rpl312
-rw-r--r--daemon/lua/meson.build118
-rw-r--r--daemon/lua/postconfig.lua70
-rw-r--r--daemon/lua/sandbox.lua.in833
-rw-r--r--daemon/lua/trust_anchors.lua.in532
-rw-r--r--daemon/lua/trust_anchors.rst123
-rw-r--r--daemon/lua/trust_anchors.test/bootstrap.test.lua112
-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.sh3
-rw-r--r--daemon/lua/trust_anchors.test/root.keys1
-rw-r--r--daemon/lua/trust_anchors.test/ta.test.lua85
-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.lua236
-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.tmpl4
-rwxr-xr-xdaemon/lua/trust_anchors.test/x509/gen.sh13
-rw-r--r--daemon/lua/trust_anchors.test/x509/server-key.pem182
-rw-r--r--daemon/lua/trust_anchors.test/x509/server.pem27
-rw-r--r--daemon/lua/trust_anchors.test/x509/server.tmpl7
-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.tmpl4
-rw-r--r--daemon/lua/zonefile.lua93
-rw-r--r--daemon/main.c613
-rw-r--r--daemon/meson.build68
-rw-r--r--daemon/network.c928
-rw-r--r--daemon/network.h162
-rw-r--r--daemon/proxyv2.c290
-rw-r--r--daemon/proxyv2.h50
-rw-r--r--daemon/proxyv2.test/deckard.yaml25
-rw-r--r--daemon/proxyv2.test/dnsdist_config.j211
-rw-r--r--daemon/proxyv2.test/kresd_config.j263
-rw-r--r--daemon/proxyv2.test/proxyv2_valid.rpl72
-rw-r--r--daemon/scripting.rst398
-rw-r--r--daemon/session.c850
-rw-r--r--daemon/session.h166
-rw-r--r--daemon/tls.c1215
-rw-r--r--daemon/tls.h235
-rw-r--r--daemon/tls_ephemeral_credentials.c237
-rw-r--r--daemon/tls_session_ticket-srv.c245
-rw-r--r--daemon/udp_queue.c146
-rw-r--r--daemon/udp_queue.h16
-rw-r--r--daemon/worker.c2252
-rw-r--r--daemon/worker.h195
-rw-r--r--daemon/zimport.c741
-rw-r--r--daemon/zimport.h48
-rw-r--r--daemon/zimport.test/tz-rfc-a1-bad.zone14
-rw-r--r--daemon/zimport.test/tz-rfc-a1.zone14
-rw-r--r--daemon/zimport.test/tz-rfc-a2.zone35
-rw-r--r--daemon/zimport.test/tz-rfc-a3.zone31
-rw-r--r--daemon/zimport.test/tz-rfc-a4.zone37
-rw-r--r--daemon/zimport.test/tz-rfc-a5.zone48
-rw-r--r--daemon/zimport.test/zimport.test.lua47
151 files changed, 26493 insertions, 0 deletions
diff --git a/daemon/.packaging/centos/7/builddeps b/daemon/.packaging/centos/7/builddeps
new file mode 100644
index 0000000..3247738
--- /dev/null
+++ b/daemon/.packaging/centos/7/builddeps
@@ -0,0 +1,13 @@
+gcc
+gcc-c++
+gnutls
+knot-libs
+knot-devel
+libcmocka-devel
+libedit-devel
+libcap-ng
+libuv-devel
+lmdb-devel
+luajit-devel
+meson
+systemd-devel
diff --git a/daemon/.packaging/centos/7/pre-build.sh b/daemon/.packaging/centos/7/pre-build.sh
new file mode 100755
index 0000000..d3a9503
--- /dev/null
+++ b/daemon/.packaging/centos/7/pre-build.sh
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+yum update -y
+yum install -y wget epel-release
+
+# add build repository
+cd /etc/yum.repos.d/
+wget https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/CentOS_7_EPEL/home:CZ-NIC:knot-resolver-build.repo
+
diff --git a/daemon/.packaging/centos/7/pre-run.sh b/daemon/.packaging/centos/7/pre-run.sh
new file mode 100755
index 0000000..ee15ec7
--- /dev/null
+++ b/daemon/.packaging/centos/7/pre-run.sh
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+yum update -y
+yum install -y wget epel-release
+
+# add build repository
+cd /etc/yum.repos.d/
+wget https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/CentOS_7_EPEL/home:CZ-NIC:knot-resolver-latest.repo
diff --git a/daemon/.packaging/centos/7/rundeps b/daemon/.packaging/centos/7/rundeps
new file mode 100644
index 0000000..648501e
--- /dev/null
+++ b/daemon/.packaging/centos/7/rundeps
@@ -0,0 +1,6 @@
+knot-libs
+libedit
+libuv
+luajit
+lua-basexx
+lua-http
diff --git a/daemon/.packaging/centos/8/builddeps b/daemon/.packaging/centos/8/builddeps
new file mode 100644
index 0000000..984fa0b
--- /dev/null
+++ b/daemon/.packaging/centos/8/builddeps
@@ -0,0 +1,14 @@
+gcc
+gcc-c++
+meson
+"pkgconfig(cmocka)"
+"pkgconfig(gnutls)"
+"pkgconfig(libedit)"
+"pkgconfig(libknot)"
+"pkgconfig(libzscanner)"
+"pkgconfig(libdnssec)"
+"pkgconfig(libsystemd)"
+"pkgconfig(libcap-ng)"
+"pkgconfig(libuv)"
+"pkgconfig(lmdb)"
+"pkgconfig(luajit)"
diff --git a/daemon/.packaging/centos/8/pre-build.sh b/daemon/.packaging/centos/8/pre-build.sh
new file mode 100755
index 0000000..31398f8
--- /dev/null
+++ b/daemon/.packaging/centos/8/pre-build.sh
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnf install -y wget 'dnf-command(config-manager)' epel-release centos-release
+
+dnf config-manager --enable PowerTools
+dnf config-manager --enable Devel
+dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/CentOS_8_EPEL/home:CZ-NIC:knot-resolver-build.repo
+dnf install -y knot
+dnf upgrade -y
diff --git a/daemon/.packaging/centos/8/pre-run.sh b/daemon/.packaging/centos/8/pre-run.sh
new file mode 100755
index 0000000..94f8eb0
--- /dev/null
+++ b/daemon/.packaging/centos/8/pre-run.sh
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnf install -y wget 'dnf-command(config-manager)' epel-release centos-release
+
+dnf config-manager --enable PowerTools
+dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/CentOS_8_EPEL/home:CZ-NIC:knot-resolver-latest.repo
+dnf upgrade -y
diff --git a/daemon/.packaging/centos/8/rundeps b/daemon/.packaging/centos/8/rundeps
new file mode 100644
index 0000000..e3779ec
--- /dev/null
+++ b/daemon/.packaging/centos/8/rundeps
@@ -0,0 +1,6 @@
+libedit
+knot-libs
+libuv
+luajit
+lua5.1-basexx
+lua5.1-http
diff --git a/daemon/.packaging/debian/10/builddeps b/daemon/.packaging/debian/10/builddeps
new file mode 100644
index 0000000..604993c
--- /dev/null
+++ b/daemon/.packaging/debian/10/builddeps
@@ -0,0 +1,12 @@
+debhelper
+libcmocka-dev
+libedit-dev
+libgnutls28-dev
+libknot-dev
+liblmdb-dev
+luajit-5.1-dev
+libsystemd-dev
+libuv1-dev
+luajit
+pkg-config
+meson
diff --git a/daemon/.packaging/debian/10/pre-build.sh b/daemon/.packaging/debian/10/pre-build.sh
new file mode 100755
index 0000000..dc3b801
--- /dev/null
+++ b/daemon/.packaging/debian/10/pre-build.sh
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add debian build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/Debian_10/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Debian_10/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/debian/10/pre-run.sh b/daemon/.packaging/debian/10/pre-run.sh
new file mode 100755
index 0000000..3a3906a
--- /dev/null
+++ b/daemon/.packaging/debian/10/pre-run.sh
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/Debian_10/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Debian_10/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/debian/10/rundeps b/daemon/.packaging/debian/10/rundeps
new file mode 100644
index 0000000..a0f40c1
--- /dev/null
+++ b/daemon/.packaging/debian/10/rundeps
@@ -0,0 +1,15 @@
+adduser
+dns-root-data
+systemd
+libc6
+libdnssec7
+libedit2
+libgcc1
+libgnutls30
+libknot10
+liblmdb0
+libluajit-5.1-2
+libstdc++6
+libsystemd0
+libuv1
+libzscanner3
diff --git a/daemon/.packaging/debian/9/builddeps b/daemon/.packaging/debian/9/builddeps
new file mode 100644
index 0000000..604993c
--- /dev/null
+++ b/daemon/.packaging/debian/9/builddeps
@@ -0,0 +1,12 @@
+debhelper
+libcmocka-dev
+libedit-dev
+libgnutls28-dev
+libknot-dev
+liblmdb-dev
+luajit-5.1-dev
+libsystemd-dev
+libuv1-dev
+luajit
+pkg-config
+meson
diff --git a/daemon/.packaging/debian/9/pre-build.sh b/daemon/.packaging/debian/9/pre-build.sh
new file mode 100755
index 0000000..953025f
--- /dev/null
+++ b/daemon/.packaging/debian/9/pre-build.sh
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add debian build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/Debian_9.0/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Debian_9.0/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/debian/9/pre-run.sh b/daemon/.packaging/debian/9/pre-run.sh
new file mode 100755
index 0000000..fa8d377
--- /dev/null
+++ b/daemon/.packaging/debian/9/pre-run.sh
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/Debian_9.0/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Debian_9.0/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/debian/9/rundeps b/daemon/.packaging/debian/9/rundeps
new file mode 100644
index 0000000..a0f40c1
--- /dev/null
+++ b/daemon/.packaging/debian/9/rundeps
@@ -0,0 +1,15 @@
+adduser
+dns-root-data
+systemd
+libc6
+libdnssec7
+libedit2
+libgcc1
+libgnutls30
+libknot10
+liblmdb0
+libluajit-5.1-2
+libstdc++6
+libsystemd0
+libuv1
+libzscanner3
diff --git a/daemon/.packaging/fedora/31/builddeps b/daemon/.packaging/fedora/31/builddeps
new file mode 100644
index 0000000..984fa0b
--- /dev/null
+++ b/daemon/.packaging/fedora/31/builddeps
@@ -0,0 +1,14 @@
+gcc
+gcc-c++
+meson
+"pkgconfig(cmocka)"
+"pkgconfig(gnutls)"
+"pkgconfig(libedit)"
+"pkgconfig(libknot)"
+"pkgconfig(libzscanner)"
+"pkgconfig(libdnssec)"
+"pkgconfig(libsystemd)"
+"pkgconfig(libcap-ng)"
+"pkgconfig(libuv)"
+"pkgconfig(lmdb)"
+"pkgconfig(luajit)"
diff --git a/daemon/.packaging/fedora/31/pre-build.sh b/daemon/.packaging/fedora/31/pre-build.sh
new file mode 100755
index 0000000..7e279da
--- /dev/null
+++ b/daemon/.packaging/fedora/31/pre-build.sh
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnf install -y wget
+
+dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Fedora_31/home:CZ-NIC:knot-resolver-build.repo
+dnf install -y knot
+dnf upgrade -y
diff --git a/daemon/.packaging/fedora/31/pre-run.sh b/daemon/.packaging/fedora/31/pre-run.sh
new file mode 100755
index 0000000..b84b42d
--- /dev/null
+++ b/daemon/.packaging/fedora/31/pre-run.sh
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnf install -y wget
+
+dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Fedora_31/home:CZ-NIC:knot-resolver-latest.repo
+dnf upgrade -y
diff --git a/daemon/.packaging/fedora/31/rundeps b/daemon/.packaging/fedora/31/rundeps
new file mode 100644
index 0000000..7517b6b
--- /dev/null
+++ b/daemon/.packaging/fedora/31/rundeps
@@ -0,0 +1,7 @@
+libedit
+knot-libs
+libuv
+luajit
+lua5.1-basexx
+lua5.1-psl
+lua5.1-http
diff --git a/daemon/.packaging/fedora/32/builddeps b/daemon/.packaging/fedora/32/builddeps
new file mode 100644
index 0000000..984fa0b
--- /dev/null
+++ b/daemon/.packaging/fedora/32/builddeps
@@ -0,0 +1,14 @@
+gcc
+gcc-c++
+meson
+"pkgconfig(cmocka)"
+"pkgconfig(gnutls)"
+"pkgconfig(libedit)"
+"pkgconfig(libknot)"
+"pkgconfig(libzscanner)"
+"pkgconfig(libdnssec)"
+"pkgconfig(libsystemd)"
+"pkgconfig(libcap-ng)"
+"pkgconfig(libuv)"
+"pkgconfig(lmdb)"
+"pkgconfig(luajit)"
diff --git a/daemon/.packaging/fedora/32/pre-build.sh b/daemon/.packaging/fedora/32/pre-build.sh
new file mode 100755
index 0000000..97caead
--- /dev/null
+++ b/daemon/.packaging/fedora/32/pre-build.sh
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnf install -y wget
+
+dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/Fedora_32/home:CZ-NIC:knot-resolver-build.repo
+dnf install -y knot
+dnf upgrade -y
diff --git a/daemon/.packaging/fedora/32/pre-run.sh b/daemon/.packaging/fedora/32/pre-run.sh
new file mode 100755
index 0000000..b224b7e
--- /dev/null
+++ b/daemon/.packaging/fedora/32/pre-run.sh
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+dnf install -y wget
+
+dnf config-manager --add-repo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/Fedora_32/home:CZ-NIC:knot-resolver-latest.repo
+dnf upgrade -y
diff --git a/daemon/.packaging/fedora/32/rundeps b/daemon/.packaging/fedora/32/rundeps
new file mode 100644
index 0000000..7517b6b
--- /dev/null
+++ b/daemon/.packaging/fedora/32/rundeps
@@ -0,0 +1,7 @@
+libedit
+knot-libs
+libuv
+luajit
+lua5.1-basexx
+lua5.1-psl
+lua5.1-http
diff --git a/daemon/.packaging/leap/15.2/builddeps b/daemon/.packaging/leap/15.2/builddeps
new file mode 100644
index 0000000..e568905
--- /dev/null
+++ b/daemon/.packaging/leap/15.2/builddeps
@@ -0,0 +1,14 @@
+gcc
+gcc-c++
+lmdb-devel
+meson
+"pkgconfig(cmocka)"
+"pkgconfig(gnutls)"
+"pkgconfig(libedit)"
+"pkgconfig(libknot)"
+"pkgconfig(libzscanner)"
+"pkgconfig(libdnssec)"
+"pkgconfig(libsystemd)"
+"pkgconfig(libcap-ng)"
+"pkgconfig(libuv)"
+"pkgconfig(luajit)"
diff --git a/daemon/.packaging/leap/15.2/pre-build.sh b/daemon/.packaging/leap/15.2/pre-build.sh
new file mode 100755
index 0000000..274931a
--- /dev/null
+++ b/daemon/.packaging/leap/15.2/pre-build.sh
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+zypper addrepo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/openSUSE_Leap_15.2/home:CZ-NIC:knot-resolver-build.repo
+zypper --no-gpg-checks refresh
+zypper install -y knot
+
diff --git a/daemon/.packaging/leap/15.2/pre-run.sh b/daemon/.packaging/leap/15.2/pre-run.sh
new file mode 100755
index 0000000..9b0b5da
--- /dev/null
+++ b/daemon/.packaging/leap/15.2/pre-run.sh
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+zypper addrepo https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/openSUSE_Leap_15.2/home:CZ-NIC:knot-resolver-latest.repo
+zypper --no-gpg-checks refresh
diff --git a/daemon/.packaging/leap/15.2/rundeps b/daemon/.packaging/leap/15.2/rundeps
new file mode 100644
index 0000000..3f601a0
--- /dev/null
+++ b/daemon/.packaging/leap/15.2/rundeps
@@ -0,0 +1,4 @@
+libedit0
+knot-libs
+libuv1
+libluajit-5_1-2
diff --git a/daemon/.packaging/leap/docker-image-name b/daemon/.packaging/leap/docker-image-name
new file mode 100644
index 0000000..388ed86
--- /dev/null
+++ b/daemon/.packaging/leap/docker-image-name
@@ -0,0 +1 @@
+opensuse/leap
diff --git a/daemon/.packaging/test.config b/daemon/.packaging/test.config
new file mode 100644
index 0000000..72ec48d
--- /dev/null
+++ b/daemon/.packaging/test.config
@@ -0,0 +1,2 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+quit()
diff --git a/daemon/.packaging/ubuntu/16.04/builddeps b/daemon/.packaging/ubuntu/16.04/builddeps
new file mode 100644
index 0000000..7b1d943
--- /dev/null
+++ b/daemon/.packaging/ubuntu/16.04/builddeps
@@ -0,0 +1,16 @@
+debhelper
+libcmocka-dev
+libedit-dev
+libgnutls28-dev
+libknot-dev
+liblmdb-dev
+libluajit-5.1-dev
+libsystemd-dev
+libuv1-dev
+luajit
+pkg-config
+meson
+doxygen
+python3-breathe
+python3-sphinx
+python3-sphinx-rtd-theme
diff --git a/daemon/.packaging/ubuntu/16.04/pre-build.sh b/daemon/.packaging/ubuntu/16.04/pre-build.sh
new file mode 100755
index 0000000..5af89ab
--- /dev/null
+++ b/daemon/.packaging/ubuntu/16.04/pre-build.sh
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/xUbuntu_16.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/xUbuntu_16.04/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/ubuntu/16.04/pre-run.sh b/daemon/.packaging/ubuntu/16.04/pre-run.sh
new file mode 100755
index 0000000..bb81453
--- /dev/null
+++ b/daemon/.packaging/ubuntu/16.04/pre-run.sh
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/xUbuntu_16.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/xUbuntu_16.04/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/ubuntu/16.04/rundeps b/daemon/.packaging/ubuntu/16.04/rundeps
new file mode 100644
index 0000000..a0f40c1
--- /dev/null
+++ b/daemon/.packaging/ubuntu/16.04/rundeps
@@ -0,0 +1,15 @@
+adduser
+dns-root-data
+systemd
+libc6
+libdnssec7
+libedit2
+libgcc1
+libgnutls30
+libknot10
+liblmdb0
+libluajit-5.1-2
+libstdc++6
+libsystemd0
+libuv1
+libzscanner3
diff --git a/daemon/.packaging/ubuntu/18.04/builddeps b/daemon/.packaging/ubuntu/18.04/builddeps
new file mode 100644
index 0000000..7b1d943
--- /dev/null
+++ b/daemon/.packaging/ubuntu/18.04/builddeps
@@ -0,0 +1,16 @@
+debhelper
+libcmocka-dev
+libedit-dev
+libgnutls28-dev
+libknot-dev
+liblmdb-dev
+libluajit-5.1-dev
+libsystemd-dev
+libuv1-dev
+luajit
+pkg-config
+meson
+doxygen
+python3-breathe
+python3-sphinx
+python3-sphinx-rtd-theme
diff --git a/daemon/.packaging/ubuntu/18.04/pre-build.sh b/daemon/.packaging/ubuntu/18.04/pre-build.sh
new file mode 100755
index 0000000..77551b8
--- /dev/null
+++ b/daemon/.packaging/ubuntu/18.04/pre-build.sh
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/xUbuntu_18.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/xUbuntu_18.04/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/ubuntu/18.04/pre-run.sh b/daemon/.packaging/ubuntu/18.04/pre-run.sh
new file mode 100755
index 0000000..71d2a32
--- /dev/null
+++ b/daemon/.packaging/ubuntu/18.04/pre-run.sh
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/xUbuntu_18.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/xUbuntu_18.04/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/ubuntu/18.04/rundeps b/daemon/.packaging/ubuntu/18.04/rundeps
new file mode 100644
index 0000000..a0f40c1
--- /dev/null
+++ b/daemon/.packaging/ubuntu/18.04/rundeps
@@ -0,0 +1,15 @@
+adduser
+dns-root-data
+systemd
+libc6
+libdnssec7
+libedit2
+libgcc1
+libgnutls30
+libknot10
+liblmdb0
+libluajit-5.1-2
+libstdc++6
+libsystemd0
+libuv1
+libzscanner3
diff --git a/daemon/.packaging/ubuntu/20.04/builddeps b/daemon/.packaging/ubuntu/20.04/builddeps
new file mode 100644
index 0000000..7b1d943
--- /dev/null
+++ b/daemon/.packaging/ubuntu/20.04/builddeps
@@ -0,0 +1,16 @@
+debhelper
+libcmocka-dev
+libedit-dev
+libgnutls28-dev
+libknot-dev
+liblmdb-dev
+libluajit-5.1-dev
+libsystemd-dev
+libuv1-dev
+luajit
+pkg-config
+meson
+doxygen
+python3-breathe
+python3-sphinx
+python3-sphinx-rtd-theme
diff --git a/daemon/.packaging/ubuntu/20.04/pre-build.sh b/daemon/.packaging/ubuntu/20.04/pre-build.sh
new file mode 100755
index 0000000..e55fba6
--- /dev/null
+++ b/daemon/.packaging/ubuntu/20.04/pre-build.sh
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-build/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-build.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-build/xUbuntu_20.04/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/ubuntu/20.04/pre-run.sh b/daemon/.packaging/ubuntu/20.04/pre-run.sh
new file mode 100755
index 0000000..75c32f8
--- /dev/null
+++ b/daemon/.packaging/ubuntu/20.04/pre-run.sh
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# add build repository
+apt-get update
+apt-get install -y wget gnupg apt-utils
+
+echo 'deb http://download.opensuse.org/repositories/home:/CZ-NIC:/knot-resolver-latest/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/home:CZ-NIC:knot-resolver-latest.list
+wget -nv https://download.opensuse.org/repositories/home:CZ-NIC:knot-resolver-latest/xUbuntu_20.04/Release.key -O Release.key
+apt-key add - < Release.key
+
+apt-get update
+apt-get upgrade -y
diff --git a/daemon/.packaging/ubuntu/20.04/rundeps b/daemon/.packaging/ubuntu/20.04/rundeps
new file mode 100644
index 0000000..a0f40c1
--- /dev/null
+++ b/daemon/.packaging/ubuntu/20.04/rundeps
@@ -0,0 +1,15 @@
+adduser
+dns-root-data
+systemd
+libc6
+libdnssec7
+libedit2
+libgcc1
+libgnutls30
+libknot10
+liblmdb0
+libluajit-5.1-2
+libstdc++6
+libsystemd0
+libuv1
+libzscanner3
diff --git a/daemon/bindings/api.h b/daemon/bindings/api.h
new file mode 100644
index 0000000..2b43385
--- /dev/null
+++ b/daemon/bindings/api.h
@@ -0,0 +1,12 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <lua.h>
+
+/** Make all the bindings accessible from the lua state,
+ * .i.e. define those lua tables. */
+void kr_bindings_register(lua_State *L);
+
diff --git a/daemon/bindings/cache.c b/daemon/bindings/cache.c
new file mode 100644
index 0000000..d42ff62
--- /dev/null
+++ b/daemon/bindings/cache.c
@@ -0,0 +1,382 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/bindings/impl.h"
+
+/** @internal return cache, or throw lua error if not open */
+static struct kr_cache * cache_assert_open(lua_State *L)
+{
+ struct kr_cache *cache = &the_worker->engine->resolver.cache;
+ if (kr_fails_assert(cache) || !kr_cache_is_open(cache))
+ lua_error_p(L, "no cache is open yet, use cache.open() or cache.size, etc.");
+ return cache;
+}
+
+/** Return available cached backends. */
+static int cache_backends(lua_State *L)
+{
+ struct engine *engine = the_worker->engine;
+
+ 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, &cache->stats);
+ 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))
+ lua_error_p(L, "cache.checkpoint() takes no parameters or a true value");
+
+ 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);
+#define add_stat(name) \
+ lua_pushinteger(L, (cache->stats.name)); \
+ lua_setfield(L, -2, #name)
+ add_stat(open);
+ add_stat(close);
+ add_stat(count);
+ cache->stats.count_entries = cache->api->count(cache->db, &cache->stats);
+ add_stat(count_entries);
+ add_stat(clear);
+ add_stat(commit);
+ add_stat(read);
+ add_stat(read_miss);
+ add_stat(write);
+ add_stat(remove);
+ add_stat(remove_miss);
+ add_stat(match);
+ add_stat(match_miss);
+ add_stat(read_leq);
+ add_stat(read_leq_miss);
+ /* usage_percent statistics special case - double */
+ cache->stats.usage_percent = cache->api->usage_percent(cache->db);
+ lua_pushnumber(L, cache->stats.usage_percent);
+ lua_setfield(L, -2, "usage_percent");
+#undef add_stat
+
+ 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) || n > 1)
+ lua_error_p(L, "expected 'max_ttl(number ttl)'");
+ uint32_t min = cache->ttl_min;
+ int64_t ttl = lua_tointeger(L, 1);
+ if (ttl < 1 || ttl < min || ttl > TTL_MAX_MAX) {
+ lua_error_p(L,
+ "max_ttl must be larger than minimum TTL, and in range <1, "
+ STR(TTL_MAX_MAX) ">'");
+ }
+ 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))
+ lua_error_p(L, "expected 'min_ttl(number ttl)'");
+ uint32_t max = cache->ttl_max;
+ int64_t ttl = lua_tointeger(L, 1);
+ if (ttl < 0 || ttl > max || ttl > TTL_MAX_MAX) {
+ lua_error_p(L,
+ "min_ttl must be smaller than maximum TTL, and in range <0, "
+ STR(TTL_MAX_MAX) ">'");
+ }
+ 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))
+ lua_error_p(L, "expected 'open(number max_size, string config = \"\")'");
+
+ /* Select cache storage backend */
+ struct engine *engine = the_worker->engine;
+
+ lua_Integer csize_lua = lua_tointeger(L, 1);
+ if (!(csize_lua >= 8192 && csize_lua < SIZE_MAX)) { /* min. is basically arbitrary */
+ lua_error_p(L, "invalid cache size specified, it must be in range <8192, "
+ STR(SIZE_MAX) ">");
+ }
+ 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)
+ lua_error_p(L, "unsupported cache backend");
+
+ /* 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];
+ get_workdir(cwd, sizeof(cwd));
+ return luaL_error(L, "can't open cache path '%s'; working directory '%s'; %s",
+ opts.path, cwd, kr_strerror(ret));
+ }
+ /* Let's check_health() every five seconds to avoid keeping old cache alive
+ * even in case of not having any work to do. */
+ ret = kr_cache_check_health(&engine->resolver.cache, 5000);
+ if (ret != 0) {
+ kr_log_error(CACHE, "periodic health check failed (ignored): %s\n",
+ kr_strerror(ret));
+ }
+
+ /* 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 kr_cache *cache = &the_worker->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
+
+/** 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);
+ lua_error_maybe(L, ret);
+
+ /* Clear reputation tables */
+ struct kr_context *ctx = &the_worker->engine->resolver;
+ lru_reset(ctx->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));
+ if (kr_fails_assert(!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))
+ lua_error_p(L, "expected 'cache.get(string key)'");
+
+ /* 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);
+ lua_error_maybe(L, ret);
+ /* 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)
+{
+ lua_error_maybe(L, ENOSYS);
+ return kr_error(ENOSYS); /* doesn't happen */
+}
+
+/** 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 kr_context *ctx = &the_worker->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))
+ lua_error_p(L, "expected 'cache.ns_tout(interval in ms)'");
+
+ lua_Integer interval_lua = lua_tointeger(L, 1);
+ if (!(interval_lua > 0 && interval_lua < UINT_MAX)) {
+ lua_error_p(L, "invalid interval specified, it must be in range > 0, < "
+ STR(UINT_MAX));
+ }
+
+ ctx->cache_rtt_tout_retry_interval = interval_lua;
+ lua_pushinteger(L, ctx->cache_rtt_tout_retry_interval);
+ return 1;
+}
+
+int kr_bindings_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 },
+ { "clear_everything", cache_clear_everything },
+ { "get", cache_get },
+ { "max_ttl", cache_max_ttl },
+ { "min_ttl", cache_min_ttl },
+ { "ns_tout", cache_ns_tout },
+ { NULL, NULL }
+ };
+
+ luaL_register(L, "cache", lib);
+ return 1;
+}
+
diff --git a/daemon/bindings/cache.rst b/daemon/bindings/cache.rst
new file mode 100644
index 0000000..36114d2
--- /dev/null
+++ b/daemon/bindings/cache.rst
@@ -0,0 +1,338 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+Cache
+=====
+
+Cache in Knot Resolver is stored on disk and also shared between
+:ref:`systemd-multiple-instances` so resolver doesn't lose the cached data on
+restart or crash.
+
+To improve performance even further the resolver implements so-called aggressive caching
+for DNSSEC-validated data (:rfc:`8198`), which improves performance and also protects
+against some types of Random Subdomain Attacks.
+
+
+.. _`cache_sizing`:
+
+Sizing
+------
+
+For personal and small office use-cases cache size around 100 MB is more than enough.
+
+For large deployments we recommend to run Knot Resolver on a dedicated machine,
+and to allocate 90% of machine's free memory for resolver's cache.
+
+.. note:: Choosing a cache size that can fit into RAM is important even if the
+ cache is stored on disk (default). Otherwise, the extra I/O caused by disk
+ access for missing pages can cause performance issues.
+
+For example, imagine you have a machine with 16 GB of memory.
+After machine restart you use command ``free -m`` to determine
+amount of free memory (without swap):
+
+.. code-block:: bash
+
+ $ free -m
+ total used free
+ Mem: 15907 979 14928
+
+Now you can configure cache size to be 90% of the free memory 14 928 MB, i.e. 13 453 MB:
+
+.. code-block:: lua
+
+ -- 90 % of free memory after machine restart
+ cache.size = 13453 * MB
+
+It is also possible to set the cache size based on the file system size. This is useful
+if you use a dedicated partition for cache (e.g. non-persistent tmpfs). It is recommended
+to leave some free space for special files, such as locks.:
+
+.. code-block:: lua
+
+ cache.size = cache.fssize() - 10*MB
+
+.. note:: The :ref:`garbage-collector` can be used to periodically trim the
+ cache. It is enabled and configured by default when running kresd with
+ systemd integration.
+
+.. _`cache_persistence`:
+
+Persistence
+-----------
+.. tip:: Using tmpfs for cache improves performance and reduces disk I/O.
+
+By default the cache is saved on a persistent storage device
+so the content of the cache is persisted during system reboot.
+This usually leads to smaller latency after restart etc.,
+however in certain situations a non-persistent cache storage might be preferred, e.g.:
+
+ - Resolver handles high volume of queries and I/O performance to disk is too low.
+ - Threat model includes attacker getting access to disk content in power-off state.
+ - Disk has limited number of writes (e.g. flash memory in routers).
+
+If non-persistent cache is desired configure cache directory to be on
+tmpfs_ filesystem, a temporary in-memory file storage.
+The cache content will be saved in memory, and thus have faster access
+and will be lost on power-off or reboot.
+
+
+.. note:: In most of the Unix-like systems ``/tmp`` and ``/var/run`` are
+ commonly mounted as tmpfs. While it is technically possible to move the
+ cache to an existing tmpfs filesystem, it is *not recommended*, since the
+ path to cache is configured in multiple places.
+
+Mounting the cache directory as tmpfs_ is the recommended approach. Make sure
+to use appropriate ``size=`` option and don't forget to adjust the size in the
+config file as well.
+
+.. code-block:: none
+
+ # /etc/fstab
+ tmpfs /var/cache/knot-resolver tmpfs rw,size=2G,uid=knot-resolver,gid=knot-resolver,nosuid,nodev,noexec,mode=0700 0 0
+
+.. code-block:: lua
+
+ -- /etc/knot-resolver/kresd.conf
+ cache.size = cache.fssize() - 10*MB
+
+.. _tmpfs: https://en.wikipedia.org/wiki/Tmpfs
+
+Configuration reference
+-----------------------
+
+.. 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``.
+
+ The URI ``lmdb://path`` 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.current_storage)
+
+.. function:: cache.backends()
+
+ :return: map of backends
+
+ .. note:: For now there is only one backend implementation, even though the APIs are ready for different (synchronous) 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.fssize()
+
+ :return: Partition size of cache storage.
+
+.. function:: cache.stats()
+
+ Return table with low-level statistics for internal cache operation and storage.
+ This counts each access to cache and does not directly map to individual
+ DNS queries or resource records.
+ For query-level statistics see :ref:`stats module <mod-stats>`.
+
+ Example:
+
+ .. code-block:: lua
+
+ > cache.stats()
+ [clear] => 0
+ [close] => 0
+ [commit] => 117
+ [count] => 2
+ [count_entries] => 6187
+ [match] => 21
+ [match_miss] => 2
+ [open] => 0
+ [read] => 4313
+ [read_leq] => 9
+ [read_leq_miss] => 4
+ [read_miss] => 1143
+ [remove] => 17
+ [remove_miss] => 0
+ [usage_percent] => 15.625
+ [write] => 189
+
+
+ Cache operation `read_leq` (*read less or equal*, i.e. range search) was requested 9 times,
+ and 4 out of 9 operations were finished with *cache miss*.
+ Cache contains 6187 internal entries which occupy 15.625 % cache size.
+
+
+.. function:: cache.max_ttl([ttl])
+
+ :param number ttl: maximum TTL in seconds (default: 1 day)
+
+ .. KR_CACHE_DEFAULT_TTL_MAX ^^
+
+ :return: current maximum TTL
+
+ Get or set upper TTL bound applied to all received records.
+
+ .. note:: The `ttl` value must be in range `(min_ttl, 2147483647)`.
+
+ .. 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 TTL in seconds (default: 5 seconds)
+
+ .. KR_CACHE_DEFAULT_TTL_MIN ^^
+
+ :return: current minimum TTL
+
+ Get or set lower TTL bound applied to all received records.
+ Forcing TTL higher than specified violates DNS standards, so use higher values with care.
+ TTL still won't be extended beyond expiration of the corresponding DNSSEC signature.
+
+ .. note:: The `ttl` value must be in range `<0, max_ttl)`.
+
+ .. 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] asynchronous 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.
diff --git a/daemon/bindings/event.c b/daemon/bindings/event.c
new file mode 100644
index 0000000..4cefa13
--- /dev/null
+++ b/daemon/bindings/event.c
@@ -0,0 +1,209 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/bindings/impl.h"
+
+#include <unistd.h>
+#include <uv.h>
+
+static void event_free(uv_timer_t *timer)
+{
+ lua_State *L = the_worker->engine->L;
+ int ref = (intptr_t) timer->data;
+ luaL_unref(L, LUA_REGISTRYINDEX, ref);
+ free(timer);
+}
+
+static void event_callback(uv_timer_t *timer)
+{
+ lua_State *L = the_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)
+{
+ lua_State *L = the_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)
+ lua_error_p(L, "out of memory");
+
+ /* 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);
+ lua_error_p(L, "couldn't start the event");
+ }
+
+ /* Save callback and timer in registry */
+ lua_newtable(L);
+ lua_pushvalue(L, 2);
+ lua_rawseti(L, -2, 1);
+ lua_pushpointer(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))
+ lua_error_p(L, "expected 'after(number timeout, function)'");
+
+ return event_sched(L, lua_tointeger(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_tointeger(L, 1) == 0
+ || !lua_isfunction(L, 2))
+ lua_error_p(L, "expected 'recurrent(number interval, function)'");
+
+ return event_sched(L, 0, lua_tointeger(L, 1));
+}
+
+static int event_cancel(lua_State *L)
+{
+ int n = lua_gettop(L);
+ if (n < 1 || !lua_isnumber(L, 1))
+ lua_error_p(L, "expected 'cancel(number event)'");
+
+ /* Fetch event if it exists */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1));
+ bool ok = lua_istable(L, -1);
+
+ /* Close the timer */
+ uv_handle_t **timer_pp = NULL;
+ if (ok) {
+ lua_rawgeti(L, -1, 2);
+ timer_pp = lua_touserdata(L, -1);
+ ok = timer_pp && *timer_pp;
+ /* That have been sufficient safety checks, hopefully. */
+ }
+ if (ok && !uv_is_closing(*timer_pp)) {
+ uv_close(*timer_pp, (uv_close_cb)event_free);
+ }
+ lua_pushboolean(L, ok);
+ 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))
+ lua_error_p(L, "expected 'reschedule(number event, number timeout)'");
+
+ /* Fetch event if it exists */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1));
+ bool ok = lua_istable(L, -1);
+
+ /* Reschedule the timer */
+ uv_handle_t **timer_pp = NULL;
+ if (ok) {
+ lua_rawgeti(L, -1, 2);
+ timer_pp = lua_touserdata(L, -1);
+ ok = timer_pp && *timer_pp;
+ /* That have been sufficient safety checks, hopefully. */
+ }
+ if (ok && !uv_is_closing(*timer_pp)) {
+ int ret = uv_timer_start((uv_timer_t *)*timer_pp,
+ event_callback, lua_tointeger(L, 2), 0);
+ if (ret != 0) {
+ uv_close(*timer_pp, (uv_close_cb)event_free);
+ ok = false;
+ }
+ }
+ lua_pushboolean(L, ok);
+ 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))
+ lua_error_p(L, "expected 'socket(number fd, function)'");
+
+ uv_poll_t *handle = malloc(sizeof(*handle));
+ if (!handle)
+ lua_error_p(L, "out of memory");
+
+ /* Start timer with the reference */
+ int sock = lua_tointeger(L, 1);
+ uv_loop_t *loop = uv_default_loop();
+ int ret = uv_poll_init(loop, handle, sock);
+ if (ret == 0)
+ ret = uv_poll_start(handle, UV_READABLE, event_fdcallback);
+ if (ret != 0) {
+ free(handle);
+ lua_error_p(L, "couldn't start event poller");
+ }
+
+ /* Save callback and timer in registry */
+ lua_newtable(L);
+ lua_pushvalue(L, 2);
+ lua_rawseti(L, -2, 1);
+ lua_pushpointer(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 kr_bindings_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 }
+ };
+
+ luaL_register(L, "event", lib);
+ return 1;
+}
+
diff --git a/daemon/bindings/event.rst b/daemon/bindings/event.rst
new file mode 100644
index 0000000..a96f299
--- /dev/null
+++ b/daemon/bindings/event.rst
@@ -0,0 +1,139 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+Timers and events reference
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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
+
+ Execute function immediately and then periodically after each ``interval``.
+
+ 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!')
+ -- Halve 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).
+
+Example:
+
+.. code-block:: lua
+
+ function async_print(testname, sleep)
+ log(testname .. ': system time before sleep' .. tostring(os.time())
+ worker.sleep(sleep) -- other coroutines continue execution now
+ log(testname .. ': system time AFTER sleep' .. tostring(os.time())
+ end
+
+ worker.coroutine(function() async_print('call #1', 5) end)
+ worker.coroutine(function() async_print('call #2', 3) end)
+
+Output from this example demonstrates that both calls to function ``async_print`` were executed asynchronously:
+
+
+.. code-block:: none
+
+ call #2: system time before sleep 1578065073
+ call #1: system time before sleep 1578065073
+ call #2: system time AFTER sleep 1578065076
+ call #1: system time AFTER sleep 1578065078
+
diff --git a/daemon/bindings/impl.c b/daemon/bindings/impl.c
new file mode 100644
index 0000000..8c48df8
--- /dev/null
+++ b/daemon/bindings/impl.c
@@ -0,0 +1,95 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <dirent.h>
+#include <lua.h>
+#include <lauxlib.h>
+#include <string.h>
+
+
+const char * lua_table_checkindices(lua_State *L, const char *keys[])
+{
+ /* Iterate over table at the top of the stack.
+ * http://www.lua.org/manual/5.1/manual.html#lua_next */
+ for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
+ lua_pop(L, 1); /* we don't need the value */
+ /* We need to copy the key, as _tostring() confuses _next().
+ * https://www.lua.org/manual/5.1/manual.html#lua_tolstring */
+ lua_pushvalue(L, -1);
+ const char *key = lua_tostring(L, -1);
+ if (!key)
+ return "<NON-STRING_INDEX>";
+ for (const char **k = keys; ; ++k) {
+ if (*k == NULL)
+ return key;
+ if (strcmp(*k, key) == 0)
+ break;
+ }
+ }
+ return NULL;
+}
+
+/** Return table listing filenames in a given directory (ls -A). */
+static int kluautil_list_dir(lua_State *L)
+{
+ lua_newtable(L); // empty table even on errors
+
+ const char *path = lua_tolstring(L, 1, NULL);
+ if (!path) return 1;
+ DIR *dir = opendir(path);
+ if (!dir) return 1;
+
+ struct dirent *entry;
+ int lua_i = 1;
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
+ lua_pushstring(L, entry->d_name);
+ lua_rawseti(L, -2, lua_i++);
+ }
+ }
+
+ closedir(dir);
+ return 1;
+}
+
+
+/* Each of these just creates the correspondingly named lua table of functions. */
+int kr_bindings_cache (lua_State *L); /* ./cache.c */
+int kr_bindings_event (lua_State *L); /* ./event.c */
+int kr_bindings_modules (lua_State *L); /* ./modules.c */
+int kr_bindings_net (lua_State *L); /* ./net.c */
+int kr_bindings_worker (lua_State *L); /* ./worker.c */
+
+void kr_bindings_register(lua_State *L)
+{
+ kr_bindings_cache(L);
+ kr_bindings_event(L);
+ kr_bindings_modules(L);
+ kr_bindings_net(L);
+ kr_bindings_worker(L);
+
+ /* Finally some lua utils *written in C*, not really a binding. */
+ lua_register(L, "kluautil_list_dir", kluautil_list_dir);
+}
+
+void lua_error_p(lua_State *L, const char *fmt, ...)
+{
+ /* Add a stack trace and throw the result as a lua error. */
+ luaL_traceback(L, L, "error occurred here (config filename:lineno is at the bottom, if config is involved):", 0);
+ /* Push formatted custom message, prepended with "ERROR: ". */
+ lua_pushliteral(L, "\nERROR: ");
+ {
+ va_list args;
+ va_start(args, fmt);
+ lua_pushvfstring(L, fmt, args);
+ va_end(args);
+ }
+ lua_concat(L, 3);
+ lua_error(L);
+ /* TODO: we might construct a little more friendly trace by using luaL_where().
+ * In particular, in case the error happens in a function that was called
+ * directly from a config file (the most common case), there isn't much need
+ * to format the trace in this heavy way. */
+}
+
diff --git a/daemon/bindings/impl.h b/daemon/bindings/impl.h
new file mode 100644
index 0000000..d522756
--- /dev/null
+++ b/daemon/bindings/impl.h
@@ -0,0 +1,90 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "daemon/engine.h"
+#include "daemon/worker.h" /* the_worker is often useful */
+
+#include <lua.h>
+#include <lauxlib.h>
+/* It may happen that include files are messed up and we're hitting a header
+ * e.g. from vanilla Lua. Even 5.1 won't work due to missing luaL_traceback() in <lauxlib.h>. */
+#if (LUA_VERSION_NUM) != 501 || !defined(LUA_LJDIR)
+ #error "Incorrect Lua version in #include <lua.h> - LuaJIT compatible with Lua 5.1 is required"
+#endif
+
+
+/** Useful to stringify macros into error strings. */
+#define STR(s) STRINGIFY_TOKEN(s)
+#define STRINGIFY_TOKEN(s) #s
+
+
+/** Check lua table at the top of the stack for allowed keys.
+ * \param keys NULL-terminated array of 0-terminated strings
+ * \return NULL if passed or the offending string (pushed on top of lua stack)
+ * \note Future work: if non-NULL is returned, there's extra stuff on the lua stack.
+ * \note Brute-force complexity: table length * summed length of keys.
+ */
+const char * lua_table_checkindices(lua_State *L, const char *keys[]);
+
+/** If the value at the top of the stack isn't a table, make it a single-element list. */
+static inline void lua_listify(lua_State *L)
+{
+ if (lua_istable(L, -1))
+ return;
+ lua_createtable(L, 1, 0);
+ lua_insert(L, lua_gettop(L) - 1); /* swap the top two stack elements */
+ lua_pushinteger(L, 1);
+ lua_insert(L, lua_gettop(L) - 1); /* swap the top two stack elements */
+ lua_settable(L, -3);
+}
+
+
+/** Throw a formatted lua error.
+ *
+ * The message will get prefixed by "ERROR: " and supplemented by stack trace.
+ * \return never! It calls lua_error().
+ *
+ * Example:
+ ERROR: not a valid pin_sha256: 'a1Z/3ek=', raw length 5 instead of 32
+ stack traceback:
+ [C]: in function 'tls_client'
+ /PathToPREFIX/lib/kdns_modules/policy.lua:175: in function 'TLS_FORWARD'
+ /PathToConfig.lua:46: in main chunk
+ */
+KR_PRINTF(2) KR_NORETURN KR_COLD
+void lua_error_p(lua_State *L, const char *fmt, ...);
+/** @internal Annotate for static checkers. */
+KR_NORETURN int lua_error(lua_State *L);
+
+/** Shortcut for common case. */
+static inline void lua_error_maybe(lua_State *L, int err)
+{
+ if (err) lua_error_p(L, "%s", kr_strerror(err));
+}
+
+static inline int execute_callback(lua_State *L, int argc)
+{
+ int ret = engine_pcall(L, argc);
+ if (ret != 0) {
+ kr_log_error(SYSTEM, "error: %s\n", lua_tostring(L, -1));
+ }
+ /* Clear the stack, there may be event a/o anything returned */
+ lua_settop(L, 0);
+ return ret;
+}
+
+/** Push a pointer as heavy/full userdata.
+ *
+ * It's useful as a replacement of lua_pushlightuserdata(),
+ * but note that it behaves differently in lua (converts to pointer-to-pointer).
+ */
+static inline void lua_pushpointer(lua_State *L, void *p)
+{
+ void **addr = lua_newuserdata(L, sizeof(void *));
+ kr_require(addr);
+ memcpy(addr, &p, sizeof(void *));
+}
+
diff --git a/daemon/bindings/modules.c b/daemon/bindings/modules.c
new file mode 100644
index 0000000..acae270
--- /dev/null
+++ b/daemon/bindings/modules.c
@@ -0,0 +1,77 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/bindings/impl.h"
+
+
+/** List loaded modules */
+static int mod_list(lua_State *L)
+{
+ const module_array_t * const modules = &the_worker->engine->modules;
+ lua_newtable(L);
+ for (unsigned i = 0; i < modules->len; ++i) {
+ struct kr_module *module = 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))
+ lua_error_p(L, "expected 'load(string name)'");
+ /* 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 */
+ int ret = engine_register(the_worker->engine, name, precedence, ref);
+ free(declaration);
+ if (ret != 0) {
+ if (ret == kr_error(EIDRM)) {
+ lua_error_p(L, "referenced module not found");
+ } else {
+ lua_error_maybe(L, ret);
+ }
+ }
+
+ 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))
+ lua_error_p(L, "expected 'unload(string name)'");
+ /* Unload engine module */
+ int ret = engine_unregister(the_worker->engine, lua_tostring(L, 1));
+ lua_error_maybe(L, ret);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+int kr_bindings_modules(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "list", mod_list },
+ { "load", mod_load },
+ { "unload", mod_unload },
+ { NULL, NULL }
+ };
+
+ luaL_register(L, "modules", lib);
+ return 1;
+}
+
diff --git a/daemon/bindings/modules.rst b/daemon/bindings/modules.rst
new file mode 100644
index 0000000..09df6ff
--- /dev/null
+++ b/daemon/bindings/modules.rst
@@ -0,0 +1,43 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+Modules
+=======
+
+Knot Resolver functionality consists of separate modules, which allow you
+to mix-and-match features you need without slowing down operation
+by features you do not use.
+
+This practically means that you need to load module before using features contained in it, for example:
+
+.. code-block:: lua
+
+ -- load module and make dnstap features available
+ modules.load('dnstap')
+ -- configure dnstap features
+ dnstap.config({
+ socket_path = "/tmp/dnstap.sock"
+ })
+
+Obviously ordering matters, so you have to load module first and configure it after it is loaded.
+
+Here is full reference manual for module configuration:
+
+
+.. function:: modules.list()
+
+ :return: List of loaded modules.
+
+.. function:: modules.load(name)
+
+ :param string name: Module name, e.g. "hints"
+ :return: ``true`` if modules was (or already is) loaded, error otherwise.
+
+ Load a module by name.
+
+.. function:: modules.unload(name)
+
+ :param string name: Module name, e.g. "detect_time_jump"
+ :return: ``true`` if modules was unloaded, error otherwise.
+
+ Unload a module by name. This is useful for unloading modules loaded by default, mainly for debugging purposes.
+
diff --git a/daemon/bindings/net.c b/daemon/bindings/net.c
new file mode 100644
index 0000000..f1fa6f3
--- /dev/null
+++ b/daemon/bindings/net.c
@@ -0,0 +1,1260 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/bindings/impl.h"
+
+#include "contrib/base64.h"
+#include "contrib/cleanup.h"
+#include "daemon/network.h"
+#include "daemon/tls.h"
+#include "lib/utils.h"
+
+#include <stdlib.h>
+
+#define PROXY_DATA_STRLEN (INET6_ADDRSTRLEN + 1 + 3 + 1)
+
+/** Table and next index on top of stack -> append entries for given endpoint_array_t. */
+static int net_list_add(const char *b_key, uint32_t key_len, trie_val_t *val, void *ext)
+{
+ endpoint_array_t *ep_array = *val;
+ lua_State *L = (lua_State *)ext;
+ lua_Integer i = lua_tointeger(L, -1);
+ for (int j = 0; j < ep_array->len; ++j) {
+ struct endpoint *ep = &ep_array->at[j];
+ lua_newtable(L); // connection tuple
+
+ if (ep->flags.kind) {
+ lua_pushstring(L, ep->flags.kind);
+ } else if (ep->flags.http && ep->flags.tls) {
+ lua_pushliteral(L, "doh2");
+ } else if (ep->flags.tls) {
+ lua_pushliteral(L, "tls");
+ } else if (ep->flags.xdp) {
+ lua_pushliteral(L, "xdp");
+ } else {
+ lua_pushliteral(L, "dns");
+ }
+ lua_setfield(L, -2, "kind");
+
+ lua_newtable(L); // "transport" table
+
+ switch (ep->family) {
+ case AF_INET:
+ lua_pushliteral(L, "inet4");
+ break;
+ case AF_INET6:
+ lua_pushliteral(L, "inet6");
+ break;
+ case AF_XDP:
+ lua_pushliteral(L, "inet4+inet6"); // both UDP ports at once
+ break;
+ case AF_UNIX:
+ lua_pushliteral(L, "unix");
+ break;
+ default:
+ kr_assert(false);
+ lua_pushliteral(L, "invalid");
+ }
+ lua_setfield(L, -2, "family");
+
+ const char *ip_str_const = network_endpoint_key_str((struct endpoint_key *) b_key);
+ kr_require(ip_str_const);
+ auto_free char *ip_str = strdup(ip_str_const);
+ kr_require(ip_str);
+ char *hm = strchr(ip_str, '#');
+ if (hm) /* Omit port */
+ *hm = '\0';
+ lua_pushstring(L, ip_str);
+
+ if (ep->family == AF_INET || ep->family == AF_INET6) {
+ lua_setfield(L, -2, "ip");
+ lua_pushboolean(L, ep->flags.freebind);
+ lua_setfield(L, -2, "freebind");
+ } else if (ep->family == AF_UNIX) {
+ lua_setfield(L, -2, "path");
+ } else if (ep->family == AF_XDP) {
+ lua_setfield(L, -2, "interface");
+ lua_pushinteger(L, ep->nic_queue);
+ lua_setfield(L, -2, "nic_queue");
+ }
+
+ if (ep->family != AF_UNIX) {
+ lua_pushinteger(L, ep->port);
+ lua_setfield(L, -2, "port");
+ }
+
+ if (ep->family == AF_UNIX) {
+ lua_pushliteral(L, "stream");
+ } else if (ep->flags.sock_type == SOCK_STREAM) {
+ lua_pushliteral(L, "tcp");
+ } else if (ep->flags.sock_type == SOCK_DGRAM) {
+ lua_pushliteral(L, "udp");
+ } else {
+ kr_assert(false);
+ lua_pushliteral(L, "invalid");
+ }
+ lua_setfield(L, -2, "protocol");
+
+ lua_setfield(L, -2, "transport");
+
+ lua_settable(L, -3);
+ i++;
+ lua_pushinteger(L, i);
+ }
+ return kr_ok();
+}
+
+/** List active endpoints. */
+static int net_list(lua_State *L)
+{
+ lua_newtable(L);
+ lua_pushinteger(L, 1);
+ trie_apply_with_key(the_worker->engine->net.endpoints, net_list_add, L);
+ lua_pop(L, 1);
+ return 1;
+}
+
+/** Listen on an address list represented by the top of lua stack.
+ * \note flags.kind ownership is not transferred, and flags.sock_type doesn't make sense
+ * \return success */
+static bool net_listen_addrs(lua_State *L, int port, endpoint_flags_t flags, int16_t nic_queue)
+{
+ if (kr_fails_assert(flags.xdp || nic_queue == -1))
+ return false;
+
+ /* 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 network *net = &the_worker->engine->net;
+ const bool is_unix = str[0] == '/';
+ int ret = 0;
+ if (!flags.kind && !flags.tls) { /* normal UDP or XDP */
+ flags.sock_type = SOCK_DGRAM;
+ ret = network_listen(net, str, port, nic_queue, flags);
+ }
+ if (!flags.kind && !flags.xdp && ret == 0) { /* common for TCP, DoT and DoH (v2) */
+ flags.sock_type = SOCK_STREAM;
+ ret = network_listen(net, str, port, nic_queue, flags);
+ }
+ if (flags.kind) {
+ flags.kind = strdup(flags.kind);
+ flags.sock_type = SOCK_STREAM; /* TODO: allow to override this? */
+ ret = network_listen(net, str, (is_unix ? 0 : port), nic_queue, flags);
+ }
+ if (ret == 0) return true; /* success */
+
+ if (is_unix) {
+ kr_log_error(NETWORK, "bind to '%s' (UNIX): %s\n",
+ str, kr_strerror(ret));
+ } else if (flags.xdp) {
+ const char *err_str = knot_strerror(ret);
+ if (ret == KNOT_ELIMIT) {
+ if ((strcmp(str, "::") == 0 || strcmp(str, "0.0.0.0") == 0)) {
+ err_str = "wildcard addresses not supported with XDP";
+ } else {
+ err_str = "address matched multiple network interfaces";
+ }
+ } else if (ret == kr_error(ENODEV)) {
+ err_str = "invalid address or interface name";
+ }
+ /* Notable OK strerror: KNOT_EPERM Operation not permitted */
+
+ if (nic_queue == -1) {
+ kr_log_error(NETWORK, "failed to initialize XDP for '%s@%d'"
+ " (nic_queue = <auto>): %s\n",
+ str, port, err_str);
+ } else {
+ kr_log_error(NETWORK, "failed to initialize XDP for '%s@%d'"
+ " (nic_queue = %d): %s\n",
+ str, port, nic_queue, err_str);
+ }
+
+ } else {
+ const char *stype = flags.sock_type == SOCK_DGRAM ? "UDP" : "TCP";
+ kr_log_error(NETWORK, "bind to '%s@%d' (%s): %s\n",
+ str, port, stype, kr_strerror(ret));
+ }
+ return false; /* failure */
+ }
+
+ /* Last case: table where all entries are added recursively. */
+ if (!lua_istable(L, -1))
+ lua_error_p(L, "bad type for address");
+ lua_pushnil(L);
+ while (lua_next(L, -2)) {
+ if (!net_listen_addrs(L, port, flags, nic_queue))
+ return false;
+ lua_pop(L, 1);
+ }
+ return true;
+}
+
+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) {
+ lua_error_p(L, "expected one to three arguments; usage:\n"
+ "net.listen(addresses, [port = " STR(KR_DNS_PORT)
+ ", flags = {tls = (port == " STR(KR_DNS_TLS_PORT) ")}])\n");
+ }
+
+ int port = KR_DNS_PORT;
+ if (n > 1) {
+ if (lua_isnumber(L, 2)) {
+ port = lua_tointeger(L, 2);
+ } else
+ if (!lua_isnil(L, 2)) {
+ lua_error_p(L, "wrong type of second parameter (port number)");
+ }
+ }
+
+ endpoint_flags_t flags = { 0 };
+ if (port == KR_DNS_TLS_PORT) {
+ flags.tls = true;
+ } else if (port == KR_DNS_DOH_PORT) {
+ flags.http = flags.tls = true;
+ }
+
+ int16_t nic_queue = -1;
+ if (n > 2 && !lua_isnil(L, 3)) {
+ if (!lua_istable(L, 3))
+ lua_error_p(L, "wrong type of third parameter (table expected)");
+ flags.tls = table_get_flag(L, 3, "tls", flags.tls);
+ flags.freebind = table_get_flag(L, 3, "freebind", false);
+
+ lua_getfield(L, 3, "kind");
+ const char *k = lua_tostring(L, -1);
+ if (k && strcasecmp(k, "dns") == 0) {
+ flags.tls = flags.http = false;
+ } else if (k && strcasecmp(k, "xdp") == 0) {
+ flags.tls = flags.http = false;
+ flags.xdp = true;
+ } else if (k && strcasecmp(k, "tls") == 0) {
+ flags.tls = true;
+ flags.http = false;
+ } else if (k && strcasecmp(k, "doh2") == 0) {
+ flags.tls = flags.http = true;
+ } else if (k) {
+ flags.kind = k;
+ if (strcasecmp(k, "doh") == 0) {
+ lua_error_p(L, "kind=\"doh\" was renamed to kind=\"doh_legacy\", switch to the new implementation with kind=\"doh2\" or update your config");
+ }
+ }
+
+ lua_getfield(L, 3, "nic_queue");
+ if (lua_isnumber(L, -1)) {
+ if (flags.xdp) {
+ nic_queue = lua_tointeger(L, -1);
+ } else {
+ lua_error_p(L, "nic_queue only supported with kind = 'xdp'");
+ }
+ } else if (!lua_isnil(L, -1)) {
+ lua_error_p(L, "wrong value of nic_queue (integer expected)");
+ }
+ }
+
+ /* Memory management of `kind` string is difficult due to longjmp etc.
+ * Pop will unreference the lua value, so we store it on C stack instead (!) */
+ const int kind_alen = flags.kind ? strlen(flags.kind) + 1 : 1 /* 0 length isn't C standard */;
+ char kind_buf[kind_alen];
+ if (flags.kind) {
+ memcpy(kind_buf, flags.kind, kind_alen);
+ flags.kind = kind_buf;
+ }
+
+ /* Now focus on the first argument. */
+ lua_settop(L, 1);
+ if (!net_listen_addrs(L, port, flags, nic_queue))
+ lua_error_p(L, "net.listen() failed to bind");
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+/** Prints the specified `data` into the specified `dst` buffer. */
+static char *proxy_data_to_string(int af, const struct net_proxy_data *data,
+ char *dst, size_t size)
+{
+ kr_assert(size >= PROXY_DATA_STRLEN);
+ const void *in_addr = (af == AF_INET)
+ ? (void *) &data->addr.ip4
+ : (void *) &data->addr.ip6;
+ char *cur = dst;
+
+ const char *ret = inet_ntop(af, in_addr, cur, size);
+ if (!ret)
+ return NULL;
+
+ cur += strlen(cur); /*< advance cursor to after the address */
+ *(cur++) = '/';
+ int masklen = snprintf(cur, 3 + 1, "%u", data->netmask);
+ cur[masklen] = '\0';
+ return dst;
+}
+
+/** Put all IP addresses from `trie` into the table at the top of the Lua stack.
+ * For each address, increment the integer at `i`. All addresses in `trie` must
+ * be from the specified `family`. */
+static void net_proxy_addr_put(lua_State *L, int family, trie_t *trie, int *i)
+{
+ char addrbuf[PROXY_DATA_STRLEN];
+ const char *addr;
+ trie_it_t *it;
+ for (it = trie_it_begin(trie); !trie_it_finished(it); trie_it_next(it)) {
+ lua_pushinteger(L, *i);
+ struct net_proxy_data *data = *trie_it_val(it);
+ addr = proxy_data_to_string(family, data,
+ addrbuf, sizeof(addrbuf));
+ lua_pushstring(L, addr);
+ lua_settable(L, -3);
+ *i += 1;
+ }
+ trie_it_free(it);
+}
+
+/** Allow PROXYv2 headers for IP address. */
+static int net_proxy_allowed(lua_State *L)
+{
+ struct network *net = &the_worker->engine->net;
+ int n = lua_gettop(L);
+ int i = 1;
+ const char *addr;
+
+ /* Return current state */
+ if (n == 0) {
+ lua_newtable(L);
+ i = 1;
+
+ if (net->proxy_all4) {
+ lua_pushinteger(L, i);
+ lua_pushstring(L, "0.0.0.0/0");
+ lua_settable(L, -3);
+ i += 1;
+ } else {
+ net_proxy_addr_put(L, AF_INET, net->proxy_addrs4, &i);
+ }
+
+ if (net->proxy_all6) {
+ lua_pushinteger(L, i);
+ lua_pushstring(L, "::/0");
+ lua_settable(L, -3);
+ i += 1;
+ } else {
+ net_proxy_addr_put(L, AF_INET6, net->proxy_addrs6, &i);
+ }
+
+ return 1;
+ }
+
+ if (n != 1)
+ lua_error_p(L, "net.proxy_allowed() takes one parameter (string or table)");
+
+ if (!lua_istable(L, 1) && !lua_isstring(L, 1))
+ lua_error_p(L, "net.proxy_allowed() argument must be string or table");
+
+ /* Reset allowed proxy addresses */
+ network_proxy_reset(net);
+
+ /* Add new proxy addresses */
+ if (lua_istable(L, 1)) {
+ for (i = 1; !lua_isnil(L, -1); i++) {
+ lua_pushinteger(L, i);
+ lua_gettable(L, 1);
+ if (lua_isnil(L, -1)) /* missing value - end iteration */
+ break;
+ if (!lua_isstring(L, -1))
+ lua_error_p(L, "net.proxy_allowed() argument may only contain strings");
+ addr = lua_tostring(L, -1);
+ int ret = network_proxy_allow(net, addr);
+ if (ret)
+ lua_error_p(L, "invalid argument");
+ }
+ } else if (lua_isstring(L, 1)) {
+ addr = lua_tostring(L, 1);
+ int ret = network_proxy_allow(net, addr);
+ if (ret)
+ lua_error_p(L, "invalid argument");
+ }
+
+ return 0;
+}
+
+/** Close endpoint. */
+static int net_close(lua_State *L)
+{
+ /* Check parameters */
+ const int n = lua_gettop(L);
+ bool ok = (n == 1 || n == 2) && lua_isstring(L, 1);
+ const char *addr = lua_tostring(L, 1);
+ int port;
+ if (ok && (n < 2 || lua_isnil(L, 2))) {
+ port = -1;
+ } else if (ok) {
+ ok = lua_isnumber(L, 2);
+ port = lua_tointeger(L, 2);
+ ok = ok && port >= 0 && port <= 65535;
+ }
+ if (!ok)
+ lua_error_p(L, "expected 'close(string addr, [number port])'");
+
+ int ret = network_close(&the_worker->engine->net, addr, port);
+ 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';
+ }
+
+ if (kr_sockaddr_link_local((struct sockaddr *) &iface.address)) {
+ /* Link-local IPv6: add %interface prefix */
+ auto_free char *str = NULL;
+ int ret = asprintf(&str, "%s%%%s", buf, iface.name);
+ kr_assert(ret > 0);
+ lua_pushstring(L, str);
+ } else {
+ lua_pushstring(L, buf);
+ }
+
+ lua_rawseti(L, -2, lua_objlen(L, -2) + 1);
+ lua_setfield(L, -2, "addr");
+
+ /* Hardware address. */
+ char *p = buf;
+ for (int k = 0; k < sizeof(iface.phys_addr); ++k) {
+ sprintf(p, "%.2x:", (uint8_t)iface.phys_addr[k]);
+ p += 3;
+ }
+ p[-1] = '\0';
+ 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 kr_context *ctx = &the_worker->engine->resolver;
+ const int argc = lua_gettop(L);
+ if (argc == 0) {
+ lua_pushinteger(L, knot_edns_get_payload(ctx->downstream_opt_rr));
+ lua_pushinteger(L, knot_edns_get_payload(ctx->upstream_opt_rr));
+ return 2;
+ }
+
+ if (argc == 1) {
+ int bufsize = lua_tointeger(L, 1);
+ if (bufsize < 512 || bufsize > UINT16_MAX)
+ lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">");
+ knot_edns_set_payload(ctx->downstream_opt_rr, (uint16_t)bufsize);
+ knot_edns_set_payload(ctx->upstream_opt_rr, (uint16_t)bufsize);
+ } else if (argc == 2) {
+ int bufsize_downstream = lua_tointeger(L, 1);
+ int bufsize_upstream = lua_tointeger(L, 2);
+ if (bufsize_downstream < 512 || bufsize_upstream < 512
+ || bufsize_downstream > UINT16_MAX || bufsize_upstream > UINT16_MAX) {
+ lua_error_p(L, "bufsize must be within <512, " STR(UINT16_MAX) ">");
+ }
+ knot_edns_set_payload(ctx->downstream_opt_rr, (uint16_t)bufsize_downstream);
+ knot_edns_set_payload(ctx->upstream_opt_rr, (uint16_t)bufsize_upstream);
+ }
+ return 0;
+}
+
+/** Set TCP pipelining size. */
+static int net_pipeline(lua_State *L)
+{
+ struct worker_ctx *worker = the_worker;
+ if (!worker) {
+ return 0;
+ }
+ if (!lua_isnumber(L, 1)) {
+ lua_pushinteger(L, worker->tcp_pipeline_max);
+ return 1;
+ }
+ int len = lua_tointeger(L, 1);
+ if (len < 0 || len > UINT16_MAX)
+ lua_error_p(L, "tcp_pipeline must be within <0, " STR(UINT16_MAX) ">");
+ worker->tcp_pipeline_max = len;
+ lua_pushinteger(L, len);
+ return 1;
+}
+
+static int net_tls(lua_State *L)
+{
+ struct network *net = &the_worker->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_error_p(L, "net.tls takes two parameters: (\"cert_file\", \"key_file\")");
+
+ int r = tls_certificate_set(net, lua_tostring(L, 1), lua_tostring(L, 2));
+ lua_error_maybe(L, r);
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+/** Configure HTTP headers for DoH requests. */
+static int net_doh_headers(lua_State *L)
+{
+ doh_headerlist_t *headers = &the_worker->doh_qry_headers;
+ int i;
+ const char *name;
+
+ /* Only return current configuration. */
+ if (lua_gettop(L) == 0) {
+ lua_newtable(L);
+ for (i = 0; i < headers->len; i++) {
+ lua_pushinteger(L, i + 1);
+ name = headers->at[i];
+ lua_pushlstring(L, name, strlen(name));
+ lua_settable(L, -3);
+ }
+ return 1;
+ }
+
+ if (lua_gettop(L) != 1)
+ lua_error_p(L, "net.doh_headers() takes one parameter (string or table)");
+
+ if (!lua_istable(L, 1) && !lua_isstring(L, 1))
+ lua_error_p(L, "net.doh_headers() argument must be string or table");
+
+ /* Clear existing headers. */
+ for (i = 0; i < headers->len; i++)
+ free((void *)headers->at[i]);
+ array_clear(*headers);
+
+ if (lua_istable(L, 1)) {
+ for (i = 1; !lua_isnil(L, -1); i++) {
+ lua_pushinteger(L, i);
+ lua_gettable(L, 1);
+ if (lua_isnil(L, -1)) /* missing value - end iteration */
+ break;
+ if (!lua_isstring(L, -1))
+ lua_error_p(L, "net.doh_headers() argument table can only contain strings");
+ name = lua_tostring(L, -1);
+ array_push(*headers, strdup(name));
+ }
+ } else if (lua_isstring(L, 1)) {
+ name = lua_tostring(L, 1);
+ array_push(*headers, strdup(name));
+ }
+
+ return 0;
+}
+
+/** Return a lua table with TLS authentication parameters.
+ * The format is the same as passed to policy.TLS_FORWARD();
+ * more precisely, it's in a compatible canonical form. */
+static int tls_params2lua(lua_State *L, trie_t *params)
+{
+ lua_newtable(L);
+ if (!params) /* Allowed special case. */
+ return 1;
+ trie_it_t *it;
+ size_t list_index = 0;
+ for (it = trie_it_begin(params); !trie_it_finished(it); trie_it_next(it)) {
+ /* Prepare table for the current address
+ * and its index in the returned list. */
+ lua_pushinteger(L, ++list_index);
+ lua_createtable(L, 0, 2);
+
+ /* Get the "addr#port" string... */
+ size_t ia_len;
+ const char *key = trie_it_key(it, &ia_len);
+ int af = AF_UNSPEC;
+ if (ia_len == 2 + sizeof(struct in_addr)) {
+ af = AF_INET;
+ } else if (ia_len == 2 + sizeof(struct in6_addr)) {
+ af = AF_INET6;
+ }
+ if (kr_fails_assert(key && af != AF_UNSPEC))
+ lua_error_p(L, "internal error: bad IP address");
+ uint16_t port;
+ memcpy(&port, key, sizeof(port));
+ port = ntohs(port);
+ const char *ia = key + sizeof(port);
+ char str[INET6_ADDRSTRLEN + 1 + 5 + 1];
+ size_t len = sizeof(str);
+ if (kr_fails_assert(kr_ntop_str(af, ia, port, str, &len) == kr_ok()))
+ lua_error_p(L, "internal error: bad IP address conversion");
+ /* ...and push it as [1]. */
+ lua_pushinteger(L, 1);
+ lua_pushlstring(L, str, len - 1 /* len includes '\0' */);
+ lua_settable(L, -3);
+
+ const tls_client_param_t *e = *trie_it_val(it);
+ if (kr_fails_assert(e))
+ lua_error_p(L, "internal problem - NULL entry for %s", str);
+
+ /* .hostname = */
+ if (e->hostname) {
+ lua_pushstring(L, e->hostname);
+ lua_setfield(L, -2, "hostname");
+ }
+
+ /* .ca_files = */
+ if (e->ca_files.len) {
+ lua_createtable(L, e->ca_files.len, 0);
+ for (size_t i = 0; i < e->ca_files.len; ++i) {
+ lua_pushinteger(L, i + 1);
+ lua_pushstring(L, e->ca_files.at[i]);
+ lua_settable(L, -3);
+ }
+ lua_setfield(L, -2, "ca_files");
+ }
+
+ /* .pin_sha256 = ... ; keep sane indentation via goto. */
+ if (!e->pins.len) goto no_pins;
+ lua_createtable(L, e->pins.len, 0);
+ for (size_t i = 0; i < e->pins.len; ++i) {
+ uint8_t pin_base64[TLS_SHA256_BASE64_BUFLEN];
+ int err = kr_base64_encode(e->pins.at[i], TLS_SHA256_RAW_LEN,
+ pin_base64, sizeof(pin_base64));
+ if (kr_fails_assert(err >= 0))
+ lua_error_p(L,
+ "internal problem when converting pin_sha256: %s",
+ kr_strerror(err));
+ lua_pushinteger(L, i + 1);
+ lua_pushlstring(L, (const char *)pin_base64, err);
+ /* pin_base64 isn't 0-terminated ^^^ */
+ lua_settable(L, -3);
+ }
+ lua_setfield(L, -2, "pin_sha256");
+
+ no_pins:/* .insecure = */
+ if (e->insecure) {
+ lua_pushboolean(L, true);
+ lua_setfield(L, -2, "insecure");
+ }
+ /* Now the whole table is pushed atop the returned list. */
+ lua_settable(L, -3);
+ }
+ trie_it_free(it);
+ return 1;
+}
+
+static inline int cmp_sha256(const void *p1, const void *p2)
+{
+ return memcmp(*(char * const *)p1, *(char * const *)p2, TLS_SHA256_RAW_LEN);
+}
+static int net_tls_client(lua_State *L)
+{
+ /* TODO idea: allow starting the lua table with *multiple* IP targets,
+ * meaning the authentication config should be applied to each.
+ */
+ struct network *net = &the_worker->engine->net;
+ if (lua_gettop(L) == 0)
+ return tls_params2lua(L, net->tls_client_params);
+ /* Various basic sanity-checking. */
+ if (lua_gettop(L) != 1 || !lua_istable(L, 1))
+ lua_error_maybe(L, EINVAL);
+ /* check that only allowed keys are present */
+ {
+ const char *bad_key = lua_table_checkindices(L, (const char *[])
+ { "1", "hostname", "ca_file", "pin_sha256", "insecure", NULL });
+ if (bad_key)
+ lua_error_p(L, "found unexpected key '%s'", bad_key);
+ }
+
+ /**** Phase 1: get the parameter into a C struct, incl. parse of CA files,
+ * regardless of the address-pair having an entry already. */
+
+ tls_client_param_t *newcfg = tls_client_param_new();
+ if (!newcfg)
+ lua_error_p(L, "out of memory or something like that :-/");
+ /* Shortcut for cleanup actions needed from now on. */
+ #define ERROR(...) do { \
+ free(newcfg); \
+ lua_error_p(L, __VA_ARGS__); \
+ } while (false)
+
+ /* .hostname - always accepted. */
+ lua_getfield(L, 1, "hostname");
+ if (!lua_isnil(L, -1)) {
+ const char *hn_str = lua_tostring(L, -1);
+ /* Convert to lower-case dname and back, for checking etc. */
+ knot_dname_t dname[KNOT_DNAME_MAXLEN];
+ if (!hn_str || !knot_dname_from_str(dname, hn_str, sizeof(dname)))
+ ERROR("invalid hostname");
+ knot_dname_to_lower(dname);
+ char *h = knot_dname_to_str_alloc(dname);
+ if (!h)
+ ERROR("%s", kr_strerror(ENOMEM));
+ /* Strip the final dot produced by knot_dname_*() */
+ h[strlen(h) - 1] = '\0';
+ newcfg->hostname = h;
+ }
+ lua_pop(L, 1);
+
+ /* .ca_file - it can be a list of paths, contrary to the name. */
+ bool has_ca_file = false;
+ lua_getfield(L, 1, "ca_file");
+ if (!lua_isnil(L, -1)) {
+ if (!newcfg->hostname)
+ ERROR("missing hostname but specifying ca_file");
+ lua_listify(L);
+ array_init(newcfg->ca_files); /*< placate apparently confused scan-build */
+ if (array_reserve(newcfg->ca_files, lua_objlen(L, -1)) != 0) /*< optim. */
+ ERROR("%s", kr_strerror(ENOMEM));
+ /* Iterate over table at the top of the stack.
+ * http://www.lua.org/manual/5.1/manual.html#lua_next */
+ for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
+ has_ca_file = true; /* deferred here so that {} -> false */
+ const char *ca_file = lua_tostring(L, -1);
+ if (!ca_file)
+ ERROR("ca_file contains a non-string");
+ /* Let gnutls process it immediately, so garbage gets detected. */
+ int ret = gnutls_certificate_set_x509_trust_file(
+ newcfg->credentials, ca_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0) {
+ ERROR("failed to import certificate file '%s': %s - %s\n",
+ ca_file, gnutls_strerror_name(ret),
+ gnutls_strerror(ret));
+ } else {
+ kr_log_debug(TLSCLIENT, "imported %d certs from file '%s'\n",
+ ret, ca_file);
+ }
+
+ ca_file = strdup(ca_file);
+ if (!ca_file || array_push(newcfg->ca_files, ca_file) < 0)
+ ERROR("%s", kr_strerror(ENOMEM));
+ }
+ /* Sort the strings for easier comparison later. */
+ if (newcfg->ca_files.len) {
+ qsort(&newcfg->ca_files.at[0], newcfg->ca_files.len,
+ sizeof(newcfg->ca_files.at[0]), strcmp_p);
+ }
+ }
+ lua_pop(L, 1);
+
+ /* .pin_sha256 */
+ lua_getfield(L, 1, "pin_sha256");
+ if (!lua_isnil(L, -1)) {
+ if (has_ca_file)
+ ERROR("mixing pin_sha256 with ca_file is not supported");
+ lua_listify(L);
+ array_init(newcfg->pins); /*< placate apparently confused scan-build */
+ if (array_reserve(newcfg->pins, lua_objlen(L, -1)) != 0) /*< optim. */
+ ERROR("%s", kr_strerror(ENOMEM));
+ /* Iterate over table at the top of the stack. */
+ for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) {
+ const char *pin = lua_tostring(L, -1);
+ if (!pin)
+ ERROR("pin_sha256 is not a string");
+ uint8_t *pin_raw = malloc(TLS_SHA256_RAW_LEN);
+ /* Push the string early to simplify error processing. */
+ if (kr_fails_assert(pin_raw && array_push(newcfg->pins, pin_raw) >= 0)) {
+ free(pin_raw);
+ ERROR("%s", kr_strerror(ENOMEM));
+ }
+ int ret = kr_base64_decode((const uint8_t *)pin, strlen(pin),
+ pin_raw, TLS_SHA256_RAW_LEN + 8);
+ if (ret < 0) {
+ ERROR("not a valid pin_sha256: '%s' (length %d), %s\n",
+ pin, (int)strlen(pin), knot_strerror(ret));
+ } else if (ret != TLS_SHA256_RAW_LEN) {
+ ERROR("not a valid pin_sha256: '%s', "
+ "raw length %d instead of "
+ STR(TLS_SHA256_RAW_LEN)"\n",
+ pin, ret);
+ }
+ }
+ /* Sort the raw strings for easier comparison later. */
+ if (newcfg->pins.len) {
+ qsort(&newcfg->pins.at[0], newcfg->pins.len,
+ sizeof(newcfg->pins.at[0]), cmp_sha256);
+ }
+ }
+ lua_pop(L, 1);
+
+ /* .insecure */
+ lua_getfield(L, 1, "insecure");
+ if (lua_isnil(L, -1)) {
+ if (!newcfg->hostname && !newcfg->pins.len)
+ ERROR("no way to authenticate and not set as insecure");
+ } else if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) {
+ newcfg->insecure = true;
+ if (has_ca_file || newcfg->pins.len)
+ ERROR("set as insecure but provided authentication config");
+ } else {
+ ERROR("incorrect value in the 'insecure' field");
+ }
+ lua_pop(L, 1);
+
+ /* Init CAs from system trust store, if needed. */
+ if (!newcfg->insecure && !newcfg->pins.len && !has_ca_file) {
+ int ret = gnutls_certificate_set_x509_system_trust(newcfg->credentials);
+ if (ret <= 0) {
+ ERROR("failed to use system CA certificate store: %s",
+ ret ? gnutls_strerror(ret) : kr_strerror(ENOENT));
+ } else {
+ kr_log_debug(TLSCLIENT, "imported %d certs from system store\n",
+ ret);
+ }
+ }
+ #undef ERROR
+
+ /**** Phase 2: deal with the C authentication "table". */
+ /* Parse address and port. */
+ lua_pushinteger(L, 1);
+ lua_gettable(L, 1);
+ const char *addr_str = lua_tostring(L, -1);
+ if (!addr_str)
+ lua_error_p(L, "address is not a string");
+ char buf[INET6_ADDRSTRLEN + 1];
+ uint16_t port = 853;
+ const struct sockaddr *addr = NULL;
+ if (kr_straddr_split(addr_str, buf, &port) == kr_ok())
+ addr = kr_straddr_socket(buf, port, NULL);
+ /* Add newcfg into the C map, saving the original into oldcfg. */
+ if (!addr)
+ lua_error_p(L, "address '%s' could not be converted", addr_str);
+ tls_client_param_t **oldcfgp = tls_client_param_getptr(
+ &net->tls_client_params, addr, true);
+ free_const(addr);
+ if (!oldcfgp)
+ lua_error_p(L, "internal error when extending tls_client_params map");
+ tls_client_param_t *oldcfg = *oldcfgp;
+ *oldcfgp = newcfg; /* replace old config in trie with the new one */
+ /* If there was no original entry, it's easy! */
+ if (!oldcfg)
+ return 0;
+
+ /* Check for equality (newcfg vs. oldcfg), and print a warning if not equal.*/
+ const bool ok_h = (!newcfg->hostname && !oldcfg->hostname)
+ || (newcfg->hostname && oldcfg->hostname && strcmp(newcfg->hostname, oldcfg->hostname) == 0);
+ bool ok_ca = newcfg->ca_files.len == oldcfg->ca_files.len;
+ for (int i = 0; ok_ca && i < newcfg->ca_files.len; ++i)
+ ok_ca = strcmp(newcfg->ca_files.at[i], oldcfg->ca_files.at[i]) == 0;
+ bool ok_pins = newcfg->pins.len == oldcfg->pins.len;
+ for (int i = 0; ok_pins && i < newcfg->pins.len; ++i)
+ ok_ca = memcmp(newcfg->pins.at[i], oldcfg->pins.at[i], TLS_SHA256_RAW_LEN) == 0;
+ const bool ok_insecure = newcfg->insecure == oldcfg->insecure;
+ if (!(ok_h && ok_ca && ok_pins && ok_insecure)) {
+ kr_log_warning(TLSCLIENT,
+ "warning: re-defining TLS authentication parameters for %s\n",
+ addr_str);
+ }
+ tls_client_param_unref(oldcfg);
+ return 0;
+}
+
+int net_tls_client_clear(lua_State *L)
+{
+ /* One parameter: address -> convert it to a struct sockaddr. */
+ if (lua_gettop(L) != 1 || !lua_isstring(L, 1))
+ lua_error_p(L, "net.tls_client_clear() requires one parameter (\"address\")");
+ const char *addr_str = lua_tostring(L, 1);
+ char buf[INET6_ADDRSTRLEN + 1];
+ uint16_t port = 853;
+ const struct sockaddr *addr = NULL;
+ if (kr_straddr_split(addr_str, buf, &port) == kr_ok())
+ addr = kr_straddr_socket(buf, port, NULL);
+ if (!addr)
+ lua_error_p(L, "invalid IP address");
+ /* Do the actual removal. */
+ struct network *net = &the_worker->engine->net;
+ int r = tls_client_param_remove(net->tls_client_params, addr);
+ free_const(addr);
+ lua_error_maybe(L, r);
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_tls_padding(lua_State *L)
+{
+ struct kr_context *ctx = &the_worker->engine->resolver;
+
+ /* Only return current padding. */
+ if (lua_gettop(L) == 0) {
+ if (ctx->tls_padding < 0) {
+ lua_pushboolean(L, true);
+ return 1;
+ } else if (ctx->tls_padding == 0) {
+ lua_pushboolean(L, false);
+ return 1;
+ }
+ lua_pushinteger(L, ctx->tls_padding);
+ return 1;
+ }
+
+ const char *errstr = "net.tls_padding parameter has to be true, false,"
+ " or a number between <0, " STR(MAX_TLS_PADDING) ">";
+ if (lua_gettop(L) != 1)
+ lua_error_p(L, "%s", errstr);
+ if (lua_isboolean(L, 1)) {
+ bool x = lua_toboolean(L, 1);
+ if (x) {
+ ctx->tls_padding = -1;
+ } else {
+ ctx->tls_padding = 0;
+ }
+ } else if (lua_isnumber(L, 1)) {
+ int padding = lua_tointeger(L, 1);
+ if ((padding < 0) || (padding > MAX_TLS_PADDING))
+ lua_error_p(L, "%s", errstr);
+ ctx->tls_padding = padding;
+ } else {
+ lua_error_p(L, "%s", errstr);
+ }
+ 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 = &the_worker->engine->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_error_p(L,
+ "net.tls_sticket_secret takes one parameter: (\"secret string\")");
+ }
+ secret = lua_tolstring(L, 1, &secret_len);
+ if (secret_len < net_tls_sticket_MIN_SECRET_LEN || !secret) {
+ lua_error_p(L, "net.tls_sticket_secret - the secret is shorter than "
+ STR(net_tls_sticket_MIN_SECRET_LEN) " bytes");
+ }
+ }
+
+ 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_error_p(L,
+ "net.tls_sticket_secret_string - can't create session ticket context");
+ }
+
+ 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_error_p(L,
+ "net.tls_sticket_secret_file takes one parameter: (\"file name\")");
+ }
+
+ const char *file_name = lua_tostring(L, 1);
+ if (strlen(file_name) == 0)
+ lua_error_p(L, "net.tls_sticket_secret_file - empty file name");
+
+ FILE *fp = fopen(file_name, "r");
+ if (fp == NULL) {
+ lua_error_p(L, "net.tls_sticket_secret_file - can't open file '%s': %s",
+ file_name, strerror(errno));
+ }
+
+ 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_error_p(L,
+ "net.tls_sticket_secret_file - error reading from file '%s': %s",
+ file_name, strerror(err));
+ }
+ if (secret_len < net_tls_sticket_MIN_SECRET_LEN) {
+ lua_error_p(L,
+ "net.tls_sticket_secret_file - file '%s' is shorter than "
+ STR(net_tls_sticket_MIN_SECRET_LEN) " bytes",
+ file_name);
+ }
+ fclose(fp);
+
+ struct network *net = &the_worker->engine->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_error_p(L,
+ "net.tls_sticket_secret_file - can't create session ticket context");
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_outgoing(lua_State *L, int family)
+{
+ union kr_sockaddr *addr;
+ if (family == AF_INET)
+ addr = (union kr_sockaddr*)&the_worker->out_addr4;
+ else
+ addr = (union kr_sockaddr*)&the_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 (kr_fails_assert(addr->ip.sa_family == family))
+ lua_error_p(L, "bad address family");
+ 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));
+ lua_error_maybe(L, err);
+ lua_pushstring(L, addr_buf);
+ return 1;
+ }
+
+ if ((lua_gettop(L) != 1) || (!lua_isstring(L, 1) && !lua_isnil(L, 1)))
+ lua_error_p(L, "net.outgoing_vX takes one address string parameter or nil");
+
+ 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)
+ lua_error_p(L, "net.outgoing_vX: failed to parse the address");
+ 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_pushinteger(L, *timeout);
+ return 1;
+ }
+
+ if ((lua_gettop(L) != 1))
+ lua_error_p(L, "%s takes one parameter: (\"idle timeout\")", name);
+
+ if (lua_isnumber(L, 1)) {
+ int idle_timeout = lua_tointeger(L, 1);
+ if (idle_timeout <= 0)
+ lua_error_p(L, "%s parameter has to be positive number", name);
+ *timeout = idle_timeout;
+ } else {
+ lua_error_p(L, "%s parameter has to be positive number", name);
+ }
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static int net_tcp_in_idle(lua_State *L)
+{
+ struct network *net = &the_worker->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 network *net = &the_worker->engine->net;
+ return net_update_timeout(L, &net->tcp.tls_handshake_timeout, "net.tls_handshake_timeout");
+}
+
+static int net_bpf_set(lua_State *L)
+{
+ if (lua_gettop(L) != 1 || !lua_isnumber(L, 1)) {
+ lua_error_p(L, "net.bpf_set(fd) takes one parameter:"
+ " the open file descriptor of a loaded BPF program");
+ }
+
+#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_p(L, "failed to convert parameter");
+ }
+ lua_pop(L, 1);
+
+ if (network_set_bpf(&the_worker->engine->net, progfd) == 0) {
+ lua_error_p(L, "failed to attach BPF program to some networks: %s",
+ kr_strerror(errno));
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+
+#endif
+ lua_error_p(L, "BPF is not supported on this operating system");
+}
+
+static int net_bpf_clear(lua_State *L)
+{
+ if (lua_gettop(L) != 0)
+ lua_error_p(L, "net.bpf_clear() does not take any parameters");
+
+#if __linux__
+
+ network_clear_bpf(&the_worker->engine->net);
+
+ lua_pushboolean(L, 1);
+ return 1;
+
+#endif
+ lua_error_p(L, "BPF is not supported on this operating system");
+}
+
+static int net_register_endpoint_kind(lua_State *L)
+{
+ const int param_count = lua_gettop(L);
+ if (param_count != 1 && param_count != 2)
+ lua_error_p(L, "expected one or two parameters");
+ if (!lua_isstring(L, 1)) {
+ lua_error_p(L, "incorrect kind '%s'", lua_tostring(L, 1));
+ }
+ size_t kind_len;
+ const char *kind = lua_tolstring(L, 1, &kind_len);
+ struct network *net = &the_worker->engine->net;
+
+ /* Unregistering */
+ if (param_count == 1) {
+ void *val;
+ if (trie_del(net->endpoint_kinds, kind, kind_len, &val) == KNOT_EOK) {
+ const int fun_id = (char *)val - (char *)NULL;
+ luaL_unref(L, LUA_REGISTRYINDEX, fun_id);
+ return 0;
+ }
+ lua_error_p(L, "attempt to unregister unknown kind '%s'\n", kind);
+ } /* else -> param_count == 2 */
+
+ /* Registering */
+ if (!lua_isfunction(L, 2)) {
+ lua_error_p(L, "second parameter: expected function but got %s\n",
+ lua_typename(L, lua_type(L, 2)));
+ }
+ const int fun_id = luaL_ref(L, LUA_REGISTRYINDEX);
+ /* ^^ The function is on top of the stack, incidentally. */
+ void **pp = trie_get_ins(net->endpoint_kinds, kind, kind_len);
+ if (!pp) lua_error_maybe(L, kr_error(ENOMEM));
+ if (*pp != NULL || !strcasecmp(kind, "dns") || !strcasecmp(kind, "tls"))
+ lua_error_p(L, "attempt to register known kind '%s'\n", kind);
+ *pp = (char *)NULL + fun_id;
+ /* We don't attempt to engage corresponding endpoints now.
+ * That's the job for network_engage_endpoints() later. */
+ return 0;
+}
+
+int kr_bindings_net(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "list", net_list },
+ { "listen", net_listen },
+ { "proxy_allowed", net_proxy_allowed },
+ { "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 },
+ { "register_endpoint_kind", net_register_endpoint_kind },
+ { "doh_headers", net_doh_headers },
+ { NULL, NULL }
+ };
+ luaL_register(L, "net", lib);
+ return 1;
+}
+
diff --git a/daemon/bindings/net_client.rst b/daemon/bindings/net_client.rst
new file mode 100644
index 0000000..34e6236
--- /dev/null
+++ b/daemon/bindings/net_client.rst
@@ -0,0 +1,34 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+IPv4 and IPv6 usage
+-------------------
+
+Following settings affect client part of the resolver,
+i.e. communication between the resolver itself and other DNS servers.
+
+IPv4 and IPv6 protocols are used by default. For performance reasons it is
+recommended to explicitly disable protocols which are not available
+on your system, though the impact of IPv6 outage is lowered since release 5.3.0.
+
+.. envvar:: net.ipv4 = true|false
+
+ :return: boolean (default: true)
+
+ Enable/disable using IPv4 for contacting upstream nameservers.
+
+.. envvar:: net.ipv6 = true|false
+
+ :return: boolean (default: true)
+
+ Enable/disable using IPv6 for contacting upstream nameservers.
+
+.. function:: net.outgoing_v4([string address])
+
+ Get/set the IPv4 address used to perform queries.
+ The default is ``nil``, which lets the OS choose any address.
+
+.. function:: net.outgoing_v6([string address])
+
+ Get/set the IPv6 address used to perform queries.
+ The default is ``nil``, which lets the OS choose any address.
+
diff --git a/daemon/bindings/net_dns_tweaks.rst b/daemon/bindings/net_dns_tweaks.rst
new file mode 100644
index 0000000..4cfeba6
--- /dev/null
+++ b/daemon/bindings/net_dns_tweaks.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+DNS protocol tweaks
+-------------------
+
+Following settings change low-level details of DNS protocol implementation.
+Default values should not be changed except for very special cases.
+
+.. function:: net.bufsize([udp_downstream_bufsize][, udp_upstream_bufsize])
+
+ Get/set maximum EDNS payload size advertised in DNS packets. Different values can be configured for communication downstream (towards clients) and upstream (towards other DNS servers). Set and also get operations use values in this order.
+
+ Default is 1232 bytes which was chosen to minimize risk of `issues caused by IP fragmentation <https://blog.apnic.net/2019/07/12/its-time-to-consider-avoiding-ip-fragmentation-in-the-dns/>`_. Further details can be found at `DNS Flag Day 2020 <https://www.dnsflagday.net/2020/>`_ web site.
+
+ Minimal value allowed by standard :rfc:`6891` is 512 bytes, which is equal to DNS packet size without Extension Mechanisms for DNS. Value 1220 bytes is minimum size required by DNSSEC standard :rfc:`4035`.
+
+ Example output:
+
+ .. code-block:: lua
+
+ -- set downstream and upstream bufsize to value 4096
+ > net.bufsize(4096)
+ -- get configured downstream and upstream bufsizes, respectively
+ > net.bufsize()
+ 4096 -- result # 1
+ 4096 -- result # 2
+
+ -- set downstream bufsize to 4096 and upstream bufsize to 1232
+ > net.bufsize(4096, 1232)
+ -- get configured downstream and upstream bufsizes, respectively
+ > net.bufsize()
+ 4096 -- result # 1
+ 1232 -- result # 2
+
+.. include:: ../modules/workarounds/README.rst
diff --git a/daemon/bindings/net_server.rst b/daemon/bindings/net_server.rst
new file mode 100644
index 0000000..f346aeb
--- /dev/null
+++ b/daemon/bindings/net_server.rst
@@ -0,0 +1,225 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+Addresses and services
+----------------------
+
+Addresses, ports, protocols, and API calls available for clients communicating
+with resolver are configured using :func:`net.listen`.
+
+First you need to decide what service should be available on given IP address
++ port combination.
+
+.. csv-table::
+ :header: "Protocol/service", "net.listen *kind*"
+
+ "DNS (unencrypted UDP+TCP, :rfc:`1034`)","``dns``"
+ "DNS (unencrypted UDP, :ref:`using XDP Linux API <dns-over-xdp>`)","``xdp``"
+ ":ref:`dns-over-tls`","``tls``"
+ ":ref:`dns-over-https`","``doh2``"
+ ":ref:`Web management <mod-http-built-in-services>`","``webmgmt``"
+ ":ref:`Control socket <control-sockets>`","``control``"
+ ":ref:`mod-http-doh`","``doh_legacy``"
+
+.. note:: By default, **unencrypted DNS and DNS-over-TLS** are configured to **listen
+ on localhost**.
+
+ Control sockets are created either in
+ ``/run/knot-resolver/control/`` (when using systemd) or ``$PWD/control/``.
+
+.. function:: net.listen(addresses, [port = 53, { kind = 'dns', freebind = false }])
+
+ :return: ``true`` if port is bound, an error otherwise
+
+ Listen on addresses; port and flags are optional.
+ The addresses can be specified as a string or device.
+ Port 853 implies ``kind = 'tls'`` but it is always better to be explicit.
+ Freebind allows binding to a non-local or not yet available address.
+
+.. csv-table::
+ :header: "**Network protocol**", "**Configuration command**"
+
+ "DNS (UDP+TCP, :rfc:`1034`)","``net.listen('192.0.2.123', 53)``"
+ "DNS (UDP, :ref:`using XDP <dns-over-xdp>`)","``net.listen('192.0.2.123', 53, { kind = 'xdp' })``"
+ ":ref:`dns-over-tls`","``net.listen('192.0.2.123', 853, { kind = 'tls' })``"
+ ":ref:`dns-over-https`","``net.listen('192.0.2.123', 443, { kind = 'doh2' })``"
+ ":ref:`Web management <mod-http-built-in-services>`","``net.listen('192.0.2.123', 8453, { kind = 'webmgmt' })``"
+ ":ref:`Control socket <control-sockets>`","``net.listen('/tmp/kres.control', nil, { kind = 'control' })``"
+
+
+Examples:
+
+ .. code-block:: lua
+
+ net.listen('::1')
+ net.listen(net.lo, 53)
+ net.listen(net.eth0, 853, { kind = 'tls' })
+ net.listen('192.0.2.1', 53, { freebind = true })
+ net.listen({'127.0.0.1', '::1'}, 53, { kind = 'dns' })
+ net.listen('::', 443, { kind = 'doh2' })
+ net.listen('::', 8453, { kind = 'webmgmt' }) -- see http module
+ net.listen('/tmp/kresd-socket', nil, { kind = 'webmgmt' }) -- http module supports AF_UNIX
+ net.listen('eth0', 53, { kind = 'xdp' })
+ net.listen('192.0.2.123', 53, { kind = 'xdp', nic_queue = 0 })
+
+.. warning:: On machines with multiple IP addresses avoid listening on wildcards
+ ``0.0.0.0`` or ``::``. Knot Resolver could answer from different IP
+ addresses if the network address ranges overlap,
+ and clients would probably refuse such a response.
+
+.. _proxyv2:
+
+PROXYv2 protocol
+^^^^^^^^^^^^^^^^
+
+Knot Resolver supports proxies that utilize the `PROXYv2 protocol <https://www.haproxy.org/download/2.5/doc/proxy-protocol.txt>`_
+to identify clients.
+
+A PROXY header contains the IP address of the original client who sent a query.
+This allows the resolver to treat queries as if they actually came from
+the client's IP address rather than the address of the proxy they came through.
+For example, :ref:`Views and ACLs <mod-view>` are able to work properly when
+PROXYv2 is in use.
+
+Since allowing usage of the PROXYv2 protocol for all clients would be a security
+vulnerability, because clients would then be able to spoof their IP addresses via
+the PROXYv2 header, the resolver requires you to specify explicitly which clients
+are allowed to send PROXYv2 headers via the :func:`net.proxy_allowed` function.
+
+PROXYv2 queries from clients who are not explicitly allowed to use this protocol
+will be discarded.
+
+.. function:: net.proxy_allowed([addresses])
+
+ Allow usage of the PROXYv2 protocol headers by clients on the specified
+ ``addresses``. It is possible to permit whole networks to send PROXYv2 headers
+ by specifying the network mask using the CIDR notation
+ (e.g. ``172.22.0.0/16``). IPv4 as well as IPv6 addresses are supported.
+
+ If you wish to allow all clients to use PROXYv2 (e.g. because you have this
+ kind of security handled on another layer of your network infrastructure),
+ you can specify a netmask of ``/0``. Please note that this setting is
+ address-family-specific, so this needs to be applied to both IPv4 and IPv6
+ separately.
+
+ Subsequent calls to the function overwrite the effects of all previous calls.
+ Providing a table of strings as the function parameter allows multiple
+ distinct addresses to use the PROXYv2 protocol.
+
+ When called without arguments, ``net.proxy_allowed`` returns a table of all
+ addresses currently allowed to use the PROXYv2 protocol and does not change
+ the configuration.
+
+Examples:
+
+ .. code-block:: lua
+
+ net.proxy_allowed('172.22.0.1') -- allows '172.22.0.1' specifically
+ net.proxy_allowed('172.18.1.0/24') -- allows everyone at '172.18.1.*'
+ net.proxy_allowed({
+ '172.22.0.1', '172.18.1.0/24'
+ }) -- allows both of the above at once
+ net.proxy_allowed({ 'fe80::/10' } -- allows everyone at IPv6 link-local
+ net.proxy_allowed({
+ '::/0', '0.0.0.0/0'
+ }) -- allows everyone
+ net.proxy_allowed('::/0') -- allows all IPv6 (but no IPv4)
+ net.proxy_allowed({}) -- prevents everyone from using PROXYv2
+ net.proxy_allowed() -- returns a list of all currently allowed addresses
+
+Features for scripting
+^^^^^^^^^^^^^^^^^^^^^^
+Following configuration functions are useful mainly for scripting or :ref:`runtime-cfg`.
+
+.. function:: net.close(address, [port])
+
+ :return: boolean (at least one endpoint closed)
+
+ Close all endpoints listening on the specified address, optionally restricted by port as well.
+
+
+.. function:: net.list()
+
+ :return: Table of bound interfaces.
+
+ Example output:
+
+ .. code-block:: none
+
+ [1] => {
+ [kind] => tls
+ [transport] => {
+ [family] => inet4
+ [ip] => 127.0.0.1
+ [port] => 853
+ [protocol] => tcp
+ }
+ }
+ [2] => {
+ [kind] => dns
+ [transport] => {
+ [family] => inet6
+ [ip] => ::1
+ [port] => 53
+ [protocol] => udp
+ }
+ }
+ [3] => {
+ [kind] => dns
+ [transport] => {
+ [family] => inet6
+ [ip] => ::1
+ [port] => 53
+ [protocol] => tcp
+ }
+ }
+ [4] => {
+ [kind] => xdp
+ [transport] => {
+ [family] => inet4+inet6
+ [interface] => eth2
+ [nic_queue] => 0
+ [port] => 53
+ [protocol] => udp
+ }
+ }
+
+.. 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.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.
+
+.. _`dnsproxy module`: https://www.knot-dns.cz/docs/2.7/html/modules.html#dnsproxy-tiny-dns-proxy
+
+
diff --git a/daemon/bindings/net_tlssrv.rst b/daemon/bindings/net_tlssrv.rst
new file mode 100644
index 0000000..f496cd7
--- /dev/null
+++ b/daemon/bindings/net_tlssrv.rst
@@ -0,0 +1,188 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _tls-server-config:
+
+DoT and DoH (encrypted DNS)
+---------------------------
+
+.. warning::
+
+ It is important to understand **limits of encrypting only DNS traffic**.
+ Relevant security analysis can be found in article
+ *Simran Patil and Nikita Borisov. 2019. What can you learn from an IP?*
+ See `slides <https://irtf.org/anrw/2019/slides-anrw19-final44.pdf>`_
+ or `the article itself <https://dl.acm.org/authorize?N687437>`_.
+
+DoT and DoH encrypt DNS traffic with Transport Layer Security (TLS) protocol
+and thus protects DNS traffic from certain types of attacks.
+
+You can learn more about DoT and DoH and their implementation in Knot Resolver
+in `this article
+<https://en.blog.nic.cz/2020/11/25/encrypted-dns-in-knot-resolver-dot-and-doh/>`_.
+
+.. _dns-over-tls:
+
+DNS-over-TLS (DoT)
+^^^^^^^^^^^^^^^^^^
+
+DNS-over-TLS server (:rfc:`7858`) can be configured using ``tls`` kind in
+:func:`net.listen()`. It is enabled on localhost by default.
+
+For certificate configuration, refer to :ref:`dot-doh-config-options`.
+
+.. _dns-over-https:
+
+DNS-over-HTTPS (DoH)
+^^^^^^^^^^^^^^^^^^^^
+
+.. note:: Knot Resolver currently offers two DoH implementations. It is
+ recommended to use this new implementation, which is more reliable, scalable
+ and has fewer dependencies. Make sure to use ``doh2`` kind in
+ :func:`net.listen()` to select this implementation.
+
+.. tip:: Independent information about political controversies around the
+ DoH deployment by default can be found in blog posts `DNS Privacy at IETF
+ 104 <http://www.potaroo.net/ispcol/2019-04/angst.html>`_ and `More DOH
+ <http://www.potaroo.net/ispcol/2019-04/moredoh.html>`_ by Geoff Huston and
+ `Centralised DoH is bad for Privacy, in 2019 and beyond
+ <https://labs.ripe.net/Members/bert_hubert/centralised-doh-is-bad-for-privacy-in-2019-and-beyond>`_
+ by Bert Hubert.
+
+DNS-over-HTTPS server (:rfc:`8484`) can be configured using ``doh2`` kind in
+:func:`net.listen()`.
+
+This implementation supports HTTP/2 (:rfc:`7540`). Queries can be sent to the
+``/dns-query`` endpoint, e.g.:
+
+.. code-block:: bash
+
+ $ kdig @127.0.0.1 +https www.knot-resolver.cz AAAA
+
+**Only TLS version 1.3 (or higher) is supported with DNS-over-HTTPS.** The
+additional considerations for TLS 1.2 required by HTTP/2 are not implemented
+(:rfc:`7540#section-9.2`).
+
+.. warning:: Take care when configuring your server to listen on well known
+ HTTPS port. If an unrelated HTTPS service is running on the same port with
+ REUSEPORT enabled, you will end up with both services malfunctioning.
+
+.. _dot-doh-config-options:
+
+HTTP status codes
+"""""""""""""""""
+
+As specified by :rfc:`8484`, the resolver responds with status **200 OK** whenever
+it can produce a valid DNS reply for a given query, even in cases where the DNS
+``rcode`` indicates an error (like ``NXDOMAIN``, ``SERVFAIL``, etc.).
+
+For DoH queries malformed at the HTTP level, the resolver may respond with
+the following status codes:
+
+ * **400 Bad Request** for a generally malformed query, like one not containing
+ a valid DNS packet
+ * **404 Not Found** when an incorrect HTTP endpoint is queried - the only
+ supported ones are ``/dns-query`` and ``/doh``
+ * **413 Payload Too Large** when the DNS query exceeds its maximum size
+ * **415 Unsupported Media Type** when the query's ``Content-Type`` header
+ is not ``application/dns-message``
+ * **431 Request Header Fields Too Large** when a header in the query is too
+ large to process
+ * **501 Not Implemented** when the query uses a method other than
+ ``GET``, ``POST``, or ``HEAD``
+
+Configuration options for DoT and DoH
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. note:: These settings affect both DNS-over-TLS and DNS-over-HTTPS (except
+ the legacy implementation).
+
+A self-signed certificate is generated by default. For serious deployments
+it is strongly recommended to configure your own TLS certificates signed
+by a trusted CA. This is done using function :c:func:`net.tls()`.
+
+.. function:: net.tls([cert_path], [key_path])
+
+ When called with path arguments, the function loads the server TLS
+ certificate and private key for DoT and DoH.
+
+ When called without arguments, the command returns the currently configured paths.
+
+ Example output:
+
+ .. code-block:: lua
+
+ > net.tls("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem")
+ > net.tls() -- print configured paths
+ [cert_file] => '/etc/knot-resolver/server-cert.pem'
+ [key_file] => '/etc/knot-resolver/server-key.pem'
+
+ .. tip:: The certificate files aren't automatically reloaded on change. If
+ you update the certificate files, e.g. using ACME, you have to either
+ restart the service(s) or call this function again using
+ :ref:`control-sockets`.
+
+.. 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 endianness
+ and time_t structure and size (`sizeof(time_t)`).
+
+.. _pfs: https://en.wikipedia.org/wiki/Forward_secrecy
+
+ **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 <pfs_>`_.
+
+ .. warning:: **Setting the secret is probably too risky with TLS <= 1.2 and
+ GnuTLS < 3.7.5**. GnuTLS 3.7.5 adds an option to disable resumption via
+ tickets for TLS <= 1.2, enabling them only for protocols that do guarantee
+ `PFS <pfs_>`_. Knot Resolver makes use of this new option when linked
+ against GnuTLS >= 3.7.5.
+
+.. 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.
+
+.. 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.
+
+Configuration options for DoH
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. function:: net.doh_headers([string or table of strings])
+
+ Selects the headers to be exposed. These headers and their values are
+ available in ``request.qsource.headers``. Comparison
+ is case-insensitive and pseudo-headers are supported as well.
+
+ The following snippet can be used in the lua module to access headers
+ ``:method`` and ``user-agent``:
+
+ .. code-block:: lua
+
+ net.doh_headers({':method', 'user-agent'})
+
+ ...
+
+ for i = 1, tonumber(req.qsource.headers.len) do
+ local name = ffi.string(req.qsource.headers.at[i - 1].name)
+ local value = ffi.string(req.qsource.headers.at[i - 1].value)
+ print(name, value)
+ end
diff --git a/daemon/bindings/net_xdpsrv.rst b/daemon/bindings/net_xdpsrv.rst
new file mode 100644
index 0000000..e3014fe
--- /dev/null
+++ b/daemon/bindings/net_xdpsrv.rst
@@ -0,0 +1,140 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _dns-over-xdp:
+
+XDP for higher UDP performance
+------------------------------
+
+.. warning::
+ As of version 5.2.0, XDP support in Knot Resolver is considered
+ experimental. The impact on overall throughput and performance may not
+ always be beneficial.
+
+Using XDP allows significant speedup of UDP packet processing in recent Linux kernels,
+especially with some network drivers that implement good support.
+The basic idea is that for selected packets the Linux networking stack is bypassed,
+and some drivers can even directly use the user-space buffers for reading and writing.
+
+.. TODO perhaps some hint/link about how significant speedup one might get? (link to some talk video?)
+
+Prerequisites
+^^^^^^^^^^^^^
+.. this is mostly copied from knot-dns doc/operations.rst
+
+.. warning::
+ Bypassing the network stack has significant implications, such as bypassing the firewall
+ and monitoring solutions.
+ Make sure you're familiar with the trade-offs before using this feature.
+ Read more in :ref:`dns-over-xdp_limitations`.
+
+* Linux kernel 4.18+ (5.x+ is recommended for optimal performance) compiled with
+ the `CONFIG_XDP_SOCKETS=y` option. XDP isn't supported in other operating systems.
+* libknot compiled with XDP support
+* **A multiqueue network card with native XDP support is highly recommended**,
+ otherwise the performance gain will be much lower and you may encounter
+ issues due to XDP emulation.
+ Successfully tested cards:
+
+ * Intel series 700 (driver `i40e`), maximum number of queues per interface is 64.
+ * Intel series 500 (driver `ixgbe`), maximum number of queues per interface is 64.
+ The number of CPUs available has to be at most 64!
+
+
+Set up
+^^^^^^
+.. first parts are mostly copied from knot-dns doc/operations.rst
+
+The server instances need additional Linux **capabilities** during startup.
+(Or you could start them as `root`.)
+Execute command
+
+.. code-block:: bash
+
+ systemctl edit kresd@.service
+
+And insert these lines:
+
+.. code-block:: ini
+
+ [Service]
+ CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_IPC_LOCK CAP_SYS_RESOURCE
+ AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_IPC_LOCK CAP_SYS_RESOURCE
+
+The ``CAP_SYS_RESOURCE`` is only needed on Linux < 5.11.
+
+.. TODO suggest some way for ethtool -L? Perhaps via systemd units?
+
+You want the same number of kresd instances and network **queues** on your card;
+you can use ``ethtool -L`` before the services start.
+With XDP this is more important than with vanilla UDP, as we only support one instance
+per queue and unclaimed queues will fall back to vanilla UDP.
+Ideally you can set these numbers as high as the number of CPUs that you want kresd to use.
+
+Modification of ``/etc/knot-resolver/kresd.conf`` may often be quite simple, for example:
+
+.. code-block:: lua
+
+ net.listen('eth2', 53, { kind = 'xdp' })
+ net.listen('203.0.113.53', 53, { kind = 'dns' })
+
+Note that you want to also keep the vanilla DNS line to service TCP
+and possibly any fallback UDP (e.g. from unclaimed queues).
+XDP listening is in principle done on queues of whole network interfaces
+and the target addresses of incoming packets aren't checked in any way,
+but you are still allowed to specify interface by an address
+(if it's unambiguous at that moment):
+
+.. code-block:: lua
+
+ net.listen('203.0.113.53', 53, { kind = 'xdp' })
+ net.listen('203.0.113.53', 53, { kind = 'dns' })
+
+The default selection of queues is tailored for the usual naming convention:
+``kresd@1.service``, ``kresd@2.service``, ...
+but you can still specify them explicitly, e.g. the default is effectively the same as:
+
+.. code-block:: lua
+
+ net.listen('eth2', 53, { kind = 'xdp', nic_queue = env.SYSTEMD_INSTANCE - 1 })
+
+
+Optimizations
+^^^^^^^^^^^^^
+.. this is basically copied from knot-dns doc/operations.rst
+
+Some helpful commands:
+
+.. code-block:: text
+
+ ethtool -N <interface> rx-flow-hash udp4 sdfn
+ ethtool -N <interface> rx-flow-hash udp6 sdfn
+ ethtool -L <interface> combined <queue-number>
+ ethtool -G <interface> rx <ring-size> tx <ring-size>
+ renice -n 19 -p $(pgrep '^ksoftirqd/[0-9]*$')
+
+.. TODO CPU affinities? `CPUAffinity=%i` in systemd unit sounds good.
+
+
+.. _dns-over-xdp_limitations:
+
+Limitations
+^^^^^^^^^^^
+.. this is basically copied from knot-dns doc/operations.rst
+
+* VLAN segmentation is not supported.
+* MTU higher than 1792 bytes is not supported.
+* Multiple BPF filters per one network device are not supported.
+* Symmetrical routing is required (query source MAC/IP addresses and
+ reply destination MAC/IP addresses are the same).
+* Systems with big-endian byte ordering require special recompilation of libknot.
+* IPv4 header and UDP checksums are not verified on received DNS messages.
+* DNS over XDP traffic is not visible to common system tools (e.g. firewall, tcpdump etc.).
+* BPF filter is not automatically unloaded from the network device. Manual filter unload::
+
+ ip link set dev <interface> xdp off
+
+* Knot Resolver only supports using XDP towards clients currently (not towards upstreams).
+* When starting up an XDP socket you may get a harmless warning::
+
+ libbpf: Kernel error message: XDP program already attached
+
diff --git a/daemon/bindings/worker.c b/daemon/bindings/worker.c
new file mode 100644
index 0000000..d985000
--- /dev/null
+++ b/daemon/bindings/worker.c
@@ -0,0 +1,81 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/bindings/impl.h"
+
+
+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 = the_worker;
+ if (!worker) {
+ return 0;
+ }
+ lua_newtable(L);
+ lua_pushnumber(L, worker->stats.queries);
+ lua_setfield(L, -2, "queries");
+ lua_pushnumber(L, worker->stats.concurrent);
+ lua_setfield(L, -2, "concurrent");
+ lua_pushnumber(L, worker->stats.dropped);
+ lua_setfield(L, -2, "dropped");
+
+ lua_pushnumber(L, worker->stats.timeout);
+ lua_setfield(L, -2, "timeout");
+ 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.ipv4);
+ lua_setfield(L, -2, "ipv4");
+ lua_pushnumber(L, worker->stats.ipv6);
+ lua_setfield(L, -2, "ipv6");
+ lua_pushnumber(L, worker->stats.err_udp);
+ lua_setfield(L, -2, "err_udp");
+ lua_pushnumber(L, worker->stats.err_tcp);
+ lua_setfield(L, -2, "err_tcp");
+ lua_pushnumber(L, worker->stats.err_tls);
+ lua_setfield(L, -2, "err_tls");
+ lua_pushnumber(L, worker->stats.err_http);
+ lua_setfield(L, -2, "err_http");
+
+ /* 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 kr_bindings_worker(lua_State *L)
+{
+ static const luaL_Reg lib[] = {
+ { "stats", wrk_stats },
+ { NULL, NULL }
+ };
+ luaL_register(L, "worker", lib);
+ return 1;
+}
+
diff --git a/daemon/bindings/worker.rst b/daemon/bindings/worker.rst
new file mode 100644
index 0000000..9dfcbe8
--- /dev/null
+++ b/daemon/bindings/worker.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+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.id
+
+ Value from environment variable ``SYSTEMD_INSTANCE``,
+ or if it is not set, :envvar:`PID <worker.pid>` (string).
+
+.. envvar:: worker.pid
+
+ Current worker process PID (number).
+
+.. function:: worker.stats()
+
+ Return table of statistics. See member descriptions in :c:type:`worker_stats`.
+ A few fields are added, mainly from POSIX ``getrusage()``:
+
+ * ``usertime`` and ``systime`` -- CPU time used, in seconds
+ * ``pagefaults`` -- the number of hard page faults, i.e. those that required I/O activity
+ * ``swaps`` -- the number of times the process was “swapped” out of main memory; unused on Linux
+ * ``csw`` -- the number of context switches, both voluntary and involuntary
+ * ``rss`` -- current memory usage in bytes, including whole cache (resident set size)
+
+ Example:
+
+ .. code-block:: lua
+
+ print(worker.stats().concurrent)
+
diff --git a/daemon/cache.test/clear.test.lua b/daemon/cache.test/clear.test.lua
new file mode 100644
index 0000000..e1f6914
--- /dev/null
+++ b/daemon/cache.test/clear.test.lua
@@ -0,0 +1,215 @@
+-- unload modules which are not related to this test
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+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
+-- log_level('debug')
+
+-- 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.remove('.')
+trust_anchors.add('. IN DS 48409 8 2 3D63A0C25BCE86621DE63636F11B35B908EFE8E9381E0E3E9DEFD89EA952C27D')
+
+local check_answer = require('test_utils').check_answer
+
+-- 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 = require('ffi').C.zi_zone_import({ zone_file = 'testroot.zone' })
+ assert(import_res == 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 everything 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
+
+local function test_cache_used(lower, upper)
+ return function()
+ local usage = cache.stats().usage_percent
+ ok(usage >= lower and usage <= upper,
+ string.format('cache percentage usage %.1f is between <%d, %d>', usage, lower, upper))
+ end
+end
+
+return {
+ test_cache_used(0, 1),
+ import_zone,
+ test_cache_used(9, 11),
+ test_exact_match_qtype,
+ test_exact_match_qname,
+ test_callback,
+ import_zone,
+ test_subtree,
+ test_cache_used(9, 11),
+ test_subtree_limit,
+ test_cache_used(5, 8),
+ test_apex,
+ import_zone,
+ test_root,
+ import_zone,
+ test_complete_flush,
+ test_cache_used(0, 1),
+}
diff --git a/daemon/cache.test/insert_ns.test.integr/deckard.yaml b/daemon/cache.test/insert_ns.test.integr/deckard.yaml
new file mode 100644
index 0000000..7f99679
--- /dev/null
+++ b/daemon/cache.test/insert_ns.test.integr/deckard.yaml
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+programs:
+- name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - daemon/cache.test/insert_ns.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
+noclean: True
diff --git a/daemon/cache.test/insert_ns.test.integr/kresd_config.j2 b/daemon/cache.test/insert_ns.test.integr/kresd_config.j2
new file mode 100644
index 0000000..bf2165b
--- /dev/null
+++ b/daemon/cache.test/insert_ns.test.integr/kresd_config.j2
@@ -0,0 +1,89 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+{% for TAF in TRUST_ANCHOR_FILES %}
+trust_anchors.add_file('{{TAF}}')
+{% endfor %}
+
+{% raw %}
+-- insert NS record pointing to a non-delegated DNS server
+cache.open(1*MB)
+cache.clear()
+trust_anchors.remove('.')
+
+local ffi = require('ffi')
+local c = kres.context().cache
+ns_name = todname('ns.example.com')
+local ns_addr = '\1\2\3\4'
+local rr = kres.rrset(ns_name, kres.type.A, kres.class.IN, 2147483647)
+assert(rr:add_rdata(ns_addr, #ns_addr))
+assert(c:insert(rr, nil, ffi.C.KR_RANK_SECURE))
+
+rr_ns = kres.rrset(todname('example.com'), kres.type.NS, kres.class.IN, 3600)
+assert(rr_ns:add_rdata(ns_name, #ns_name))
+assert(c:insert(rr_ns, nil, bit.bor(ffi.C.KR_RANK_AUTH, ffi.C.KR_RANK_INSECURE)))
+
+c:commit()
+assert(cache.count() > 0)
+
+-- from now on queries for domain example.com should go directly to IP addr 1.2.3.4
+
+-- Disable RFC5011 TA update
+if ta_update then
+ modules.unload('ta_update')
+end
+
+-- Disable RFC8145 signaling, scenario doesn't provide expected answers
+if ta_signal_query then
+ modules.unload('ta_signal_query')
+end
+
+-- Disable RFC8109 priming, scenario doesn't provide expected answers
+if priming then
+ modules.unload('priming')
+end
+
+-- Disable this module because it makes one priming query
+if detect_time_skew then
+ modules.unload('detect_time_skew')
+end
+
+_hint_root_file('hints')
+log_level('debug')
+{% endraw %}
+
+net = { '{{SELF_ADDR}}' }
+
+{% if DO_IP6 == "true" %}
+net.ipv6 = true
+{% else %}
+net.ipv6 = false
+{% endif %}
+
+{% if DO_IP4 == "true" %}
+net.ipv4 = true
+{% else %}
+net.ipv4 = false
+{% endif %}
+
+{% if QMIN == "false" %}
+option('NO_MINIMIZE', true)
+{% else %}
+option('NO_MINIMIZE', false)
+{% endif %}
+
+
+-- Self-checks on globals
+assert(help() ~= nil)
+assert(worker.id ~= nil)
+-- Self-checks on facilities
+assert(cache.stats() ~= nil)
+assert(cache.backends() ~= nil)
+assert(worker.stats() ~= nil)
+assert(net.interfaces() ~= nil)
+-- Self-checks on loaded stuff
+assert(net.list()[1].transport.ip == '{{SELF_ADDR}}')
+assert(#modules.list() > 0)
+-- Self-check timers
+ev = event.recurrent(1 * sec, function (ev) return 1 end)
+event.cancel(ev)
+ev = event.after(0, function (ev) return 1 end)
diff --git a/daemon/cache.test/insert_ns.test.integr/nondelegated_auth.rpl b/daemon/cache.test/insert_ns.test.integr/nondelegated_auth.rpl
new file mode 100644
index 0000000..6a0a32a
--- /dev/null
+++ b/daemon/cache.test/insert_ns.test.integr/nondelegated_auth.rpl
@@ -0,0 +1,59 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+; target-fetch-policy: "0 0 0 0 0"
+; name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+ do-ip6: no
+CONFIG_END
+
+SCENARIO_BEGIN Delegation explicitly added into cache must be followed
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH flags rcode question
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+ENTRY_END
+
+SCENARIO_END
diff --git a/daemon/cache.test/testroot.zone b/daemon/cache.test/testroot.zone
new file mode 100644
index 0000000..2799d33
--- /dev/null
+++ b/daemon/cache.test/testroot.zone
@@ -0,0 +1,1257 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; 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..c790cc4
--- /dev/null
+++ b/daemon/cache.test/testroot.zone.unsigned
@@ -0,0 +1,216 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+. 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/engine.c b/daemon/engine.c
new file mode 100644
index 0000000..1d387ea
--- /dev/null
+++ b/daemon/engine.c
@@ -0,0 +1,896 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <contrib/cleanup.h>
+#include <ccan/json/json.h>
+#include <ccan/asprintf/asprintf.h>
+#include <dlfcn.h>
+#include <uv.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/param.h>
+#include <libzscanner/scanner.h>
+#include <sys/un.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+#include "daemon/bindings/impl.h"
+
+#include "kresconfig.h"
+#include "daemon/engine.h"
+#include "daemon/ffimodule.h"
+#include "lib/selection.h"
+#include "lib/cache/api.h"
+#include "lib/defines.h"
+#include "lib/cache/cdb_lmdb.h"
+#include "lib/dnssec/ta.h"
+#include "lib/log.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
+ #if 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 Maximum number of incomplete TCP connections in queue.
+* Default is from empirical testing - in our case, more isn't necessarily better.
+* See https://gitlab.nic.cz/knot/knot-resolver/-/merge_requests/968
+* */
+#ifndef TCP_BACKLOG_DEFAULT
+#define TCP_BACKLOG_DEFAULT 128
+#endif
+
+/* 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))
+
+/*
+ * Global bindings.
+ */
+struct args *the_args;
+
+
+/** 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"
+ "log_level(level)\n logging level (crit, err, warning, notice, info or debug)\n"
+ "log_target(target)\n logging target (syslog, stderr, stdout)\n"
+ "log_groups(groups)\n turn on debug log for selected groups\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"
+ "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"
+ "debugging\n debugging configuration\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_error_p(L, "user(user[, group])");
+
+ /* Fetch UID/GID based on string identifiers. */
+ struct passwd *user_pw = getpwnam(lua_tostring(L, 1));
+ if (!user_pw)
+ lua_error_p(L, "invalid user name");
+ 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_error_p(L, "invalid group name");
+ gid = group_pw->gr_gid;
+ }
+ /* Drop privileges */
+ bool ret = update_privileges(uid, gid);
+ if (!ret) {
+ lua_error_maybe(L, errno);
+ }
+ lua_pushboolean(L, ret);
+ return 1;
+}
+
+/** Quit current executable. */
+static int l_quit(lua_State *L)
+{
+ engine_stop(the_worker->engine);
+ return 0;
+}
+
+/** Toggle verbose mode. */
+static int l_verbose(lua_State *L)
+{
+ kr_log_deprecate(SYSTEM, "use log_level() instead of verbose()\n");
+
+ if (lua_isboolean(L, 1) || lua_isnumber(L, 1)) {
+ kr_log_level_set(lua_toboolean(L, 1) == true ? LOG_DEBUG : LOG_DEFAULT_LEVEL);
+ }
+
+ lua_pushboolean(L, kr_log_level == LOG_DEBUG);
+ return 1;
+}
+
+static int l_log_level(lua_State *L)
+{
+ const int params = lua_gettop(L);
+ if (params > 1) {
+ goto bad_call;
+ } else if (params == 1) { // set
+ const char *lvl_str = lua_tostring(L, 1);
+ if (!lvl_str)
+ goto bad_call;
+ kr_log_level_t lvl = kr_log_name2level(lvl_str);
+ if (lvl < 0)
+ lua_error_p(L, "unknown log level '%s'", lvl_str);
+ kr_log_level_set(lvl);
+ }
+ // get
+ lua_pushstring(L, kr_log_level2name(kr_log_level));
+ return 1;
+bad_call:
+ lua_error_p(L, "takes one string parameter or nothing");
+}
+
+static int l_log_target(lua_State *L)
+{
+ const int params = lua_gettop(L);
+ if (params > 1)
+ goto bad_call;
+ // set
+ if (params == 1) {
+ const char *t_str = lua_tostring(L, 1);
+ if (!t_str)
+ goto bad_call;
+ kr_log_target_t t;
+ if (strcmp(t_str, "syslog") == 0) {
+ t = LOG_TARGET_SYSLOG;
+ } else if (strcmp(t_str, "stdout") == 0) {
+ t = LOG_TARGET_STDOUT;
+ } else if (strcmp(t_str, "stderr") == 0) {
+ t = LOG_TARGET_STDERR;
+ } else {
+ lua_error_p(L, "unknown log target '%s'", t_str);
+ }
+ kr_log_target_set(t);
+ }
+ // get
+ const char *t_str = NULL;
+ switch (kr_log_target) {
+ case LOG_TARGET_SYSLOG: t_str = "syslog"; break;
+ case LOG_TARGET_STDERR: t_str = "stderr"; break;
+ case LOG_TARGET_STDOUT: t_str = "stdout"; break;
+ } // -Wswitch-enum
+ lua_pushstring(L, t_str);
+ return 1;
+bad_call:
+ lua_error_p(L, "takes one string parameter or nothing");
+}
+
+static int l_log_groups(lua_State *L)
+{
+ const int params = lua_gettop(L);
+ if (params > 1)
+ goto bad_call;
+ if (params == 1) { // set
+ if (!lua_istable(L, 1))
+ goto bad_call;
+ kr_log_group_reset();
+
+ int idx = 1;
+ lua_pushnil(L);
+ while (lua_next(L, 1) != 0) {
+ const char *grp_str = lua_tostring(L, -1);
+ if (!grp_str)
+ goto bad_call;
+
+ enum kr_log_group grp = kr_log_name2grp(grp_str);
+ if (grp >= 0) {
+ kr_log_group_add(grp);
+ } else {
+ kr_log_warning(SYSTEM, "WARNING: unknown log group '%s'\n", lua_tostring(L, -1));
+ }
+
+ ++idx;
+ lua_pop(L, 1);
+ }
+ }
+ // get
+ lua_newtable(L);
+ int i = 1;
+ for (enum kr_log_group grp = LOG_GRP_SYSTEM; grp < LOG_GRP_REQDBG; grp++) {
+ const char *name = kr_log_grp2name(grp);
+ if (kr_fails_assert(name))
+ continue;
+ if (kr_log_group_is_set(grp)) {
+ lua_pushinteger(L, i);
+ lua_pushstring(L, name);
+ lua_settable(L, -3);
+ i++;
+ }
+ }
+ return 1;
+bad_call:
+ lua_error_p(L, "takes a table of string groups as parameter or nothing");
+}
+
+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 = the_worker->engine;
+ if (lua_gettop(L) == 0) {
+ lua_pushstring(L, engine_get_hostname(engine));
+ return 1;
+ }
+ if ((lua_gettop(L) != 1) || !lua_isstring(L, 1))
+ lua_error_p(L, "hostname takes at most one parameter: (\"fqdn\")");
+
+ if (engine_set_hostname(engine, lua_tostring(L, 1)) != 0)
+ lua_error_p(L, "setting hostname failed");
+
+ 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;
+}
+
+/** Load root hints from zonefile. */
+static int l_hint_root_file(lua_State *L)
+{
+ struct kr_context *ctx = &the_worker->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_error_p(L, "error when opening '%s': %s", file, err);
+ } 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_objlen(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_error_p(L, "a JSON string is required");
+
+ const char *json_str = lua_tostring(L, 1);
+ JsonNode *root_node = json_decode(json_str);
+
+ if (!root_node)
+ lua_error_p(L, "invalid JSON string");
+ l_unpack_json(L, root_node);
+ json_delete(root_node);
+
+ return 1;
+}
+
+/*
+ * Engine API.
+ */
+
+static int init_resolver(struct engine *engine)
+{
+ /* Note: whole *engine had been zeroed by engine_init(). */
+ struct kr_context * const ctx = &engine->resolver;
+ /* Default options (request flags). */
+ ctx->options.REORDER_RR = true;
+
+ /* Open resolution context */
+ ctx->trust_anchors = trie_create(NULL);
+ ctx->negative_anchors = trie_create(NULL);
+ ctx->vld_limit_crypto = KR_VLD_LIMIT_CRYPTO_DEFAULT;
+ ctx->pool = engine->pool;
+ ctx->modules = &engine->modules;
+ ctx->cache_rtt_tout_retry_interval = KR_NS_TIMEOUT_RETRY_INTERVAL;
+ /* Create OPT RR */
+ ctx->downstream_opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t));
+ ctx->upstream_opt_rr = mm_alloc(engine->pool, sizeof(knot_rrset_t));
+ if (!ctx->downstream_opt_rr || !ctx->upstream_opt_rr) {
+ return kr_error(ENOMEM);
+ }
+ knot_edns_init(ctx->downstream_opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool);
+ knot_edns_init(ctx->upstream_opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, engine->pool);
+ /* Use default TLS padding */
+ ctx->tls_padding = -1;
+ /* Empty init; filled via ./lua/postconfig.lua */
+ kr_zonecut_init(&ctx->root_hints, (const uint8_t *)"", engine->pool);
+ lru_create(&ctx->cache_cookie, LRU_COOKIES_SIZE, NULL, 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. */
+ 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_verbose);
+ lua_setglobal(engine->L, "verbose");
+ lua_pushcfunction(engine->L, l_log_level);
+ lua_setglobal(engine->L, "log_level");
+ lua_pushcfunction(engine->L, l_log_target);
+ lua_setglobal(engine->L, "log_target");
+ lua_pushcfunction(engine->L, l_log_groups);
+ lua_setglobal(engine->L, "log_groups");
+ lua_pushcfunction(engine->L, l_setuser);
+ lua_setglobal(engine->L, "user");
+ 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");
+ /* Random number generator */
+ lua_getfield(engine->L, LUA_GLOBALSINDEX, "math");
+ lua_getfield(engine->L, -1, "randomseed");
+ lua_remove(engine->L, -2);
+ lua_Number seed = kr_rand_bytes(sizeof(lua_Number));
+ lua_pushnumber(engine->L, seed);
+ lua_call(engine->L, 1, 0);
+ 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
+ );
+ if (kr_fails_assert(ret > 0))
+ return;
+
+ ret = luaL_loadstring(engine->L, snippet);
+ if (kr_fails_assert(ret == 0)) {
+ free(snippet);
+ return;
+ }
+ lua_call(engine->L, 0, 0);
+ free(snippet);
+}
+
+int init_lua(struct engine *engine) {
+ if (!engine) {
+ return kr_error(EINVAL);
+ }
+
+ /* Use libdir 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",
+ LIBDIR, 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;
+}
+
+
+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);
+
+ /* Initialize lua */
+ ret = init_lua(engine);
+ if (ret != 0) {
+ engine_deinit(engine);
+ return ret;
+ }
+
+ return ret;
+}
+
+/** Unregister a (found) module */
+static void engine_unload(struct engine *engine, struct kr_module *module)
+{
+ auto_free char *name = module->name ? strdup(module->name) : NULL;
+ kr_module_unload(module); /* beware: lua/C mix, could be confusing */
+ /* Clear in Lua world, but not for embedded modules ('cache' in particular). */
+ if (name && !kr_module_get_embedded(name)) {
+ lua_pushnil(engine->L);
+ lua_setglobal(engine->L, name);
+ }
+ free(module);
+}
+
+void engine_deinit(struct engine *engine)
+{
+ if (!engine || kr_fails_assert(engine->L))
+ return;
+ /* Only close sockets and services; no need to clean up mempool. */
+
+ /* Network deinit is split up. We first need to stop listening,
+ * then we can unload modules during which we still want
+ * e.g. the endpoint kind registry to work (inside ->net),
+ * and this registry deinitialization uses the lua state. */
+ network_close_force(&engine->net);
+ for (size_t i = 0; i < engine->modules.len; ++i) {
+ engine_unload(engine, engine->modules.at[i]);
+ }
+ kr_zonecut_deinit(&engine->resolver.root_hints);
+ kr_cache_close(&engine->resolver.cache);
+
+ /* The LRUs are currently malloc-ated and need to be freed. */
+ lru_free(engine->resolver.cache_cookie);
+
+ network_deinit(&engine->net);
+ ffimodule_deinit(engine->L);
+ lua_close(engine->L);
+
+ /* Free data structures */
+ array_clear(engine->modules);
+ array_clear(engine->backends);
+ kr_ta_clear(engine->resolver.trust_anchors);
+ trie_free(engine->resolver.trust_anchors);
+ kr_ta_clear(engine->resolver.negative_anchors);
+ trie_free(engine->resolver.negative_anchors);
+ free(engine->hostname);
+}
+
+int engine_pcall(lua_State *L, int argc)
+{
+ 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_load_sandbox(struct engine *engine)
+{
+ /* Init environment */
+ int ret = luaL_dofile(engine->L, LIBDIR "/sandbox.lua");
+ if (ret != 0) {
+ kr_log_error(SYSTEM, "error %s\n", lua_tostring(engine->L, -1));
+ lua_pop(engine->L, 1);
+ return kr_error(ENOEXEC);
+ }
+ ret = ffimodule_init(engine->L);
+ return ret;
+}
+
+int engine_loadconf(struct engine *engine, const char *config_path)
+{
+ if (kr_fails_assert(config_path))
+ return kr_error(EINVAL);
+
+ char cwd[PATH_MAX];
+ get_workdir(cwd, sizeof(cwd));
+ kr_log_debug(SYSTEM, "loading config '%s' (workdir '%s')\n", config_path, cwd);
+
+ int ret = luaL_dofile(engine->L, config_path);
+ if (ret != 0) {
+ kr_log_error(SYSTEM, "error while loading config: "
+ "%s (workdir '%s')\n", lua_tostring(engine->L, -1), cwd);
+ lua_pop(engine->L, 1);
+ }
+ return ret;
+}
+
+int engine_start(struct engine *engine)
+{
+ /* Clean up stack */
+ lua_settop(engine->L, 0);
+
+ return kr_ok();
+}
+
+void engine_stop(struct engine *engine)
+{
+ if (!engine) {
+ return;
+ }
+ uv_stop(uv_default_loop());
+}
+
+/** @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 (kr_fails_assert(engine && name))
+ 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; /*< some outside modules may still use this value */
+
+ int ret = kr_module_load(module, name, LIBDIR "/kres_modules");
+ if (ret == 0) {
+ /* We have a C module, loaded and init() was called.
+ * Now we need to prepare the lua side. */
+ lua_State *L = engine->L;
+ lua_getglobal(L, "modules_create_table_for_c");
+ lua_pushpointer(L, module);
+ if (lua_isnil(L, -2)) {
+ /* When loading the three embedded modules, we don't
+ * have the "modules_*" lua function yet, but fortunately
+ * we don't need it there. Let's just check they're embedded.
+ * TODO: solve this better *without* breaking stuff. */
+ lua_pop(L, 2);
+ if (module->lib != RTLD_DEFAULT) {
+ ret = kr_error(1);
+ lua_pushliteral(L, "missing modules_create_table_for_c()");
+ }
+ } else {
+ ret = engine_pcall(L, 1);
+ }
+ if (kr_fails_assert(ret == 0)) { /* probably not critical, but weird */
+ kr_log_error(SYSTEM, "internal error when loading C module %s: %s\n",
+ module->name, lua_tostring(L, -1));
+ lua_pop(L, 1);
+ }
+
+ } else if (ret == kr_error(ENOENT)) {
+ /* No luck with C module, so try to load and .init() lua module. */
+ ret = ffimodule_register_lua(engine, module, name);
+ if (ret != 0) {
+ kr_log_error(SYSTEM, "failed to load module '%s'\n", name);
+ }
+
+ } else if (ret == kr_error(ENOTSUP)) {
+ /* Print a more helpful message when module is linked against an old resolver ABI. */
+ kr_log_error(SYSTEM, "module '%s' links to unsupported ABI, please rebuild it\n", name);
+ }
+
+ if (ret != 0) {
+ engine_unload(engine, module);
+ return ret;
+ }
+
+ /* Push to the right place in engine->modules */
+ if (array_push(engine->modules, module) < 0) {
+ engine_unload(engine, module);
+ return kr_error(ENOMEM);
+ }
+ 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 kr_ok();
+}
+
+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);
+}
+
diff --git a/daemon/engine.h b/daemon/engine.h
new file mode 100644
index 0000000..63accd3
--- /dev/null
+++ b/daemon/engine.h
@@ -0,0 +1,84 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#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"
+
+struct engine {
+ struct kr_context resolver;
+ struct network net;
+ module_array_t modules;
+ array_t(const struct kr_cdb_api *) backends;
+ knot_mm_t *pool;
+ char *hostname;
+ struct lua_State *L;
+};
+
+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_load_sandbox(struct engine *engine);
+int engine_loadconf(struct engine *engine, const char *config_path);
+
+/** 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);
+
+/** Set/get the per engine hostname */
+char *engine_get_hostname(struct engine *engine);
+int engine_set_hostname(struct engine *engine, const char *hostname);
+
+/** 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);
+
+/* @internal Array of ip address shorthand. */
+typedef array_t(char*) addr_array_t;
+
+typedef array_t(const char*) config_array_t;
+
+typedef struct {
+ int fd;
+ endpoint_flags_t flags; /**< .sock_type isn't meaningful here */
+} flagged_fd_t;
+typedef array_t(flagged_fd_t) flagged_fd_array_t;
+
+struct args {
+ addr_array_t addrs, addrs_tls;
+ flagged_fd_array_t fds;
+ int control_fd;
+ int forks;
+ config_array_t config;
+ const char *rundir;
+ bool interactive;
+ bool quiet;
+ bool tty_binary_output;
+};
+
+/** Pointer to kresd arguments. */
+KR_EXPORT extern struct args *the_args;
diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c
new file mode 100644
index 0000000..4206b4c
--- /dev/null
+++ b/daemon/ffimodule.c
@@ -0,0 +1,304 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <uv.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+#include "daemon/bindings/impl.h"
+#include "daemon/engine.h"
+#include "daemon/ffimodule.h"
+#include "daemon/worker.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+/** @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 */
+};
+
+/** Lua registry indices for functions that wrap layer callbacks (shared by all lua modules). */
+static int l_ffi_wrap_slots[SLOT_count] = { 0 };
+
+/** @internal Continue with coroutine. */
+static void l_ffi_resume_cb(uv_idle_t *check)
+{
+ lua_State *L = check->data;
+ int status = lua_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);
+}
+
+/** Common part of calling modname.(de)init in lua.
+ * The function to call should be on top of the stack and it gets popped. */
+static int l_ffi_modcb(lua_State *L, struct kr_module *module)
+{
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1); /* .(de)init == nil, maybe even the module table doesn't exist */
+ return kr_ok();
+ }
+ lua_getglobal(L, "modules_ffi_wrap_modcb");
+ lua_insert(L, -2); /* swap with .(de)init */
+ lua_pushpointer(L, module);
+ if (lua_pcall(L, 2, 0, 0) == 0)
+ return kr_ok();
+ kr_log_error(SYSTEM, "error: %s\n", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ return kr_error(1);
+}
+
+static int l_ffi_deinit(struct kr_module *module)
+{
+ /* Call .deinit(), if it exists. */
+ lua_State *L = the_worker->engine->L;
+ lua_getglobal(L, module->name);
+ lua_getfield(L, -1, "deinit");
+ const int ret = l_ffi_modcb(L, module);
+ lua_pop(L, 1); /* the module's table */
+
+ const kr_layer_api_t *api = module->layer;
+ 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_const(api);
+ return ret;
+}
+
+kr_layer_t kr_layer_t_static;
+
+/** @internal Helper for calling a layer Lua function by e.g. SLOT_begin. */
+static int l_ffi_call_layer(kr_layer_t *ctx, int slot_ix)
+{
+ const int wrap_slot = l_ffi_wrap_slots[slot_ix];
+ const int cb_slot = ctx->api->cb_slots[slot_ix];
+ kr_require(wrap_slot > 0 && cb_slot > 0);
+ lua_State *L = the_worker->engine->L;
+ lua_rawgeti(L, LUA_REGISTRYINDEX, wrap_slot);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, cb_slot);
+ /* We pass the content of *ctx via a global structure to avoid
+ * lua (full) userdata, as that's relatively expensive (GC-allocated).
+ * Performance: copying isn't ideal, but it's not visible in profiles. */
+ memcpy(&kr_layer_t_static, ctx, sizeof(*ctx));
+
+ int ret = lua_pcall(L, 1, 1, 0);
+ /* Handle result of the pcall.
+ * Default state: ctx->req->state seems safer than ctx->state,
+ * in case the pcall touched req->state. */
+ int state = ctx->req->state;
+ if (ret) { /* Exception or another lua problem. */
+ state = KR_STATE_FAIL;
+ kr_log_error(SYSTEM, "error: %s\n", lua_tostring(L, -1));
+
+ } else if (lua_isnumber(L, -1)) { /* Explicitly returned state. */
+ state = lua_tointeger(L, -1);
+ if (!kr_state_consistent(state)) {
+ kr_log_error(SYSTEM, "error: nonsense state returned from lua module layer: %d\n",
+ state);
+ state = KR_STATE_FAIL;
+ }
+
+ } else if (lua_isnil(L, -1)) { /* Don't change state. */
+
+ } else if (kr_fails_assert(!lua_isthread(L, -1))) { /* Continuations */
+ /* TODO: unused, possibly in a bad shape. Meant KR_STATE_YIELD? */
+ if (l_ffi_defer(lua_tothread(L, -1)) != 0)
+ state = KR_STATE_FAIL;
+
+ } else { /* Nonsense returned. */
+ state = KR_STATE_FAIL;
+ kr_log_error(SYSTEM, "error: nonsense returned from lua module layer: %s\n",
+ lua_tostring(L, -1));
+ /* Unfortunately we can't easily get name of the module/function here. */
+ }
+ lua_pop(L, 1);
+ return state;
+}
+
+static int l_ffi_layer_begin(kr_layer_t *ctx)
+{
+ return l_ffi_call_layer(ctx, SLOT_begin);
+}
+
+static int l_ffi_layer_reset(kr_layer_t *ctx)
+{
+ return l_ffi_call_layer(ctx, SLOT_reset);
+}
+
+static int l_ffi_layer_finish(kr_layer_t *ctx)
+{
+ ctx->pkt = ctx->req->answer;
+ return l_ffi_call_layer(ctx, SLOT_finish);
+}
+
+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 */
+ }
+ ctx->pkt = pkt;
+ return l_ffi_call_layer(ctx, SLOT_consume);
+}
+
+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, skip */
+ }
+ ctx->pkt = pkt;
+ return l_ffi_call_layer(ctx, SLOT_produce);
+}
+
+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, skip */
+ }
+ ctx->pkt = pkt;
+ ctx->dst = dst;
+ ctx->is_stream = (type == SOCK_STREAM);
+ return l_ffi_call_layer(ctx, SLOT_checkout);
+}
+
+static int l_ffi_layer_answer_finalize(kr_layer_t *ctx)
+{
+ return l_ffi_call_layer(ctx, SLOT_answer_finalize);
+}
+
+int ffimodule_init(lua_State *L)
+{
+ /* Wrappers defined in ./lua/sandbox.lua */
+ /* for API: (int state, kr_request_t *req) */
+ lua_getglobal(L, "modules_ffi_layer_wrap1");
+ const int wrap1 = luaL_ref(L, LUA_REGISTRYINDEX);
+ /* for API: (int state, kr_request_t *req, knot_pkt_t *) */
+ lua_getglobal(L, "modules_ffi_layer_wrap2");
+ const int wrap2 = luaL_ref(L, LUA_REGISTRYINDEX);
+ lua_getglobal(L, "modules_ffi_layer_wrap_checkout");
+ const int wrap_checkout = luaL_ref(L, LUA_REGISTRYINDEX);
+ if (wrap1 == LUA_REFNIL || wrap2 == LUA_REFNIL || wrap_checkout == LUA_REFNIL) {
+ return kr_error(ENOENT);
+ }
+
+ const int slots[SLOT_count] = {
+ [SLOT_begin] = wrap1,
+ [SLOT_reset] = wrap1,
+ [SLOT_finish] = wrap2,
+ [SLOT_consume] = wrap2,
+ [SLOT_produce] = wrap2,
+ [SLOT_checkout] = wrap_checkout,
+ [SLOT_answer_finalize] = wrap1,
+ };
+ memcpy(l_ffi_wrap_slots, slots, sizeof(l_ffi_wrap_slots));
+ return kr_ok();
+}
+void ffimodule_deinit(lua_State *L)
+{
+ /* Unref each wrapper function from lua.
+ * It's probably useless, as we're about to destroy lua_State, but... */
+ const int wrapsIndices[] = {
+ SLOT_begin,
+ SLOT_consume,
+ SLOT_checkout,
+ };
+ for (int i = 0; i < sizeof(wrapsIndices) / sizeof(wrapsIndices[0]); ++i) {
+ luaL_unref(L, LUA_REGISTRYINDEX, l_ffi_wrap_slots[wrapsIndices[i]]);
+ }
+}
+
+/** @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 * sizeof(module->layer->cb_slots[0]));
+ kr_layer_api_t *api = calloc(1, api_length);
+ if (api) {
+ 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);
+ }
+ return api;
+}
+
+#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_pushfstring(L, "kres_modules.%s", name);
+ if (lua_pcall(L, 1, LUA_MULTRET, 0) != 0) {
+ kr_log_error(SYSTEM, "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->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_create(L, module);
+ }
+ lua_pop(L, 1); /* .layer table */
+
+ /* Now call .init(), if it exists. */
+ lua_getfield(L, -1, "init");
+ const int ret = l_ffi_modcb(L, module);
+ lua_pop(L, 1); /* the module's table */
+ return ret;
+}
diff --git a/daemon/ffimodule.h b/daemon/ffimodule.h
new file mode 100644
index 0000000..beb1bf1
--- /dev/null
+++ b/daemon/ffimodule.h
@@ -0,0 +1,36 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "lib/defines.h"
+#include "lib/layer.h"
+#include <lua.h>
+struct engine;
+struct kr_module;
+
+/**
+ * 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);
+
+int ffimodule_init(lua_State *L);
+void ffimodule_deinit(lua_State *L);
+
+/** Static storage for faster passing of layer function parameters to lua callbacks.
+ *
+ * We don't need to declare it in a header, but let's give it visibility. */
+KR_EXPORT extern kr_layer_t kr_layer_t_static;
+
diff --git a/daemon/http.c b/daemon/http.c
new file mode 100644
index 0000000..0c6f361
--- /dev/null
+++ b/daemon/http.c
@@ -0,0 +1,953 @@
+/*
+ * Copyright (C) CZ.NIC, z.s.p.o
+ *
+ * Initial Author: Jan Hák <jan.hak@nic.cz>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "daemon/io.h"
+#include "daemon/http.h"
+#include "daemon/worker.h"
+#include "daemon/session.h"
+#include "lib/layer/iterate.h" /* kr_response_classify */
+#include "lib/cache/util.h"
+#include "lib/generic/array.h"
+
+#include "contrib/cleanup.h"
+#include "contrib/base64url.h"
+
+/** Makes a `nghttp2_nv`. `K` is the key, `KS` is the key length,
+ * `V` is the value, `VS` is the value length. */
+#define MAKE_NV(K, KS, V, VS) \
+ (nghttp2_nv) { (uint8_t *)(K), (uint8_t *)(V), (KS), (VS), NGHTTP2_NV_FLAG_NONE }
+
+/** Makes a `nghttp2_nv` with static data. `K` is the key,
+ * `V` is the value. Both `K` and `V` MUST be string literals. */
+#define MAKE_STATIC_NV(K, V) \
+ MAKE_NV((K), sizeof(K) - 1, (V), sizeof(V) - 1)
+
+/** Makes a `nghttp2_nv` with a static key. `K` is the key,
+ * `V` is the value, `VS` is the value length. `K` MUST be a string literal. */
+#define MAKE_STATIC_KEY_NV(K, V, VS) \
+ MAKE_NV((K), sizeof(K) - 1, (V), (VS))
+
+/* Use same maximum as for tcp_pipeline_max. */
+#define HTTP_MAX_CONCURRENT_STREAMS UINT16_MAX
+
+#define HTTP_MAX_HEADER_IN_SIZE 1024
+
+#define HTTP_FRAME_HDLEN 9
+#define HTTP_FRAME_PADLEN 1
+
+#define MAX_DECIMAL_LENGTH(VT) ((CHAR_BIT * sizeof(VT) / 3) + 3)
+
+struct http_data {
+ uint8_t *buf;
+ size_t len;
+ size_t pos;
+ uint32_t ttl;
+ uv_write_cb on_write;
+ uv_write_t *req;
+};
+
+typedef array_t(nghttp2_nv) nghttp2_array_t;
+
+static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
+ nghttp2_data_provider *prov, enum http_status status);
+static int http_send_response_rst_stream(struct http_ctx *ctx, int32_t stream_id,
+ nghttp2_data_provider *prov, enum http_status status);
+
+/** Checks if `status` has the correct `category`.
+ * E.g. status 200 has category 2, status 404 has category 4, 501 has category 5 etc. */
+static inline bool http_status_has_category(enum http_status status, int category)
+{
+ return status / 100 == category;
+}
+
+/*
+ * Write HTTP/2 protocol data to underlying transport layer.
+ */
+static ssize_t send_callback(nghttp2_session *h2, const uint8_t *data, size_t length,
+ int flags, void *user_data)
+{
+ struct http_ctx *ctx = (struct http_ctx *)user_data;
+ return ctx->send_cb(data, length, ctx->session);
+}
+
+/*
+ * Sets the HTTP status of the specified `context`, but only if its status has
+ * not already been changed to an unsuccessful one.
+ */
+static inline void set_status(struct http_ctx *ctx, enum http_status status)
+{
+ if (http_status_has_category(ctx->status, 2))
+ ctx->status = status;
+}
+
+/*
+ * Send padding length (if greater than zero).
+ */
+static int send_padlen(struct http_ctx *ctx, size_t padlen)
+{
+ int ret;
+ uint8_t buf;
+
+ if (padlen == 0)
+ return 0;
+
+ buf = (uint8_t)padlen;
+ ret = ctx->send_cb(&buf, HTTP_FRAME_PADLEN, ctx->session);
+ if (ret < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ return 0;
+}
+
+/*
+ * Send HTTP/2 zero-byte padding.
+ *
+ * This sends only padlen-1 bytes of padding (if any), since padlen itself
+ * (already sent) is also considered padding. Refer to RFC7540, section 6.1
+ */
+static int send_padding(struct http_ctx *ctx, uint8_t padlen)
+{
+ static const uint8_t buf[UINT8_MAX];
+ int ret;
+
+ if (padlen <= 1)
+ return 0;
+
+ ret = ctx->send_cb(buf, padlen - 1, ctx->session);
+ if (ret < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ return 0;
+}
+
+/*
+ * Write entire DATA frame to underlying transport layer.
+ *
+ * This function reads directly from data provider to avoid copying packet wire buffer.
+ */
+static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const uint8_t *framehd,
+ size_t length, nghttp2_data_source *source, void *user_data)
+{
+ struct http_data *data;
+ int ret;
+ struct http_ctx *ctx;
+
+ ctx = (struct http_ctx *)user_data;
+ data = (struct http_data*)source->ptr;
+
+ ret = ctx->send_cb(framehd, HTTP_FRAME_HDLEN, ctx->session);
+ if (ret < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ ret = send_padlen(ctx, frame->data.padlen);
+ if (ret < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ ret = ctx->send_cb(data->buf + data->pos, length, ctx->session);
+ if (ret < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ data->pos += length;
+ if (kr_fails_assert(data->pos <= data->len))
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ ret = send_padding(ctx, (uint8_t)frame->data.padlen);
+ if (ret < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ return 0;
+}
+
+/*
+ * Check endpoint and uri path
+ */
+static int check_uri(const char* path)
+{
+ static const char *endpoints[] = {"dns-query", "doh"};
+ ssize_t endpoint_len;
+ ssize_t ret;
+
+ if (!path)
+ return kr_error(EINVAL);
+
+ char *query_mark = strstr(path, "?");
+
+ /* calculating of endpoint_len - for POST or GET method */
+ endpoint_len = (query_mark) ? query_mark - path - 1 : strlen(path) - 1;
+
+ /* check endpoint */
+ ret = -1;
+ for(int i = 0; i < sizeof(endpoints)/sizeof(*endpoints); i++)
+ {
+ if (strlen(endpoints[i]) != endpoint_len)
+ continue;
+ ret = strncmp(path + 1, endpoints[i], strlen(endpoints[i]));
+ if (!ret)
+ break;
+ }
+
+ return (ret) ? kr_error(ENOENT) : kr_ok();
+}
+
+static kr_http_header_array_t *headers_dup(kr_http_header_array_t *src)
+{
+ kr_http_header_array_t *dst = malloc(sizeof(kr_http_header_array_t));
+ kr_require(dst);
+ array_init(*dst);
+ for (size_t i = 0; i < src->len; i++) {
+ struct kr_http_header_array_entry *src_entry = &src->at[i];
+ struct kr_http_header_array_entry dst_entry = {
+ .name = strdup(src_entry->name),
+ .value = strdup(src_entry->value)
+ };
+ array_push(*dst, dst_entry);
+ }
+
+ return dst;
+}
+
+/*
+ * Process a query from URI path if there's base64url encoded dns variable.
+ */
+static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stream_id)
+{
+ if (!ctx || !path)
+ return kr_error(EINVAL);
+
+ static const char key[] = "dns=";
+ static const char *delim = "&";
+ char *beg, *end;
+ uint8_t *dest;
+ uint32_t remaining;
+
+ char *query_mark = strstr(path, "?");
+ if (!query_mark || strlen(query_mark) == 0) /* no parameters in path */
+ return kr_error(EINVAL);
+
+ /* go over key:value pair */
+ for (beg = strtok(query_mark + 1, delim); beg != NULL; beg = strtok(NULL, delim)) {
+ if (!strncmp(beg, key, 4)) /* dns variable in path found */
+ break;
+ }
+
+ if (!beg) /* no dns variable in path */
+ return kr_error(EINVAL);
+
+ beg += sizeof(key) - 1;
+ end = strchr(beg, '&');
+ if (end == NULL)
+ end = beg + strlen(beg);
+
+ ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */
+ remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
+ dest = ctx->buf + ctx->buf_pos;
+
+ /* Decode dns message from the parameter */
+ int ret = kr_base64url_decode((uint8_t*)beg, end - beg, dest, remaining);
+ if (ret < 0) {
+ ctx->buf_pos = 0;
+ kr_log_debug(DOH, "[%p] base64url decode failed %s\n", (void *)ctx->h2, kr_strerror(ret));
+ return ret;
+ }
+
+ ctx->buf_pos += ret;
+
+ struct http_stream stream = {
+ .id = stream_id,
+ .headers = headers_dup(ctx->headers)
+ };
+ queue_push(ctx->streams, stream);
+
+ return kr_ok();
+}
+
+static void refuse_stream(nghttp2_session *h2, int32_t stream_id)
+{
+ nghttp2_submit_rst_stream(
+ h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_REFUSED_STREAM);
+}
+
+void http_free_headers(kr_http_header_array_t *headers)
+{
+ if (headers == NULL)
+ return;
+
+ for (int i = 0; i < headers->len; i++) {
+ free(headers->at[i].name);
+ free(headers->at[i].value);
+ }
+ array_clear(*headers);
+ free(headers);
+}
+/* Return the http ctx into a pristine state in which no stream is being processed. */
+static void http_cleanup_stream(struct http_ctx *ctx)
+{
+ ctx->incomplete_stream = -1;
+ ctx->current_method = HTTP_METHOD_NONE;
+ free(ctx->uri_path);
+ ctx->uri_path = NULL;
+ http_free_headers(ctx->headers);
+ ctx->headers = NULL;
+}
+
+/*
+ * Save stream id from first header's frame.
+ *
+ * We don't support interweaving from different streams. To successfully parse
+ * multiple subsequent streams, each one must be fully received before processing
+ * a new stream.
+ */
+static int begin_headers_callback(nghttp2_session *h2, const nghttp2_frame *frame,
+ void *user_data)
+{
+ struct http_ctx *ctx = (struct http_ctx *)user_data;
+ int32_t stream_id = frame->hd.stream_id;
+
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ return 0;
+ }
+
+ if (ctx->incomplete_stream != -1) {
+ kr_log_debug(DOH, "[%p] stream %d incomplete, refusing (begin_headers_callback)\n",
+ (void *)h2, ctx->incomplete_stream);
+ refuse_stream(h2, stream_id);
+ } else {
+ http_cleanup_stream(ctx); // Free any leftover data and ensure pristine state
+ ctx->incomplete_stream = stream_id;
+ ctx->last_stream = stream_id;
+ ctx->headers = malloc(sizeof(kr_http_header_array_t));
+ array_init(*ctx->headers);
+ }
+ return 0;
+}
+
+/*
+ * Process a received header name-value pair.
+ *
+ * In DoH, GET requests contain the base64url-encoded query in dns variable present in path.
+ * This variable is parsed from :path pseudoheader.
+ */
+static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t valuelen, uint8_t flags, void *user_data)
+{
+ struct http_ctx *ctx = (struct http_ctx *)user_data;
+ int32_t stream_id = frame->hd.stream_id;
+
+ if (frame->hd.type != NGHTTP2_HEADERS)
+ return 0;
+
+ if (ctx->incomplete_stream != stream_id) {
+ kr_log_debug(DOH, "[%p] stream %d incomplete, refusing (header_callback)\n",
+ (void *)h2, ctx->incomplete_stream);
+ refuse_stream(h2, stream_id);
+ return 0;
+ }
+
+ /* Store chosen headers to pass them to kr_request. */
+ for (int i = 0; i < the_worker->doh_qry_headers.len; i++) {
+ if (!strcasecmp(the_worker->doh_qry_headers.at[i], (const char *)name)) {
+ kr_http_header_array_entry_t header;
+
+ /* Limit maximum value size to reduce attack surface. */
+ if (valuelen > HTTP_MAX_HEADER_IN_SIZE) {
+ kr_log_debug(DOH,
+ "[%p] stream %d: header too large (%zu B), refused\n",
+ (void *)h2, stream_id, valuelen);
+ set_status(ctx, HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE);
+ return 0;
+ }
+
+ /* Copy the user-provided header name to keep the original case. */
+ header.name = malloc(sizeof(*header.name) * (namelen + 1));
+ memcpy(header.name, the_worker->doh_qry_headers.at[i], namelen);
+ header.name[namelen] = '\0';
+
+ header.value = malloc(sizeof(*header.value) * (valuelen + 1));
+ memcpy(header.value, value, valuelen);
+ header.value[valuelen] = '\0';
+
+ array_push(*ctx->headers, header);
+ break;
+ }
+ }
+
+ if (!strcasecmp(":path", (const char *)name)) {
+ int uri_result = check_uri((const char *)value);
+ if (uri_result == kr_error(ENOENT)) {
+ set_status(ctx, HTTP_STATUS_NOT_FOUND);
+ return 0;
+ } else if (uri_result < 0) {
+ set_status(ctx, HTTP_STATUS_BAD_REQUEST);
+ return 0;
+ }
+
+ kr_assert(ctx->uri_path == NULL);
+ ctx->uri_path = malloc(sizeof(*ctx->uri_path) * (valuelen + 1));
+ if (!ctx->uri_path)
+ return kr_error(ENOMEM);
+ memcpy(ctx->uri_path, value, valuelen);
+ ctx->uri_path[valuelen] = '\0';
+ }
+
+ if (!strcasecmp(":method", (const char *)name)) {
+ if (!strcasecmp("get", (const char *)value)) {
+ ctx->current_method = HTTP_METHOD_GET;
+ } else if (!strcasecmp("post", (const char *)value)) {
+ ctx->current_method = HTTP_METHOD_POST;
+ } else if (!strcasecmp("head", (const char *)value)) {
+ ctx->current_method = HTTP_METHOD_HEAD;
+ } else {
+ ctx->current_method = HTTP_METHOD_NONE;
+ set_status(ctx, HTTP_STATUS_NOT_IMPLEMENTED);
+ return 0;
+ }
+ }
+
+ if (!strcasecmp("content-type", (const char *)name)) {
+ if (strcasecmp("application/dns-message", (const char *)value)) {
+ set_status(ctx, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Process DATA chunk sent by the client (by POST method).
+ *
+ * We use a single DNS message buffer for the entire connection. Therefore, we
+ * don't support interweaving DATA chunks from different streams. To successfully
+ * parse multiple subsequent streams, each one must be fully received before
+ * processing a new stream. See https://gitlab.nic.cz/knot/knot-resolver/-/issues/619
+ */
+static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t stream_id,
+ const uint8_t *data, size_t len, void *user_data)
+{
+ struct http_ctx *ctx = (struct http_ctx *)user_data;
+ ssize_t remaining;
+ ssize_t required;
+ bool is_first = queue_len(ctx->streams) == 0 || queue_tail(ctx->streams).id != ctx->incomplete_stream;
+
+ if (ctx->incomplete_stream != stream_id) {
+ kr_log_debug(DOH, "[%p] stream %d incomplete, refusing (data_chunk_recv_callback)\n",
+ (void *)h2, ctx->incomplete_stream);
+ refuse_stream(h2, stream_id);
+ ctx->incomplete_stream = -1;
+ return 0;
+ }
+
+ remaining = ctx->buf_size - ctx->submitted - ctx->buf_pos;
+ required = len;
+ /* First data chunk of the new stream */
+ if (is_first)
+ required += sizeof(uint16_t);
+
+ if (required > remaining) {
+ kr_log_error(DOH, "[%p] insufficient space in buffer\n", (void *)h2);
+ ctx->incomplete_stream = -1;
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (is_first) {
+ /* FIXME: reserving the 2B length should be done elsewhere,
+ * ideally for both POST and GET at the same time. The right
+ * place would probably be after receiving HEADERS frame in
+ * on_frame_recv()
+ *
+ * queue_push() should be moved: see FIXME in
+ * submit_to_wirebuffer() */
+ ctx->buf_pos = sizeof(uint16_t); /* Reserve 2B for dnsmsg len. */
+ struct http_stream stream = {
+ .id = stream_id,
+ .headers = headers_dup(ctx->headers)
+ };
+ queue_push(ctx->streams, stream);
+ }
+
+ memmove(ctx->buf + ctx->buf_pos, data, len);
+ ctx->buf_pos += len;
+ return 0;
+}
+
+static int submit_to_wirebuffer(struct http_ctx *ctx)
+{
+ int ret = -1;
+ ssize_t len;
+
+ /* Free http_ctx's headers - by now the stream has obtained its own
+ * copy of the headers which it can operate on. */
+ /* FIXME: technically, transferring memory ownership should happen
+ * along with queue_push(ctx->streams) to avoid confusion of who owns
+ * what and when. Pushing to queue should be done AFTER we successfully
+ * finish this function. On error, we'd clean up and not push anything.
+ * However, queue's content is now also used to detect first DATA frame
+ * in stream, so it needs to be refactored first.
+ *
+ * For now, we assume memory is transferred even on error and the
+ * headers themselves get cleaned up during http_free() which is
+ * triggered after the error when session is closed.
+ *
+ * EDIT(2022-05-19): The original logic was causing occasional
+ * double-free conditions once status code support was extended.
+ *
+ * Currently, we are copying the headers from ctx instead of transferring
+ * ownership, which is still a dirty workaround and, ideally, the whole
+ * logic around header (de)allocation should be reworked to make
+ * the ownership situation clear. */
+ http_free_headers(ctx->headers);
+ ctx->headers = NULL;
+
+ len = ctx->buf_pos - sizeof(uint16_t);
+ if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) {
+ kr_log_debug(DOH, "[%p] invalid dnsmsg size: %zd B\n", (void *)ctx->h2, len);
+ set_status(ctx, (len <= 0)
+ ? HTTP_STATUS_BAD_REQUEST
+ : HTTP_STATUS_PAYLOAD_TOO_LARGE);
+ ret = 0;
+ goto cleanup;
+ }
+
+ /* Submit data to wirebuffer. */
+ knot_wire_write_u16(ctx->buf, len);
+ ctx->submitted += ctx->buf_pos;
+ ctx->buf += ctx->buf_pos;
+ ctx->buf_pos = 0;
+ ret = 0;
+cleanup:
+ http_cleanup_stream(ctx);
+ return ret;
+}
+
+/*
+ * Finalize existing buffer upon receiving the last frame in the stream.
+ *
+ * For GET, this would be HEADERS frame.
+ * For POST, it is a DATA frame.
+ *
+ * Unrelated frames (such as SETTINGS) are ignored (no data was buffered).
+ */
+static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *frame, void *user_data)
+{
+ struct http_ctx *ctx = (struct http_ctx *)user_data;
+ int32_t stream_id = frame->hd.stream_id;
+ if(kr_fails_assert(stream_id != -1))
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->incomplete_stream == stream_id) {
+ ctx->streaming = false;
+
+ if (ctx->current_method == HTTP_METHOD_GET || ctx->current_method == HTTP_METHOD_HEAD) {
+ if (process_uri_path(ctx, ctx->uri_path, stream_id) < 0) {
+ /* End processing - don't submit to wirebuffer. */
+ set_status(ctx, HTTP_STATUS_BAD_REQUEST);
+ return 0;
+ }
+ }
+
+ if (submit_to_wirebuffer(ctx) < 0)
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+/*
+ * Call on_write() callback for written (or failed) packet data.
+ */
+static void on_pkt_write(struct http_data *data, int status)
+{
+ if (!data || !data->req || !data->on_write)
+ return;
+
+ data->on_write(data->req, status);
+ free(data);
+}
+
+static int stream_write_data_free_err(trie_val_t *val, void *null)
+{
+ on_pkt_write(*val, kr_error(EIO));
+ return 0;
+}
+
+/*
+ * Cleanup for closed streams.
+ */
+static int on_stream_close_callback(nghttp2_session *h2, int32_t stream_id,
+ uint32_t error_code, void *user_data)
+{
+ struct http_data *data;
+ struct http_ctx *ctx = (struct http_ctx *)user_data;
+ int ret;
+
+ /* Ensure connection state is cleaned up in case the stream gets
+ * unexpectedly closed, e.g. by PROTOCOL_ERROR issued from nghttp2. */
+ if (ctx->incomplete_stream == stream_id)
+ http_cleanup_stream(ctx);
+
+ ret = trie_del(ctx->stream_write_data, (char *)&stream_id, sizeof(stream_id), (trie_val_t*)&data);
+ if (ret == KNOT_EOK && data)
+ on_pkt_write(data, error_code == 0 ? 0 : kr_error(EIO));
+
+ return 0;
+}
+
+/*
+ * Setup and initialize connection with new HTTP/2 context.
+ */
+struct http_ctx* http_new(struct session *session, http_send_callback send_cb)
+{
+ if (!session || !send_cb)
+ return NULL;
+
+ nghttp2_session_callbacks *callbacks;
+ struct http_ctx *ctx = NULL;
+ static const nghttp2_settings_entry iv[] = {
+ { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, HTTP_MAX_CONCURRENT_STREAMS }
+ };
+
+ if (nghttp2_session_callbacks_new(&callbacks) < 0)
+ return ctx;
+ nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+ nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback);
+ nghttp2_session_callbacks_set_on_header_callback(callbacks, header_callback);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, begin_headers_callback);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, data_chunk_recv_callback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(
+ callbacks, on_frame_recv_callback);
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ ctx = calloc(1UL, sizeof(struct http_ctx));
+ if (!ctx)
+ goto finish;
+
+ ctx->send_cb = send_cb;
+ ctx->session = session;
+ queue_init(ctx->streams);
+ ctx->stream_write_data = trie_create(NULL);
+ ctx->incomplete_stream = -1;
+ ctx->last_stream = -1;
+ ctx->submitted = 0;
+ ctx->streaming = true;
+ ctx->current_method = HTTP_METHOD_NONE;
+ ctx->uri_path = NULL;
+ ctx->status = HTTP_STATUS_OK;
+
+ nghttp2_session_server_new(&ctx->h2, callbacks, ctx);
+ nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
+ iv, sizeof(iv)/sizeof(*iv));
+
+ struct sockaddr *peer = session_get_peer(session);
+ kr_log_debug(DOH, "[%p] h2 session created for %s\n", (void *)ctx->h2, kr_straddr(peer));
+finish:
+ nghttp2_session_callbacks_del(callbacks);
+ return ctx;
+}
+
+/*
+ * Process inbound HTTP/2 data and return number of bytes read into session wire buffer.
+ *
+ * This function may trigger outgoing HTTP/2 data, such as stream resets, window updates etc.
+ *
+ * Returns 1 if stream has not ended yet, 0 if the stream has ended, or
+ * a negative value on error.
+ */
+int http_process_input_data(struct session *session, const uint8_t *buf, ssize_t nread,
+ ssize_t *out_submitted)
+{
+ struct http_ctx *ctx = session_http_get_server_ctx(session);
+ ssize_t ret = 0;
+
+ if (!ctx->h2)
+ return kr_error(ENOSYS);
+ if (kr_fails_assert(ctx->session == session))
+ return kr_error(EINVAL);
+
+ /* FIXME It is possible for the TLS/HTTP processing to be cut off at
+ * any point, waiting for more data. If we're using POST which is split
+ * into multiple DATA frames and such a stream is in the middle of
+ * processing, resetting buf_pos will corrupt its contents (and the
+ * query will be ignored). This may also be problematic in other
+ * cases. */
+ ctx->submitted = 0;
+ ctx->streaming = true;
+ ctx->buf = session_wirebuf_get_free_start(session);
+ ctx->buf_pos = 0;
+ ctx->buf_size = session_wirebuf_get_free_size(session);
+
+ ret = nghttp2_session_mem_recv(ctx->h2, buf, nread);
+ if (ret < 0) {
+ kr_log_debug(DOH, "[%p] nghttp2_session_mem_recv failed: %s (%zd)\n",
+ (void *)ctx->h2, nghttp2_strerror(ret), ret);
+ return kr_error(EIO);
+ }
+
+ ret = nghttp2_session_send(ctx->h2);
+ if (ret < 0) {
+ kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s (%zd)\n",
+ (void *)ctx->h2, nghttp2_strerror(ret), ret);
+ return kr_error(EIO);
+ }
+
+ if (!http_status_has_category(ctx->status, 2)) {
+ *out_submitted = 0;
+ http_send_status(session, ctx->status);
+ http_cleanup_stream(ctx);
+ return 0;
+ }
+
+ *out_submitted = ctx->submitted;
+ return ctx->streaming;
+}
+
+int http_send_status(struct session *session, enum http_status status)
+{
+ struct http_ctx *ctx = session_http_get_server_ctx(session);
+ if (ctx->last_stream >= 0)
+ return http_send_response_rst_stream(
+ ctx, ctx->last_stream, NULL, status);
+
+ return 0;
+}
+
+/*
+ * Provide data from buffer to HTTP/2 library.
+ *
+ * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY
+ * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback.
+ *
+ * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback
+ */
+static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf,
+ size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data)
+{
+ struct http_data *data;
+ size_t avail;
+ size_t send;
+
+ data = (struct http_data*)source->ptr;
+ avail = data->len - data->pos;
+ send = MIN(avail, length);
+
+ if (avail == send)
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+ return send;
+}
+
+/** Convenience function for pushing `nghttp2_nv` made with MAKE_*_NV into
+ * arrays. */
+static inline void push_nv(nghttp2_array_t *arr, nghttp2_nv nv)
+{
+ array_push(*arr, nv);
+}
+
+/*
+ * Send dns response provided by the HTTP/2 data provider.
+ *
+ * Data isn't guaranteed to be sent immediately due to underlying HTTP/2 flow control.
+ */
+static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
+ nghttp2_data_provider *prov, enum http_status status)
+{
+ nghttp2_session *h2 = ctx->h2;
+ int ret;
+
+ nghttp2_array_t hdrs;
+ array_init(hdrs);
+ array_reserve(hdrs, 5);
+
+ auto_free char *status_str = NULL;
+ if (likely(status == HTTP_STATUS_OK)) {
+ push_nv(&hdrs, MAKE_STATIC_NV(":status", "200"));
+ } else {
+ int status_len = asprintf(&status_str, "%d", (int)status);
+ kr_require(status_len >= 0);
+ push_nv(&hdrs, MAKE_STATIC_KEY_NV(":status", status_str, status_len));
+ }
+ push_nv(&hdrs, MAKE_STATIC_NV("access-control-allow-origin", "*"));
+
+ struct http_data *data = NULL;
+ auto_free char *size = NULL;
+ auto_free char *max_age = NULL;
+
+ if (ctx->current_method == HTTP_METHOD_HEAD && prov) {
+ /* HEAD method is the same as GET but only returns headers,
+ * so let's clean up the data here as we don't need it. */
+ free(prov->source.ptr);
+ prov = NULL;
+ }
+
+ if (prov) {
+ data = (struct http_data*)prov->source.ptr;
+ const char *directive_max_age = "max-age=";
+ int max_age_len;
+ int size_len;
+
+ size_len = asprintf(&size, "%zu", data->len);
+ kr_require(size_len >= 0);
+ max_age_len = asprintf(&max_age, "%s%" PRIu32, directive_max_age, data->ttl);
+ kr_require(max_age_len >= 0);
+
+ push_nv(&hdrs, MAKE_STATIC_NV("content-type", "application/dns-message"));
+ push_nv(&hdrs, MAKE_STATIC_KEY_NV("content-length", size, size_len));
+ push_nv(&hdrs, MAKE_STATIC_KEY_NV("cache-control", max_age, max_age_len));
+ }
+
+ ret = nghttp2_submit_response(h2, stream_id, hdrs.at, hdrs.len, prov);
+ array_clear(hdrs);
+ if (ret != 0) {
+ kr_log_debug(DOH, "[%p] nghttp2_submit_response failed: %s\n", (void *)h2, nghttp2_strerror(ret));
+ free(data);
+ return kr_error(EIO);
+ }
+
+ /* Keep reference to data, since we need to free it later on.
+ * Due to HTTP/2 flow control, this stream data may be sent at a later point, or not at all.
+ */
+ trie_val_t *stream_data_p = trie_get_ins(ctx->stream_write_data, (char *)&stream_id, sizeof(stream_id));
+ if (kr_fails_assert(stream_data_p)) {
+ kr_log_debug(DOH, "[%p] failed to insert to stream_write_data\n", (void *)h2);
+ free(data);
+ return kr_error(EIO);
+ }
+ *stream_data_p = data;
+ ret = nghttp2_session_send(h2);
+ if(ret < 0) {
+ kr_log_debug(DOH, "[%p] nghttp2_session_send failed: %s\n", (void *)h2, nghttp2_strerror(ret));
+
+ /* At this point, there was an error in some nghttp2 callback. The on_pkt_write()
+ * callback which also calls free(data) may or may not have been called. Therefore,
+ * we must guarantee it will have been called by explicitly closing the stream.
+ * Afterwards, we have no option but to pretend this function was a success. If we
+ * returned an error, qr_task_send() logic would lead to a double-free because
+ * on_write() was already called. */
+ nghttp2_submit_rst_stream(h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_INTERNAL_ERROR);
+ return 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Same as `http_send_response`, but resets the HTTP stream afterwards. Used
+ * for sending negative status messages.
+ */
+static int http_send_response_rst_stream(struct http_ctx *ctx, int32_t stream_id,
+ nghttp2_data_provider *prov, enum http_status status)
+{
+ int ret = http_send_response(ctx, stream_id, prov, status);
+ if (ret)
+ return ret;
+
+ ctx->last_stream = -1;
+ nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_NO_ERROR);
+ ret = nghttp2_session_send(ctx->h2);
+ return ret;
+}
+
+
+/*
+ * Send HTTP/2 stream data created from packet's wire buffer.
+ *
+ * If this function returns an error, the on_write() callback isn't (and
+ * mustn't be!) called, since such errors are handled in an upper layer - in
+ * qr_task_step() in daemon/worker.
+ */
+static int http_write_pkt(struct http_ctx *ctx, knot_pkt_t *pkt, int32_t stream_id,
+ uv_write_t *req, uv_write_cb on_write)
+{
+ struct http_data *data;
+ nghttp2_data_provider prov;
+
+ data = malloc(sizeof(struct http_data));
+ if (!data)
+ return kr_error(ENOMEM);
+
+ data->buf = pkt->wire;
+ data->len = pkt->size;
+ data->pos = 0;
+ data->on_write = on_write;
+ data->req = req;
+ data->ttl = packet_ttl(pkt);
+
+ prov.source.ptr = data;
+ prov.read_callback = read_callback;
+
+ return http_send_response(ctx, stream_id, &prov, HTTP_STATUS_OK);
+}
+
+/*
+ * Write request to HTTP/2 stream.
+ *
+ * Packet wire buffer must stay valid until the on_write callback.
+ */
+int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t *pkt, int32_t stream_id,
+ uv_write_cb on_write)
+{
+ struct session *session;
+ struct http_ctx *ctx;
+ int ret;
+
+ if (!req || !pkt || !handle || !handle->data || stream_id < 0)
+ return kr_error(EINVAL);
+ req->handle = (uv_stream_t *)handle;
+
+ session = handle->data;
+ if (session_flags(session)->outgoing)
+ return kr_error(ENOSYS);
+
+ ctx = session_http_get_server_ctx(session);
+ if (!ctx || !ctx->h2)
+ return kr_error(EINVAL);
+
+ ret = http_write_pkt(ctx, pkt, stream_id, req, on_write);
+ if (ret < 0)
+ return ret;
+
+ return kr_ok();
+}
+
+/*
+ * Release HTTP/2 context.
+ */
+void http_free(struct http_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ kr_log_debug(DOH, "[%p] h2 session freed\n", (void *)ctx->h2);
+
+ /* Clean up any headers whose ownership may not have been transferred.
+ * This may happen when connection is abruptly ended (e.g. due to errors while
+ * processing HTTP stream. */
+ while (queue_len(ctx->streams) > 0) {
+ struct http_stream stream = queue_head(ctx->streams);
+ http_free_headers(stream.headers);
+ queue_pop(ctx->streams);
+ }
+
+ trie_apply(ctx->stream_write_data, stream_write_data_free_err, NULL);
+ trie_free(ctx->stream_write_data);
+
+ http_cleanup_stream(ctx);
+ queue_deinit(ctx->streams);
+ nghttp2_session_del(ctx->h2);
+ free(ctx);
+}
diff --git a/daemon/http.h b/daemon/http.h
new file mode 100644
index 0000000..0749e3b
--- /dev/null
+++ b/daemon/http.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) CZ.NIC, z.s.p.o
+ *
+ * Initial Author: Jan Hák <jan.hak@nic.cz>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <uv.h>
+#include <libknot/packet/pkt.h>
+
+#if ENABLE_DOH2
+#include <nghttp2/nghttp2.h>
+#endif
+
+#include "lib/generic/queue.h"
+#include "lib/generic/trie.h"
+
+/** Transport session (opaque). */
+struct session;
+
+typedef ssize_t(*http_send_callback)(const uint8_t *buffer,
+ const size_t buffer_len,
+ struct session *session);
+
+struct http_stream {
+ int32_t id;
+ kr_http_header_array_t *headers;
+};
+
+typedef queue_t(struct http_stream) queue_http_stream;
+
+typedef enum {
+ HTTP_METHOD_NONE = 0,
+ HTTP_METHOD_GET = 1,
+ HTTP_METHOD_POST = 2,
+ HTTP_METHOD_HEAD = 3, /**< Same as GET, except it does not return payload.
+ * Required to be implemented by RFC 7231. */
+} http_method_t;
+
+/** HTTP status codes returned by kresd.
+ * This is obviously non-exhaustive of all HTTP status codes, feel free to add
+ * more if needed. */
+enum http_status {
+ HTTP_STATUS_OK = 200,
+ HTTP_STATUS_BAD_REQUEST = 400,
+ HTTP_STATUS_NOT_FOUND = 404,
+ HTTP_STATUS_PAYLOAD_TOO_LARGE = 413,
+ HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415,
+ HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ HTTP_STATUS_NOT_IMPLEMENTED = 501,
+};
+
+struct http_ctx {
+ struct nghttp2_session *h2;
+ http_send_callback send_cb;
+ struct session *session;
+ queue_http_stream streams; /* Streams present in the wire buffer. */
+ trie_t *stream_write_data; /* Dictionary of stream data that needs to be freed after write. */
+ int32_t incomplete_stream;
+ int32_t last_stream; /* The last used stream - mostly the same as incomplete_stream, but can be used after
+ completion for sending HTTP status codes. */
+ ssize_t submitted;
+ http_method_t current_method;
+ char *uri_path;
+ kr_http_header_array_t *headers;
+ uint8_t *buf; /* Part of the wire_buf that belongs to current HTTP/2 stream. */
+ ssize_t buf_pos;
+ ssize_t buf_size;
+ enum http_status status;
+ bool streaming; /* True: not all data in the stream has been received yet. */
+};
+
+#if ENABLE_DOH2
+struct http_ctx* http_new(struct session *session, http_send_callback send_cb);
+int http_process_input_data(struct session *session, const uint8_t *buf, ssize_t nread,
+ ssize_t *out_submitted);
+int http_send_status(struct session *session, enum http_status status);
+int http_write(uv_write_t *req, uv_handle_t *handle, knot_pkt_t* pkt, int32_t stream_id,
+ uv_write_cb on_write);
+void http_free(struct http_ctx *ctx);
+void http_free_headers(kr_http_header_array_t *headers);
+#endif
diff --git a/daemon/io.c b/daemon/io.c
new file mode 100644
index 0000000..6d548d7
--- /dev/null
+++ b/daemon/io.c
@@ -0,0 +1,1151 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/io.h"
+
+#include <contrib/ucw/lib.h>
+#include <contrib/ucw/mempool.h>
+#include <libknot/errcode.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#if ENABLE_XDP
+ #include <libknot/xdp/eth.h>
+ #include <libknot/xdp/xdp.h>
+ #include <net/if.h>
+#endif
+
+#include "daemon/network.h"
+#include "daemon/proxyv2.h"
+#include "daemon/worker.h"
+#include "daemon/tls.h"
+#include "daemon/http.h"
+#include "daemon/session.h"
+#include "contrib/cleanup.h"
+#include "lib/utils.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 *comm_addr, unsigned flags)
+{
+ struct session *s = handle->data;
+ if (session_flags(s)->closing || nread <= 0 || comm_addr->sa_family == AF_UNSPEC)
+ return;
+
+ if (session_flags(s)->outgoing) {
+ const struct sockaddr *peer = session_get_peer(s);
+ if (kr_fails_assert(peer->sa_family != AF_UNSPEC))
+ return;
+ if (kr_sockaddr_cmp(peer, comm_addr) != 0) {
+ kr_log_debug(IO, "<= ignoring UDP from unexpected address '%s'\n",
+ kr_straddr(comm_addr));
+ return;
+ }
+ }
+
+ const uint8_t *data = (const uint8_t *)buf->base;
+ ssize_t data_len = nread;
+ const struct sockaddr *src_addr = comm_addr;
+ const struct sockaddr *dst_addr = NULL;
+ struct proxy_result proxy;
+ bool has_proxy = false;
+ if (!session_flags(s)->outgoing && proxy_header_present(data, data_len)) {
+ if (!proxy_allowed(&the_worker->engine->net, comm_addr)) {
+ kr_log_debug(IO, "<= ignoring PROXYv2 UDP from disallowed address '%s'\n",
+ kr_straddr(comm_addr));
+ return;
+ }
+
+ ssize_t trimmed = proxy_process_header(&proxy, s, data, data_len);
+ if (trimmed == KNOT_EMALF) {
+ if (kr_log_is_debug(IO, NULL)) {
+ kr_log_debug(IO, "<= ignoring malformed PROXYv2 UDP "
+ "from address '%s'\n",
+ kr_straddr(comm_addr));
+ }
+ return;
+ } else if (trimmed < 0) {
+ if (kr_log_is_debug(IO, NULL)) {
+ kr_log_debug(IO, "<= error processing PROXYv2 UDP "
+ "from address '%s', ignoring\n",
+ kr_straddr(comm_addr));
+ }
+ return;
+ }
+
+ if (proxy.command == PROXY2_CMD_PROXY && proxy.family != AF_UNSPEC) {
+ has_proxy = true;
+ src_addr = &proxy.src_addr.ip;
+ dst_addr = &proxy.dst_addr.ip;
+
+ if (kr_log_is_debug(IO, NULL)) {
+ kr_log_debug(IO, "<= UDP query from '%s'\n",
+ kr_straddr(src_addr));
+ kr_log_debug(IO, "<= proxied through '%s'\n",
+ kr_straddr(comm_addr));
+ }
+ }
+ data = session_wirebuf_get_free_start(s);
+ data_len = nread - trimmed;
+ }
+
+ ssize_t consumed = session_wirebuf_consume(s, data, data_len);
+ kr_assert(consumed == data_len);
+
+ struct io_comm_data comm = {
+ .src_addr = src_addr,
+ .comm_addr = comm_addr,
+ .dst_addr = dst_addr,
+ .proxy = (has_proxy) ? &proxy : NULL
+ };
+ session_wirebuf_process(s, &comm);
+ session_wirebuf_discard(s);
+ mp_flush(the_worker->pkt_pool.ctx);
+}
+
+static int family_to_freebind_option(sa_family_t sa_family, int *level, int *name)
+{
+#define LOG_NO_FB kr_log_error(NETWORK, "your system does not support 'freebind', " \
+ "please remove it from your configuration\n")
+ switch (sa_family) {
+ case AF_INET:
+ *level = IPPROTO_IP;
+#if defined(IP_FREEBIND)
+ *name = IP_FREEBIND;
+#elif defined(IP_BINDANY)
+ *name = IP_BINDANY;
+#else
+ LOG_NO_FB;
+ return kr_error(ENOTSUP);
+#endif
+ break;
+ case AF_INET6:
+#if defined(IP_FREEBIND)
+ *level = IPPROTO_IP;
+ *name = IP_FREEBIND;
+#elif defined(IPV6_BINDANY)
+ *level = IPPROTO_IPV6;
+ *name = IPV6_BINDANY;
+#else
+ LOG_NO_FB;
+ return kr_error(ENOTSUP);
+#endif
+ break;
+ default:
+ return kr_error(ENOTSUP);
+ }
+ return kr_ok();
+}
+
+int io_bind(const struct sockaddr *addr, int type, const endpoint_flags_t *flags)
+{
+ const int fd = socket(addr->sa_family, type, 0);
+ if (fd < 0) return kr_error(errno);
+
+ int yes = 1;
+ if (addr->sa_family == AF_INET || addr->sa_family == AF_INET6) {
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) {
+ close(fd);
+ return kr_error(errno);
+ }
+
+#ifdef SO_REUSEPORT_LB
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, &yes, sizeof(yes))) {
+ close(fd);
+ return kr_error(errno);
+ }
+#elif defined(SO_REUSEPORT) && defined(__linux__) /* different meaning on (Free)BSD */
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes))) {
+ close(fd);
+ return kr_error(errno);
+ }
+#endif
+
+#ifdef IPV6_V6ONLY
+ if (addr->sa_family == AF_INET6
+ && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) {
+ close(fd);
+ return kr_error(errno);
+ }
+#endif
+ if (flags != NULL && flags->freebind) {
+ int optlevel;
+ int optname;
+ int ret = family_to_freebind_option(addr->sa_family, &optlevel, &optname);
+ if (ret) {
+ close(fd);
+ return kr_error(ret);
+ }
+ if (setsockopt(fd, optlevel, optname, &yes, sizeof(yes))) {
+ close(fd);
+ return kr_error(errno);
+ }
+ }
+
+ /* Linux 3.15 has IP_PMTUDISC_OMIT which makes sockets
+ * ignore PMTU information and send packets with DF=0.
+ * This mitigates DNS fragmentation attacks by preventing
+ * forged PMTU information. FreeBSD already has same semantics
+ * without setting the option.
+ https://gitlab.nic.cz/knot/knot-dns/-/issues/640
+ */
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT)
+ int omit = IP_PMTUDISC_OMIT;
+ if (type == SOCK_DGRAM && addr->sa_family == AF_INET
+ && setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &omit, sizeof(omit))) {
+ kr_log_error(IO,
+ "failed to disable Path MTU discovery for %s UDP: %s\n",
+ kr_straddr(addr), strerror(errno));
+ }
+#endif
+ }
+
+ if (bind(fd, addr, kr_sockaddr_len(addr))) {
+ close(fd);
+ return kr_error(errno);
+ }
+
+ return fd;
+}
+
+int io_listen_udp(uv_loop_t *loop, uv_udp_t *handle, int fd)
+{
+ if (!handle) {
+ return kr_error(EINVAL);
+ }
+ int ret = uv_udp_init(loop, handle);
+ if (ret) return ret;
+
+ ret = uv_udp_open(handle, fd);
+ if (ret) return ret;
+
+ uv_handle_t *h = (uv_handle_t *)handle;
+ check_bufsize(h);
+ /* Handle is already created, just create context. */
+ struct session *s = session_new(h, false, false);
+ kr_require(s);
+ session_flags(s)->outgoing = false;
+
+ int socklen = sizeof(union kr_sockaddr);
+ ret = uv_udp_getsockname(handle, session_get_sockname(s), &socklen);
+ if (ret) {
+ kr_log_error(IO, "ERROR: getsockname failed: %s\n", uv_strerror(ret));
+ abort(); /* It might be nontrivial not to leak something here. */
+ }
+
+ return io_start_read(h);
+}
+
+void tcp_timeout_trigger(uv_timer_t *timer)
+{
+ struct session *s = timer->data;
+
+ if (kr_fails_assert(!session_flags(s)->closing))
+ return;
+
+ if (!session_tasklist_is_empty(s)) {
+ int finalized = session_tasklist_finalize_expired(s);
+ the_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 finalize 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);
+ the_worker->stats.timeout += 1;
+ if (session_flags(s)->closing) {
+ return;
+ }
+ }
+ const struct network *net = &the_worker->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_debug(IO, "=> closing connection to '%s'\n",
+ peer_str ? peer_str : "");
+ if (session_flags(s)->outgoing) {
+ worker_del_tcp_waiting(the_worker, peer);
+ worker_del_tcp_connected(the_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;
+ if (kr_fails_assert(s && session_get_handle(s) == (uv_handle_t *)handle && handle->type == UV_TCP))
+ return;
+
+ 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_log_is_debug(IO, NULL)) {
+ struct sockaddr *peer = session_get_peer(s);
+ char *peer_str = kr_straddr(peer);
+ kr_log_debug(IO, "=> connection to '%s' closed by peer (%s)\n",
+ peer_str ? peer_str : "",
+ uv_strerror(nread));
+ }
+
+ session_tcp_penalize(s);
+ worker_end_tcp(s);
+ return;
+ }
+
+ const uint8_t *data = (const uint8_t *)buf->base;
+ ssize_t data_len = nread;
+ const struct sockaddr *src_addr = session_get_peer(s);
+ const struct sockaddr *dst_addr = NULL;
+ if (!session_flags(s)->outgoing && !session_flags(s)->no_proxy &&
+ proxy_header_present(data, data_len)) {
+ if (!proxy_allowed(&the_worker->engine->net, src_addr)) {
+ if (kr_log_is_debug(IO, NULL)) {
+ kr_log_debug(IO, "<= connection to '%s': PROXYv2 not allowed "
+ "for this peer, close\n",
+ kr_straddr(src_addr));
+ }
+ worker_end_tcp(s);
+ return;
+ }
+
+ struct proxy_result *proxy = session_proxy_create(s);
+ ssize_t trimmed = proxy_process_header(proxy, s, data, data_len);
+ if (trimmed < 0) {
+ if (kr_log_is_debug(IO, NULL)) {
+ if (trimmed == KNOT_EMALF) {
+ kr_log_debug(IO, "<= connection to '%s': "
+ "malformed PROXYv2 header, close\n",
+ kr_straddr(src_addr));
+ } else {
+ kr_log_debug(IO, "<= connection to '%s': "
+ "error processing PROXYv2 header, close\n",
+ kr_straddr(src_addr));
+ }
+ }
+ worker_end_tcp(s);
+ return;
+ } else if (trimmed == 0) {
+ return;
+ }
+
+ if (proxy->command != PROXY2_CMD_LOCAL && proxy->family != AF_UNSPEC) {
+ src_addr = &proxy->src_addr.ip;
+ dst_addr = &proxy->dst_addr.ip;
+
+ if (kr_log_is_debug(IO, NULL)) {
+ kr_log_debug(IO, "<= TCP stream from '%s'\n",
+ kr_straddr(src_addr));
+ kr_log_debug(IO, "<= proxied through '%s'\n",
+ kr_straddr(session_get_peer(s)));
+ }
+ }
+
+ data = session_wirebuf_get_free_start(s);
+ data_len = nread - trimmed;
+ }
+
+ session_flags(s)->no_proxy = true;
+
+ ssize_t consumed = 0;
+ 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, data, data_len);
+ if (consumed < 0) {
+ if (kr_log_is_debug(IO, NULL)) {
+ char *peer_str = kr_straddr(src_addr);
+ kr_log_debug(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;
+ }
+#if ENABLE_DOH2
+ int streaming = 1;
+ if (session_flags(s)->has_http) {
+ streaming = http_process_input_data(s, data, data_len,
+ &consumed);
+ if (streaming < 0) {
+ if (kr_log_is_debug(IO, NULL)) {
+ char *peer_str = kr_straddr(src_addr);
+ kr_log_debug(IO, "=> connection to '%s': "
+ "error processing HTTP data, close\n",
+ peer_str ? peer_str : "");
+ }
+ worker_end_tcp(s);
+ return;
+ }
+ if (consumed == 0) {
+ return;
+ }
+ data = session_wirebuf_get_free_start(s);
+ data_len = consumed;
+ }
+#endif
+
+ /* data points to start of the free space in session wire buffer.
+ Simple increase internal counter. */
+ consumed = session_wirebuf_consume(s, data, data_len);
+ kr_assert(consumed == data_len);
+
+ struct io_comm_data comm = {
+ .src_addr = src_addr,
+ .comm_addr = session_get_peer(s),
+ .dst_addr = dst_addr,
+ .proxy = session_proxy_get(s)
+ };
+ int ret = session_wirebuf_process(s, &comm);
+ if (ret < 0) {
+ /* An error has occurred, close the session. */
+ worker_end_tcp(s);
+ }
+ session_wirebuf_compress(s);
+ mp_flush(the_worker->pkt_pool.ctx);
+#if ENABLE_DOH2
+ if (session_flags(s)->has_http && streaming == 0 && ret == 0) {
+ ret = http_send_status(s, HTTP_STATUS_BAD_REQUEST);
+ if (ret) {
+ /* An error has occurred, close the session. */
+ worker_end_tcp(s);
+ }
+ }
+#endif
+}
+
+#if ENABLE_DOH2
+static ssize_t tls_send(const uint8_t *buf, const size_t len, struct session *session)
+{
+ struct tls_ctx *ctx = session_tls_get_server_ctx(session);
+ ssize_t sent = 0;
+ kr_require(ctx);
+
+ sent = gnutls_record_send(ctx->c.tls_session, buf, len);
+ if (sent < 0) {
+ kr_log_debug(DOH, "gnutls_record_send failed: %s (%zd)\n",
+ gnutls_strerror_name(sent), sent);
+ return kr_error(EIO);
+ }
+ return sent;
+}
+#endif
+
+static void _tcp_accept(uv_stream_t *master, int status, bool tls, bool http)
+{
+ if (status != 0) {
+ return;
+ }
+
+ struct worker_ctx *worker = the_worker;
+ 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, http);
+ 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;
+ kr_require(session_flags(s)->outgoing == false);
+ kr_require(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;
+ }
+
+ /* Get peer's and our address. We apparently get specific sockname here
+ * even if we listened on a wildcard address. */
+ struct sockaddr *sa = session_get_peer(s);
+ int sa_len = sizeof(struct sockaddr_in6);
+ int ret = uv_tcp_getpeername(client, sa, &sa_len);
+ if (ret || sa->sa_family == AF_UNSPEC) {
+ session_close(s);
+ return;
+ }
+ sa = session_get_sockname(s);
+ sa_len = sizeof(struct sockaddr_in6);
+ ret = uv_tcp_getsockname(client, sa, &sa_len);
+ if (ret || sa->sa_family == AF_UNSPEC) {
+ 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. */
+
+ const struct network *net = &worker->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 *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;
+
+ /* Configure ALPN. */
+ gnutls_datum_t proto;
+ if (!http) {
+ proto.data = (unsigned char *)"dot";
+ proto.size = 3;
+ } else {
+ proto.data = (unsigned char *)"h2";
+ proto.size = 2;
+ }
+ unsigned int flags = 0;
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+ /* Mandatory ALPN means the protocol must match if and
+ * only if ALPN extension is used by the client. */
+ flags |= GNUTLS_ALPN_MANDATORY;
+#endif
+ ret = gnutls_alpn_set_protocols(ctx->c.tls_session, &proto, 1, flags);
+ if (ret != GNUTLS_E_SUCCESS) {
+ session_close(s);
+ return;
+ }
+
+ session_tls_set_server_ctx(s, ctx);
+ }
+ }
+#if ENABLE_DOH2
+ if (http) {
+ struct http_ctx *ctx = session_http_get_server_ctx(s);
+ if (!ctx) {
+ if (!tls) { /* Plain HTTP is not supported. */
+ session_close(s);
+ return;
+ }
+ ctx = http_new(s, tls_send);
+ if (!ctx) {
+ session_close(s);
+ return;
+ }
+ session_http_set_server_ctx(s, ctx);
+ }
+ }
+#endif
+ 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, false);
+}
+
+static void tls_accept(uv_stream_t *master, int status)
+{
+ _tcp_accept(master, status, true, false);
+}
+
+#if ENABLE_DOH2
+static void https_accept(uv_stream_t *master, int status)
+{
+ _tcp_accept(master, status, true, true);
+}
+#endif
+
+int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls, bool has_http)
+{
+ uv_connection_cb connection;
+
+ if (!handle) {
+ return kr_error(EINVAL);
+ }
+ int ret = uv_tcp_init(loop, handle);
+ if (ret) return ret;
+
+ if (has_tls && has_http) {
+#if ENABLE_DOH2
+ connection = https_accept;
+#else
+ kr_log_error(IO, "kresd was compiled without libnghttp2 support\n");
+ return kr_error(ENOPROTOOPT);
+#endif
+ } else if (has_tls) {
+ connection = tls_accept;
+ } else if (has_http) {
+ return kr_error(EPROTONOSUPPORT);
+ } else {
+ connection = tcp_accept;
+ }
+
+ ret = uv_tcp_open(handle, (uv_os_sock_t) fd);
+ if (ret) return ret;
+
+ int val; (void)val;
+ /* TCP_DEFER_ACCEPT delays accepting connections until there is readable data. */
+#ifdef TCP_DEFER_ACCEPT
+ val = KR_CONN_RTT_MAX/1000;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val))) {
+ kr_log_error(IO, "listen TCP (defer_accept): %s\n", strerror(errno));
+ }
+#endif
+
+ ret = uv_listen((uv_stream_t *)handle, tcp_backlog, connection);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* TCP_FASTOPEN enables 1 RTT connection resumptions. */
+#ifdef TCP_FASTOPEN
+ #ifdef __linux__
+ val = 16; /* Accepts queue length hint */
+ #else
+ val = 1; /* Accepts on/off */
+ #endif
+ if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &val, sizeof(val))) {
+ kr_log_error(IO, "listen TCP (fastopen): %s%s\n", strerror(errno),
+ (errno != EPERM ? "" :
+ ". This may be caused by TCP Fast Open being disabled in the OS."));
+ }
+#endif
+
+ handle->data = NULL;
+ return 0;
+}
+
+
+enum io_stream_mode {
+ io_mode_text = 0,
+ io_mode_binary = 1,
+};
+
+struct io_stream_data {
+ enum io_stream_mode mode;
+ size_t blen; ///< length of `buf`
+ char *buf; ///< growing buffer residing on `pool` (mp_append_*)
+ knot_mm_t *pool;
+};
+
+/**
+ * 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;
+ */
+void io_tty_process_input(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
+{
+ auto_free char *commands = buf ? buf->base : NULL;
+
+ /* Set output streams */
+ FILE *out = stdout;
+ uv_os_fd_t stream_fd = -1;
+ struct args *args = the_args;
+ struct io_stream_data *data = (struct io_stream_data*) stream->data;
+ if (nread < 0 || uv_fileno((uv_handle_t *)stream, &stream_fd)) {
+ mp_delete(data->pool->ctx);
+ uv_close((uv_handle_t *)stream, (uv_close_cb) free);
+ return;
+ }
+ if (nread <= 0) {
+ return;
+ }
+ if (stream_fd != STDIN_FILENO) {
+ uv_os_fd_t dup_fd = dup(stream_fd);
+ if (dup_fd >= 0) {
+ out = fdopen(dup_fd, "w");
+ }
+ }
+
+ /** The current single command and the remaining command(s). */
+ char *cmd, *cmd_next = NULL;
+ bool incomplete_cmd = false;
+
+ if (!commands || nread <= 0) {
+ goto finish;
+ }
+
+ /* Execute */
+ if (commands[nread - 1] != '\n') {
+ incomplete_cmd = true;
+ }
+ /* Ensure commands is 0-terminated */
+ if (nread >= buf->len) { /* only equality should be possible */
+ char *newbuf = realloc(commands, nread + 1);
+ if (!newbuf)
+ goto finish;
+ commands = newbuf;
+ }
+ commands[nread] = '\0';
+
+ char *boundary = "\n\0";
+ cmd = strtok(commands, "\n");
+ /* strtok skip '\n' but we need process alone '\n' too */
+ if (commands[0] == '\n') {
+ cmd_next = cmd;
+ cmd = boundary;
+ } else {
+ cmd_next = strtok(NULL, "\n");
+ }
+
+ /** Moving pointer to end of buffer with incomplete command. */
+ char *pbuf = data->buf + data->blen;
+ lua_State *L = the_worker->engine->L;
+ while (cmd != NULL) {
+ /* Last command is incomplete - save it and execute later */
+ if (incomplete_cmd && cmd_next == NULL) {
+ pbuf = mp_append_string(data->pool->ctx, pbuf, cmd);
+ mp_append_char(data->pool->ctx, pbuf, '\0');
+ data->buf = mp_ptr(data->pool->ctx);
+ data->blen = data->blen + strlen(cmd);
+
+ /* There is new incomplete command */
+ if (commands[nread - 1] == '\n')
+ incomplete_cmd = false;
+ goto next_iter;
+ }
+
+ /* Process incomplete command from previously call */
+ if (data->blen > 0) {
+ if (commands[0] != '\n' && commands[0] != '\0') {
+ pbuf = mp_append_string(data->pool->ctx, pbuf, cmd);
+ mp_append_char(data->pool->ctx, pbuf, '\0');
+ data->buf = mp_ptr(data->pool->ctx);
+ cmd = data->buf;
+ } else {
+ cmd = data->buf;
+ }
+ data->blen = 0;
+ pbuf = data->buf;
+ }
+
+ /* Pseudo-command for switching to "binary output"; */
+ if (strcmp(cmd, "__binary") == 0) {
+ data->mode = io_mode_binary;
+ goto next_iter;
+ }
+
+ const bool cmd_failed = engine_cmd(L, cmd, false);
+ const char *message = NULL;
+ size_t len_s;
+ if (lua_gettop(L) > 0) {
+ message = lua_tolstring(L, -1, &len_s);
+ }
+
+ /* Send back the output, either in "binary" or normal mode. */
+ if (data->mode == io_mode_binary) {
+ /* Leader expects length field in all cases */
+ if (!message || len_s > UINT32_MAX) {
+ kr_log_error(IO, "unrepresentable response on control socket, "
+ "sending back empty block (command '%s')\n", cmd);
+ len_s = 0;
+ }
+ uint32_t len_n = htonl(len_s);
+ fwrite(&len_n, sizeof(len_n), 1, out);
+ if (len_s > 0)
+ fwrite(message, len_s, 1, out);
+ } else {
+ if (message)
+ fprintf(out, "%s", message);
+ if (message || !args->quiet)
+ fprintf(out, "\n");
+ if (!args->quiet)
+ fprintf(out, "> ");
+ }
+
+ /* Duplicate command and output to logs */
+ if (cmd_failed) {
+ kr_log_warning(CONTROL, "> %s\n", cmd);
+ if (message)
+ kr_log_warning(CONTROL, "%s\n", message);
+ } else {
+ kr_log_debug(CONTROL, "> %s\n", cmd);
+ if (message)
+ kr_log_debug(CONTROL, "%s\n", message);
+ }
+ next_iter:
+ lua_settop(L, 0); /* not required in some cases but harmless */
+ cmd = cmd_next;
+ cmd_next = strtok(NULL, "\n");
+ }
+
+finish:
+ /* Close if redirected */
+ if (stream_fd != STDIN_FILENO) {
+ fclose(out);
+ }
+}
+
+void io_tty_alloc(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
+{
+ buf->len = suggested;
+ buf->base = malloc(suggested);
+}
+
+struct io_stream_data *io_tty_alloc_data(void) {
+ knot_mm_t *pool = mm_ctx_mempool2(MM_DEFAULT_BLKSIZE);
+ if (!pool) {
+ return NULL;
+ }
+ struct io_stream_data *data = mm_alloc(pool, sizeof(struct io_stream_data));
+
+ data->buf = mp_start(pool->ctx, 512);
+ data->mode = io_mode_text;
+ data->blen = 0;
+ data->pool = pool;
+
+ return data;
+}
+
+void io_tty_accept(uv_stream_t *master, int status)
+{
+ /* We can't use any allocations after mp_start() and it's easier anyway. */
+ uv_pipe_t *client = malloc(sizeof(*client));
+ if (!client)
+ return;
+
+ struct io_stream_data *data = io_tty_alloc_data();
+ if (!data) {
+ free(client);
+ return;
+ }
+ client->data = data;
+
+ struct args *args = the_args;
+ uv_pipe_init(master->loop, client, 0);
+ if (uv_accept(master, (uv_stream_t *)client) != 0) {
+ mp_delete(data->pool->ctx);
+ return;
+ }
+ uv_read_start((uv_stream_t *)client, io_tty_alloc, io_tty_process_input);
+
+ /* Write command line */
+ if (!args->quiet) {
+ uv_buf_t buf = { "> ", 2 };
+ uv_try_write((uv_stream_t *)client, &buf, 1);
+ }
+}
+
+int io_listen_pipe(uv_loop_t *loop, uv_pipe_t *handle, int fd)
+{
+ if (!handle) {
+ return kr_error(EINVAL);
+ }
+ int ret = uv_pipe_init(loop, handle, 0);
+ if (ret) return ret;
+
+ ret = uv_pipe_open(handle, fd);
+ if (ret) return ret;
+
+ ret = uv_listen((uv_stream_t *)handle, 16, io_tty_accept);
+ if (ret) return ret;
+
+ handle->data = NULL;
+
+ return 0;
+}
+
+#if ENABLE_XDP
+static void xdp_rx(uv_poll_t* handle, int status, int events)
+{
+ const int XDP_RX_BATCH_SIZE = 64;
+ if (status < 0) {
+ kr_log_error(XDP, "poll status %d: %s\n", status, uv_strerror(status));
+ return;
+ }
+ if (events != UV_READABLE) {
+ kr_log_error(XDP, "poll unexpected events: %d\n", events);
+ return;
+ }
+
+ xdp_handle_data_t *xhd = handle->data;
+ kr_require(xhd && xhd->session && xhd->socket);
+ uint32_t rcvd;
+ knot_xdp_msg_t msgs[XDP_RX_BATCH_SIZE];
+ int ret = knot_xdp_recv(xhd->socket, msgs, XDP_RX_BATCH_SIZE, &rcvd
+ #if KNOT_VERSION_HEX >= 0x030100
+ , NULL
+ #endif
+ );
+
+ if (kr_fails_assert(ret == KNOT_EOK)) {
+ /* ATM other error codes can only be returned when called incorrectly */
+ kr_log_error(XDP, "knot_xdp_recv(): %d, %s\n", ret, knot_strerror(ret));
+ return;
+ }
+ kr_log_debug(XDP, "poll triggered, processing a batch of %d packets\n", (int)rcvd);
+ kr_require(rcvd <= XDP_RX_BATCH_SIZE);
+ for (int i = 0; i < rcvd; ++i) {
+ const knot_xdp_msg_t *msg = &msgs[i];
+ kr_require(msg->payload.iov_len <= KNOT_WIRE_MAX_PKTSIZE);
+ knot_pkt_t *kpkt = knot_pkt_new(msg->payload.iov_base, msg->payload.iov_len,
+ &the_worker->pkt_pool);
+ if (kpkt == NULL) {
+ ret = kr_error(ENOMEM);
+ } else {
+ struct io_comm_data comm = {
+ .src_addr = (const struct sockaddr *)&msg->ip_from,
+ .comm_addr = (const struct sockaddr *)&msg->ip_from,
+ .dst_addr = (const struct sockaddr *)&msg->ip_to
+ };
+ ret = worker_submit(xhd->session, &comm,
+ msg->eth_from, msg->eth_to, kpkt);
+ }
+ if (ret)
+ kr_log_debug(XDP, "worker_submit() == %d: %s\n", ret, kr_strerror(ret));
+ mp_flush(the_worker->pkt_pool.ctx);
+ }
+ knot_xdp_recv_finish(xhd->socket, msgs, rcvd);
+}
+/// Warn if the XDP program is running in emulated mode (XDP_SKB)
+static void xdp_warn_mode(const char *ifname)
+{
+ if (kr_fails_assert(ifname))
+ return;
+
+ const unsigned if_index = if_nametoindex(ifname);
+ if (!if_index) {
+ kr_log_warning(XDP, "warning: interface %s, unexpected error when converting its name: %s\n",
+ ifname, strerror(errno));
+ return;
+ }
+
+ const knot_xdp_mode_t mode = knot_eth_xdp_mode(if_index);
+ switch (mode) {
+ case KNOT_XDP_MODE_FULL:
+ return;
+ case KNOT_XDP_MODE_EMUL:
+ kr_log_warning(XDP, "warning: interface %s running only with XDP emulation\n",
+ ifname);
+ return;
+ case KNOT_XDP_MODE_NONE: // enum warnings from compiler
+ break;
+ }
+ kr_log_warning(XDP, "warning: interface %s running in unexpected XDP mode %d\n",
+ ifname, (int)mode);
+}
+int io_listen_xdp(uv_loop_t *loop, struct endpoint *ep, const char *ifname)
+{
+ if (!ep || !ep->handle) {
+ return kr_error(EINVAL);
+ }
+
+ // RLIMIT_MEMLOCK often needs raising when operating on BPF
+ static int ret_limit = 1;
+ if (ret_limit == 1) {
+ struct rlimit no_limit = { RLIM_INFINITY, RLIM_INFINITY };
+ ret_limit = setrlimit(RLIMIT_MEMLOCK, &no_limit)
+ ? kr_error(errno) : 0;
+ }
+ if (ret_limit) return ret_limit;
+
+ xdp_handle_data_t *xhd = malloc(sizeof(*xhd));
+ if (!xhd) return kr_error(ENOMEM);
+
+ xhd->socket = NULL; // needed for some reason
+
+ // This call is a libknot version hell, unfortunately.
+ int ret = knot_xdp_init(&xhd->socket, ifname, ep->nic_queue,
+ #if KNOT_VERSION_HEX < 0x030100
+ ep->port ? ep->port : KNOT_XDP_LISTEN_PORT_ALL,
+ KNOT_XDP_LOAD_BPF_MAYBE
+ #elif KNOT_VERSION_HEX < 0x030200
+ ep->port ? ep->port : (KNOT_XDP_LISTEN_PORT_PASS | 0),
+ KNOT_XDP_LOAD_BPF_MAYBE
+ #else
+ KNOT_XDP_FILTER_UDP | (ep->port ? 0 : KNOT_XDP_FILTER_PASS),
+ ep->port, 0/*quic_port*/,
+ KNOT_XDP_LOAD_BPF_MAYBE,
+ NULL/*xdp_config*/
+ #endif
+ );
+
+ if (!ret) xdp_warn_mode(ifname);
+
+ if (!ret) ret = uv_idle_init(loop, &xhd->tx_waker);
+ if (ret || kr_fails_assert(xhd->socket)) {
+ free(xhd);
+ return ret == 0 ? kr_error(EINVAL) : kr_error(ret);
+ }
+ xhd->tx_waker.data = xhd->socket;
+
+ ep->fd = knot_xdp_socket_fd(xhd->socket); // probably not useful
+ ret = uv_poll_init(loop, (uv_poll_t *)ep->handle, ep->fd);
+ if (ret) {
+ knot_xdp_deinit(xhd->socket);
+ free(xhd);
+ return kr_error(ret);
+ }
+
+ // beware: this sets poll_handle->data
+ xhd->session = session_new(ep->handle, false, false);
+ kr_require(!session_flags(xhd->session)->outgoing);
+ session_get_sockname(xhd->session)->sa_family = AF_XDP; // to have something in there
+
+ ep->handle->data = xhd;
+ ret = uv_poll_start((uv_poll_t *)ep->handle, UV_READABLE, xdp_rx);
+ return ret;
+}
+#endif
+
+
+int io_create(uv_loop_t *loop, uv_handle_t *handle, int type, unsigned family, bool has_tls, bool has_http)
+{
+ 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, has_http);
+ if (s == NULL) {
+ ret = -1;
+ }
+ return ret;
+}
+
+static void io_deinit(uv_handle_t *handle)
+{
+ if (!handle || !handle->data) {
+ return;
+ }
+ if (handle->type != UV_POLL) {
+ session_free(handle->data);
+ } else {
+ #if ENABLE_XDP
+ xdp_handle_data_t *xhd = handle->data;
+ uv_idle_stop(&xhd->tx_waker);
+ uv_close((uv_handle_t *)&xhd->tx_waker, NULL);
+ session_free(xhd->session);
+ knot_xdp_deinit(xhd->socket);
+ free(xhd);
+ #else
+ kr_assert(false);
+ #endif
+ }
+}
+
+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:
+ kr_assert(false);
+ 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..0e88dc1
--- /dev/null
+++ b/daemon/io.h
@@ -0,0 +1,80 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <lua.h>
+#include <uv.h>
+#include <libknot/packet/pkt.h>
+#include <gnutls/gnutls.h>
+#include "lib/generic/array.h"
+#include "daemon/worker.h"
+#include "daemon/engine.h"
+
+struct tls_ctx;
+struct tls_client_ctx;
+struct io_stream_data;
+
+/** Communication data. */
+struct io_comm_data {
+ /** The original address the data came from. May be that of a proxied
+ * client, if they came through a proxy. May be `NULL` if
+ * the communication did not come from network. */
+ const struct sockaddr *src_addr;
+
+ /** The actual address the resolver is communicating with. May be
+ * the address of a proxy if the communication came through one,
+ * otherwise it will be the same as `src_addr`. May be `NULL` if
+ * the communication did not come from network. */
+ const struct sockaddr *comm_addr;
+
+ /** The original destination address. May be the resolver's address, or
+ * the address of a proxy if the communication came through one. May be
+ * `NULL` if the communication did not come from network. */
+ const struct sockaddr *dst_addr;
+
+ /** Data parsed from a PROXY header. May be `NULL` if the communication
+ * did not come through a proxy, or if the PROXYv2 protocol was not used. */
+ const struct proxy_result *proxy;
+};
+
+/** Bind address into a file-descriptor (only, no libuv). type is e.g. SOCK_DGRAM */
+int io_bind(const struct sockaddr *addr, int type, const endpoint_flags_t *flags);
+/** Initialize a UDP handle and start listening. */
+int io_listen_udp(uv_loop_t *loop, uv_udp_t *handle, int fd);
+/** Initialize a TCP handle and start listening. */
+int io_listen_tcp(uv_loop_t *loop, uv_tcp_t *handle, int fd, int tcp_backlog, bool has_tls, bool has_http);
+/** Initialize a pipe handle and start listening. */
+int io_listen_pipe(uv_loop_t *loop, uv_pipe_t *handle, int fd);
+/** Initialize a poll handle (ep->handle) and start listening over AF_XDP on ifname.
+ * Sets ep->session. */
+int io_listen_xdp(uv_loop_t *loop, struct endpoint *ep, const char *ifname);
+
+/** Control socket / TTY - related functions. */
+void io_tty_process_input(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
+void io_tty_alloc(uv_handle_t *handle, size_t suggested, uv_buf_t *buf);
+void io_tty_accept(uv_stream_t *master, int status);
+struct io_stream_data *io_tty_alloc_data(void);
+
+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, bool has_http);
+void io_free(uv_handle_t *handle);
+
+int io_start_read(uv_handle_t *handle);
+int io_stop_read(uv_handle_t *handle);
+
+/** When uv_handle_t::type == UV_POLL, ::data points to this malloc-ed helper.
+ * (Other cases store a direct struct session pointer in ::data.) */
+typedef struct {
+ struct knot_xdp_socket *socket;
+ struct session *session;
+ uv_idle_t tx_waker;
+} xdp_handle_data_t;
+
diff --git a/daemon/lua/controlsock.test.lua b/daemon/lua/controlsock.test.lua
new file mode 100644
index 0000000..0cce03d
--- /dev/null
+++ b/daemon/lua/controlsock.test.lua
@@ -0,0 +1,169 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local cqsocket = require('cqueues.socket')
+local strerror = require('cqueues.errno').strerror
+local timeout = 5 -- seconds, per socket operation
+
+-- TODO: we get memory leaks from cqueues, but CI runs this without leak detection anyway
+
+local ctrl_sock_txt, ctrl_sock_bin, ctrl_sock_txt_longcmd, ctrl_sock_bin_longcmd
+local ctrl_sock_txt_partcmd, ctrl_sock_bin_partcmd
+
+local function onerr_fail(_, method, errno, stacklevel)
+ local errmsg = string.format('socket error: method %s error %d (%s)',
+ method, errno, strerror(errno))
+ fail(debug.traceback(errmsg, stacklevel))
+end
+
+
+local function switch_to_binary_mode(sock)
+ data = sock:xread(2, nil, timeout)
+ sock:xwrite('__binary\n', nil, timeout)
+ same(data, '> ', 'probably successful switch to binary mode')
+end
+
+local function socket_connect(path)
+ sock = cqsocket.connect({ path = path, nonblock = true })
+ sock:onerror(onerr_fail)
+ sock:setmode('bn', 'bn')
+
+ return sock
+end
+
+local function socket_fixture()
+ local path = worker.cwd..'/control/'..worker.pid
+ same(true, net.listen(path, nil, {kind = 'control'}), 'new control sockets were created')
+
+ ctrl_sock_txt = socket_connect(path)
+ ctrl_sock_txt_longcmd = socket_connect(path)
+ ctrl_sock_txt_partcmd = socket_connect(path)
+
+ ctrl_sock_bin = socket_connect(path)
+ switch_to_binary_mode(ctrl_sock_bin)
+ ctrl_sock_bin_longcmd = socket_connect(path)
+ switch_to_binary_mode(ctrl_sock_bin_longcmd)
+ ctrl_sock_bin_partcmd = socket_connect(path)
+ switch_to_binary_mode(ctrl_sock_bin_partcmd)
+end
+
+local function test_text_prompt()
+ data = ctrl_sock_txt:xread(2, nil, timeout)
+ same(data, '> ', 'text prompt looks like expected')
+end
+
+local function test_text_single_command()
+ local string = "this is test"
+ local input = string.format("'%s'\n", string)
+ local expect = input
+ ctrl_sock_txt:xwrite(input, nil, timeout)
+ data = ctrl_sock_txt:xread(#expect, nil, timeout)
+ same(data, expect,
+ 'text mode returns output in expected format')
+end
+
+local function binary_xread_len(sock)
+ data = sock:xread(4, nil, timeout)
+ local len = tonumber(data:byte(1))
+ for i=2,4 do
+ len = bit.bor(bit.lshift(len, 8), tonumber(data:byte(i)))
+ end
+
+ return len
+end
+
+local function test_binary_more_syscalls()
+ local len
+
+ ctrl_sock_bin:xwrite('worker.p', nil, timeout)
+ worker.sleep(0.01)
+ ctrl_sock_bin:xwrite('id\n', nil, timeout)
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+
+ ctrl_sock_bin:xwrite('worker.p', nil, timeout)
+ worker.sleep(0.01)
+ ctrl_sock_bin:xwrite('id\nworker.id\n', nil, timeout)
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, string.format("'%s'", worker.id),
+ 'binary mode returns string in expected format')
+
+ ctrl_sock_bin:xwrite('worker.pid', nil, timeout)
+ worker.sleep(0.01)
+ ctrl_sock_bin:xwrite('\n', nil, timeout)
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns output in expected format')
+
+ ctrl_sock_bin:xwrite('worker.pid', nil, timeout)
+ worker.sleep(0.01)
+ ctrl_sock_bin:xwrite('\nworker.id', nil, timeout)
+ worker.sleep(0.01)
+ ctrl_sock_bin:xwrite('\n', nil, timeout)
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, string.format("'%s'", worker.id),
+ 'binary mode returns string in expected format')
+
+ ctrl_sock_bin:xwrite('worker.pid\nworker.pid\nworker.pid\nworker.pid\n', nil, timeout)
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+ len = binary_xread_len(ctrl_sock_bin)
+ data = ctrl_sock_bin:xread(len, nil, timeout)
+ same(data, tostring(worker.pid),
+ 'binary mode returns number in expected format')
+end
+
+local function test_close_incomplete_cmd()
+ ctrl_sock_txt_partcmd:xwrite('worker.p', nil, timeout)
+ ctrl_sock_txt_partcmd:close()
+ pass('close text socket with short incomplete command')
+
+ ctrl_sock_bin_partcmd:xwrite('worker.p', nil, timeout)
+ ctrl_sock_bin_partcmd:close()
+ pass('close binary socket with short incomplete command')
+end
+
+
+local function test_close_during_transfer()
+ ctrl_sock_txt_longcmd:xwrite(string.rep('a', 1024*1024*10), nil, timeout)
+ ctrl_sock_txt_longcmd:close()
+ pass('close text socket with long incomplete command')
+
+ ctrl_sock_bin_longcmd:xwrite(string.rep('a', 1024*1024*10), nil, timeout)
+ ctrl_sock_bin_longcmd:close()
+ pass('close binary socket with long incomplete command')
+end
+
+local tests = {
+ socket_fixture,
+ test_text_prompt, -- prompt after connect
+ test_text_single_command,
+ test_text_prompt, -- new prompt when command is finished
+ test_close_incomplete_cmd,
+ test_close_during_transfer,
+ test_binary_more_syscalls,
+ test_text_single_command, -- command in text mode after execute commands in binary mode
+ test_text_prompt, -- new prompt when command is finished
+}
+return tests
diff --git a/daemon/lua/distro-preconfig.lua.in b/daemon/lua/distro-preconfig.lua.in
new file mode 100644
index 0000000..df155c2
--- /dev/null
+++ b/daemon/lua/distro-preconfig.lua.in
@@ -0,0 +1,19 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+log_target('syslog') -- assume running as OS service
+
+local ffi = require('ffi')
+local id = os.getenv('SYSTEMD_INSTANCE')
+if not id then
+ log_warn(ffi.C.LOG_GRP_SYSTEM, 'environment variable $SYSTEMD_INSTANCE not set')
+else
+ -- Bind to control socket in run_dir
+ worker.control_path = '@run_dir@/control/'
+ local path = worker.control_path..id
+ local ok, err = pcall(net.listen, path, nil, { kind = 'control' })
+ if not ok then
+ log_warn(ffi.C.LOG_GRP_NETWORK, 'bind to '..path..' failed '..err)
+ end
+end
+
+-- Set cache location
+rawset(cache, 'current_storage', 'lmdb://@systemd_cache_dir@')
diff --git a/daemon/lua/kluautil.lua b/daemon/lua/kluautil.lua
new file mode 100644
index 0000000..d8569b9
--- /dev/null
+++ b/daemon/lua/kluautil.lua
@@ -0,0 +1,94 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local ffi = require('ffi')
+local kluautil = {}
+
+-- Get length of table
+function kluautil.kr_table_len(t)
+ if type(t) ~= 'table' then
+ return nil
+ end
+
+ local len = 0
+ for _ in pairs(t) do
+ len = len + 1
+ end
+ return len
+end
+
+-- pack varargs including nil arguments into a table
+function kluautil.kr_table_pack(...)
+ local tab = {...}
+ tab.n = select('#', ...)
+ return tab
+end
+
+-- unpack table produced by kr_table_pack and including nil values
+function kluautil.kr_table_unpack(tab)
+ return unpack(tab, 1, tab.n)
+end
+
+-- Fetch over HTTPS
+function kluautil.kr_https_fetch(url, out_file, ca_file)
+ local http_ok, http_request = pcall(require, 'http.request')
+ local httptls_ok, http_tls = pcall(require, 'http.tls')
+ local openssl_ok, openssl_ctx = pcall(require, 'openssl.ssl.context')
+
+ if not http_ok or not httptls_ok or not openssl_ok then
+ return nil, 'error: lua-http and luaossl libraries are missing (but required)'
+ end
+ local cqerrno = require('cqueues.errno')
+
+ assert(string.match(url, '^https://'))
+
+ local req = http_request.new_from_uri(url)
+ req.tls = true
+ if ca_file then
+ req.ctx = openssl_ctx.new()
+ local store = req.ctx:getStore()
+ local load_ok, errmsg = pcall(store.add, store, ca_file)
+ if not load_ok then
+ return nil, errmsg
+ end
+ else -- use defaults
+ req.ctx = http_tls.new_client_context()
+ end
+
+ req.ctx:setVerify(openssl_ctx.VERIFY_PEER)
+
+ local headers, stream, errmsg = req:go()
+ if not headers then
+ errmsg = errmsg or 'unknown error'
+ if type(errmsg) == 'number' then
+ errmsg = cqerrno.strerror(errmsg) ..
+ ' (' .. tostring(errmsg) .. ')'
+ end
+ return nil, 'HTTP client library error: ' .. errmsg
+ end
+ if headers:get(':status') ~= "200" then
+ return nil, 'HTTP status != 200, got ' .. headers:get(':status')
+ end
+
+ local err
+ err, errmsg = stream:save_body_to_file(out_file)
+ if err == nil then
+ return nil, errmsg
+ end
+
+ out_file:seek('set', 0)
+
+ return true
+end
+
+-- Copy a lua string to C (to knot_mm_t or nil=malloc, zero-terminated).
+function kluautil.kr_string2c(str, mempool)
+ if str == nil then return nil end
+ local result = ffi.C.mm_realloc(mempool, nil, #str + 1, 0)
+ if result == nil then panic("not enough memory") end
+ ffi.copy(result, str)
+ return ffi.cast('const char *', result)
+end
+
+kluautil.list_dir = kluautil_list_dir
+
+return kluautil
diff --git a/daemon/lua/kres-gen-30.lua b/daemon/lua/kres-gen-30.lua
new file mode 100644
index 0000000..7639e79
--- /dev/null
+++ b/daemon/lua/kres-gen-30.lua
@@ -0,0 +1,641 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local ffi = require('ffi')
+--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[
+typedef long time_t;
+typedef long __time_t;
+typedef long __suseconds_t;
+struct timeval {
+ __time_t tv_sec;
+ __suseconds_t tv_usec;
+};
+
+typedef struct knot_dump_style knot_dump_style_t;
+extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT;
+struct kr_cdb_api {};
+struct lru {};
+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;
+ uint32_t size;
+ knot_rdata_t *rdata;
+} knot_rdataset_t;
+
+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_request *, const char *);
+typedef void (*trace_callback_f)(struct kr_request *);
+typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen);
+typedef bool (*addr_info_f)(struct sockaddr*);
+typedef void (*zi_callback)(int state, void *param);
+typedef struct {
+ knot_dname_t *_owner;
+ uint32_t _ttl;
+ uint16_t type;
+ uint16_t rclass;
+ knot_rdataset_t rrs;
+ void *additional;
+} knot_rrset_t;
+
+struct kr_module;
+typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *);
+typedef unsigned char knot_dname_storage_t[255];
+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;
+typedef struct knot_compr {
+ uint8_t *wire;
+ knot_rrinfo_t *rrinfo;
+ struct {
+ uint16_t pos;
+ uint8_t labels;
+ } suffix;
+} 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 trie trie_t;
+struct kr_qflags {
+ _Bool NO_MINIMIZE : 1;
+ _Bool NO_IPV6 : 1;
+ _Bool NO_IPV4 : 1;
+ _Bool TCP : 1;
+ _Bool NO_ANSWER : 1;
+ _Bool RESOLVED : 1;
+ _Bool AWAIT_IPV4 : 1;
+ _Bool AWAIT_IPV6 : 1;
+ _Bool AWAIT_CUT : 1;
+ _Bool NO_EDNS : 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;
+ _Bool PKT_IS_SANE : 1;
+ _Bool DNS64_DISABLE : 1;
+};
+typedef 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;
+ _Bool in_progress : 1;
+ _Bool dont_cache : 1;
+ knot_rrset_t *rr;
+} 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 kr_http_header_array_entry {
+ char *name;
+ char *value;
+} kr_http_header_array_entry_t;
+typedef struct {
+ kr_http_header_array_entry_t *at;
+ size_t len;
+ size_t cap;
+} kr_http_header_array_t;
+typedef struct {
+ union kr_sockaddr *at;
+ size_t len;
+ size_t cap;
+} kr_sockaddr_array_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_query *initial;
+ struct kr_request *request;
+ knot_mm_t *pool;
+ uint32_t next_uid;
+};
+struct kr_request_qsource_flags {
+ _Bool tcp : 1;
+ _Bool tls : 1;
+ _Bool http : 1;
+ _Bool xdp : 1;
+};
+struct kr_extended_error {
+ int32_t info_code;
+ const char *extra_text;
+};
+struct kr_request {
+ struct kr_context *ctx;
+ knot_pkt_t *answer;
+ struct kr_query *current_query;
+ struct {
+ const struct sockaddr *addr;
+ const struct sockaddr *comm_addr;
+ const struct sockaddr *dst_addr;
+ const knot_pkt_t *packet;
+ struct kr_request_qsource_flags flags;
+ struct kr_request_qsource_flags comm_flags;
+ size_t size;
+ int32_t stream_id;
+ kr_http_header_array_t headers;
+ } qsource;
+ struct {
+ unsigned int rtt;
+ const struct kr_transport *transport;
+ } 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;
+ _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;
+ struct {
+ addr_info_f is_tls_capable;
+ addr_info_f is_tcp_connected;
+ addr_info_f is_tcp_waiting;
+ kr_sockaddr_array_t forwarding_targets;
+ } selection_context;
+ unsigned int count_no_nsaddr;
+ unsigned int count_fail_row;
+ alloc_wire_f alloc_wire_cb;
+ struct kr_extended_error extended_error;
+};
+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};
+typedef struct kr_cdb * kr_cdb_pt;
+struct kr_cdb_stats {
+ uint64_t open;
+ uint64_t close;
+ uint64_t count;
+ uint64_t count_entries;
+ uint64_t clear;
+ uint64_t commit;
+ uint64_t read;
+ uint64_t read_miss;
+ uint64_t write;
+ uint64_t remove;
+ uint64_t remove_miss;
+ uint64_t match;
+ uint64_t match_miss;
+ uint64_t read_leq;
+ uint64_t read_leq_miss;
+ double usage_percent;
+};
+typedef struct uv_timer_s uv_timer_t;
+struct kr_cache {
+ kr_cdb_pt db;
+ const struct kr_cdb_api *api;
+ struct kr_cdb_stats stats;
+ uint32_t ttl_min;
+ uint32_t ttl_max;
+ struct timeval checkpoint_walltime;
+ uint64_t checkpoint_monotime;
+ uv_timer_t *health_timer;
+};
+typedef struct kr_layer {
+ int state;
+ struct kr_request *req;
+ const struct kr_layer_api *api;
+ knot_pkt_t *pkt;
+ struct sockaddr *dst;
+ _Bool is_stream;
+} kr_layer_t;
+typedef struct kr_layer_api {
+ int (*begin)(kr_layer_t *);
+ int (*reset)(kr_layer_t *);
+ int (*finish)(kr_layer_t *);
+ int (*consume)(kr_layer_t *, knot_pkt_t *);
+ int (*produce)(kr_layer_t *, knot_pkt_t *);
+ int (*checkout)(kr_layer_t *, knot_pkt_t *, struct sockaddr *, int);
+ int (*answer_finalize)(kr_layer_t *);
+ void *data;
+ int cb_slots[];
+} kr_layer_api_t;
+struct kr_prop {
+ kr_prop_cb *cb;
+ const char *name;
+ const char *info;
+};
+struct kr_module {
+ char *name;
+ int (*init)(struct kr_module *);
+ int (*deinit)(struct kr_module *);
+ int (*config)(struct kr_module *, const char *);
+ const kr_layer_api_t *layer;
+ const struct kr_prop *props;
+ void *lib;
+ void *data;
+};
+struct kr_server_selection {
+ _Bool initialized;
+ void (*choose_transport)(struct kr_query *, struct kr_transport **);
+ void (*update_rtt)(struct kr_query *, const struct kr_transport *, unsigned int);
+ void (*error)(struct kr_query *, const struct kr_transport *, enum kr_selection_error);
+ struct local_state *local_state;
+};
+typedef int kr_log_level_t;
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+
+kr_layer_t kr_layer_t_static;
+_Bool kr_dbg_assertion_abort;
+int kr_dbg_assertion_fork;
+
+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_query {
+ struct kr_query *parent;
+ knot_dname_t *sname;
+ uint16_t stype;
+ uint16_t sclass;
+ uint16_t id;
+ uint16_t reorder;
+ struct kr_qflags flags;
+ struct kr_qflags forward_flags;
+ uint32_t secret;
+ uint32_t uid;
+ int32_t vld_limit_crypto_remains;
+ uint32_t vld_limit_uid;
+ uint64_t creation_time_mono;
+ uint64_t timestamp_mono;
+ struct timeval timestamp;
+ struct kr_zonecut zone_cut;
+ struct kr_layer_pickle *deferred;
+ int8_t cname_depth;
+ struct kr_query *cname_parent;
+ struct kr_request *request;
+ kr_stale_cb stale_cb;
+ struct kr_server_selection server_selection;
+};
+struct kr_context {
+ struct kr_qflags options;
+ knot_rrset_t *downstream_opt_rr;
+ knot_rrset_t *upstream_opt_rr;
+ trie_t *trust_anchors;
+ trie_t *negative_anchors;
+ int32_t vld_limit_crypto;
+ struct kr_zonecut root_hints;
+ struct kr_cache cache;
+ unsigned int cache_rtt_tout_retry_interval;
+ char _stub[];
+};
+struct kr_transport {
+ knot_dname_t *ns_name;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+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 *);
+void knot_rrset_free(knot_rrset_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);
+knot_rrset_t *kr_request_ensure_edns(struct kr_request *);
+knot_pkt_t *kr_request_ensure_answer(struct kr_request *);
+int kr_request_set_extended_error(struct kr_request *, int, const char *);
+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_forward_add_target(struct kr_request *, const struct sockaddr *);
+_Bool kr_log_is_debug_fun(enum kr_log_group, const struct kr_request *);
+void kr_log_req1(const struct kr_request * const, uint32_t, const unsigned int, enum kr_log_group, const char *, const char *, ...);
+void kr_log_q1(const struct kr_query * const, enum kr_log_group, const char *, const char *, ...);
+const char *kr_log_grp2name(enum kr_log_group);
+void kr_log_fmt(enum kr_log_group, kr_log_level_t, const char *, const char *, const char *, const char *, ...);
+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 *);
+_Bool kr_pkt_has_wire(const knot_pkt_t *);
+_Bool kr_pkt_has_dnssec(const knot_pkt_t *);
+uint16_t kr_pkt_qclass(const knot_pkt_t *);
+uint16_t kr_pkt_qtype(const knot_pkt_t *);
+char *kr_pkt_text(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_cmp(const struct sockaddr *, const struct sockaddr *);
+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, knot_mm_t *);
+int kr_straddr_split(const char *, char * restrict, uint16_t *);
+_Bool kr_rank_test(uint8_t, uint8_t);
+int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *);
+int kr_ranked_rrarray_finalize(ranked_rr_array_t *, 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(void);
+const char *kr_strptime_diff(const char *, const char *, const char *, double *);
+time_t kr_file_mtime(const char *);
+long long kr_fssize(const char *);
+const char *kr_dirent_name(const struct dirent *);
+void lru_free_items_impl(struct lru *);
+struct lru *lru_create_impl(unsigned int, 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(trie_t *, const knot_dname_t *);
+int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t);
+int kr_ta_del(trie_t *, const knot_dname_t *);
+void kr_ta_clear(trie_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, _Bool);
+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_commit(struct kr_cache *);
+uint32_t packet_ttl(const knot_pkt_t *);
+typedef struct {
+ int sock_type;
+ _Bool tls;
+ _Bool http;
+ _Bool xdp;
+ _Bool freebind;
+ const char *kind;
+} endpoint_flags_t;
+typedef struct {
+ char **at;
+ size_t len;
+ size_t cap;
+} addr_array_t;
+typedef struct {
+ int fd;
+ endpoint_flags_t flags;
+} flagged_fd_t;
+typedef struct {
+ flagged_fd_t *at;
+ size_t len;
+ size_t cap;
+} flagged_fd_array_t;
+typedef struct {
+ const char **at;
+ size_t len;
+ size_t cap;
+} config_array_t;
+struct args {
+ addr_array_t addrs;
+ addr_array_t addrs_tls;
+ flagged_fd_array_t fds;
+ int control_fd;
+ int forks;
+ config_array_t config;
+ const char *rundir;
+ _Bool interactive;
+ _Bool quiet;
+ _Bool tty_binary_output;
+};
+typedef struct {
+ const char *zone_file;
+ const char *origin;
+ uint32_t ttl;
+ enum {ZI_STAMP_NOW, ZI_STAMP_MTIM} time_src;
+ _Bool downgrade;
+ _Bool zonemd;
+ const knot_rrset_t *ds;
+ zi_callback cb;
+ void *cb_param;
+} zi_config_t;
+struct args *the_args;
+struct endpoint {
+ void *handle;
+ int fd;
+ int family;
+ uint16_t port;
+ int16_t nic_queue;
+ _Bool engaged;
+ endpoint_flags_t flags;
+};
+struct request_ctx {
+ struct kr_request req;
+ struct worker_ctx *worker;
+ struct qr_task *task;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+struct qr_task {
+ struct request_ctx *ctx;
+ /* beware: hidden stub, to avoid qr_tasklist_t */
+};
+int worker_resolve_exec(struct qr_task *, knot_pkt_t *);
+knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *);
+struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags);
+int zi_zone_import(const zi_config_t);
+struct engine {
+ struct kr_context resolver;
+ char _stub[];
+};
+struct worker_ctx {
+ struct engine *engine;
+ char _stub[];
+};
+struct worker_ctx *the_worker;
+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;
+typedef 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 (*comment)(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];
+} zs_scanner_t;
+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-31.lua b/daemon/lua/kres-gen-31.lua
new file mode 100644
index 0000000..e555a6a
--- /dev/null
+++ b/daemon/lua/kres-gen-31.lua
@@ -0,0 +1,650 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local ffi = require('ffi')
+--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[
+typedef long time_t;
+typedef long __time_t;
+typedef long __suseconds_t;
+struct timeval {
+ __time_t tv_sec;
+ __suseconds_t tv_usec;
+};
+
+typedef struct knot_dump_style knot_dump_style_t;
+extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT;
+struct kr_cdb_api {};
+struct lru {};
+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;
+ uint32_t size;
+ knot_rdata_t *rdata;
+} knot_rdataset_t;
+
+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_request *, const char *);
+typedef void (*trace_callback_f)(struct kr_request *);
+typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen);
+typedef bool (*addr_info_f)(struct sockaddr*);
+typedef void (*zi_callback)(int state, void *param);
+typedef struct {
+ knot_dname_t *_owner;
+ uint32_t _ttl;
+ uint16_t type;
+ uint16_t rclass;
+ knot_rdataset_t rrs;
+ void *additional;
+} knot_rrset_t;
+
+struct kr_module;
+typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *);
+typedef unsigned char knot_dname_storage_t[255];
+typedef struct knot_pkt knot_pkt_t;
+typedef struct {
+ uint8_t *ptr[18];
+} knot_edns_options_t;
+typedef struct {
+ knot_pkt_t *pkt;
+ uint16_t pos;
+ uint16_t count;
+} knot_pktsection_t;
+typedef struct knot_compr {
+ uint8_t *wire;
+ knot_rrinfo_t *rrinfo;
+ struct {
+ uint16_t pos;
+ uint8_t labels;
+ } suffix;
+} 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 trie trie_t;
+struct kr_qflags {
+ _Bool NO_MINIMIZE : 1;
+ _Bool NO_IPV6 : 1;
+ _Bool NO_IPV4 : 1;
+ _Bool TCP : 1;
+ _Bool NO_ANSWER : 1;
+ _Bool RESOLVED : 1;
+ _Bool AWAIT_IPV4 : 1;
+ _Bool AWAIT_IPV6 : 1;
+ _Bool AWAIT_CUT : 1;
+ _Bool NO_EDNS : 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;
+ _Bool PKT_IS_SANE : 1;
+ _Bool DNS64_DISABLE : 1;
+};
+typedef 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;
+ _Bool in_progress : 1;
+ _Bool dont_cache : 1;
+ knot_rrset_t *rr;
+} 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 kr_http_header_array_entry {
+ char *name;
+ char *value;
+} kr_http_header_array_entry_t;
+typedef struct {
+ kr_http_header_array_entry_t *at;
+ size_t len;
+ size_t cap;
+} kr_http_header_array_t;
+typedef struct {
+ union kr_sockaddr *at;
+ size_t len;
+ size_t cap;
+} kr_sockaddr_array_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_query *initial;
+ struct kr_request *request;
+ knot_mm_t *pool;
+ uint32_t next_uid;
+};
+struct kr_request_qsource_flags {
+ _Bool tcp : 1;
+ _Bool tls : 1;
+ _Bool http : 1;
+ _Bool xdp : 1;
+};
+struct kr_extended_error {
+ int32_t info_code;
+ const char *extra_text;
+};
+struct kr_request {
+ struct kr_context *ctx;
+ knot_pkt_t *answer;
+ struct kr_query *current_query;
+ struct {
+ const struct sockaddr *addr;
+ const struct sockaddr *comm_addr;
+ const struct sockaddr *dst_addr;
+ const knot_pkt_t *packet;
+ struct kr_request_qsource_flags flags;
+ struct kr_request_qsource_flags comm_flags;
+ size_t size;
+ int32_t stream_id;
+ kr_http_header_array_t headers;
+ } qsource;
+ struct {
+ unsigned int rtt;
+ const struct kr_transport *transport;
+ } 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;
+ _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;
+ struct {
+ addr_info_f is_tls_capable;
+ addr_info_f is_tcp_connected;
+ addr_info_f is_tcp_waiting;
+ kr_sockaddr_array_t forwarding_targets;
+ } selection_context;
+ unsigned int count_no_nsaddr;
+ unsigned int count_fail_row;
+ alloc_wire_f alloc_wire_cb;
+ struct kr_extended_error extended_error;
+};
+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};
+typedef struct kr_cdb * kr_cdb_pt;
+struct kr_cdb_stats {
+ uint64_t open;
+ uint64_t close;
+ uint64_t count;
+ uint64_t count_entries;
+ uint64_t clear;
+ uint64_t commit;
+ uint64_t read;
+ uint64_t read_miss;
+ uint64_t write;
+ uint64_t remove;
+ uint64_t remove_miss;
+ uint64_t match;
+ uint64_t match_miss;
+ uint64_t read_leq;
+ uint64_t read_leq_miss;
+ double usage_percent;
+};
+typedef struct uv_timer_s uv_timer_t;
+struct kr_cache {
+ kr_cdb_pt db;
+ const struct kr_cdb_api *api;
+ struct kr_cdb_stats stats;
+ uint32_t ttl_min;
+ uint32_t ttl_max;
+ struct timeval checkpoint_walltime;
+ uint64_t checkpoint_monotime;
+ uv_timer_t *health_timer;
+};
+typedef struct kr_layer {
+ int state;
+ struct kr_request *req;
+ const struct kr_layer_api *api;
+ knot_pkt_t *pkt;
+ struct sockaddr *dst;
+ _Bool is_stream;
+} kr_layer_t;
+typedef struct kr_layer_api {
+ int (*begin)(kr_layer_t *);
+ int (*reset)(kr_layer_t *);
+ int (*finish)(kr_layer_t *);
+ int (*consume)(kr_layer_t *, knot_pkt_t *);
+ int (*produce)(kr_layer_t *, knot_pkt_t *);
+ int (*checkout)(kr_layer_t *, knot_pkt_t *, struct sockaddr *, int);
+ int (*answer_finalize)(kr_layer_t *);
+ void *data;
+ int cb_slots[];
+} kr_layer_api_t;
+struct kr_prop {
+ kr_prop_cb *cb;
+ const char *name;
+ const char *info;
+};
+struct kr_module {
+ char *name;
+ int (*init)(struct kr_module *);
+ int (*deinit)(struct kr_module *);
+ int (*config)(struct kr_module *, const char *);
+ const kr_layer_api_t *layer;
+ const struct kr_prop *props;
+ void *lib;
+ void *data;
+};
+struct kr_server_selection {
+ _Bool initialized;
+ void (*choose_transport)(struct kr_query *, struct kr_transport **);
+ void (*update_rtt)(struct kr_query *, const struct kr_transport *, unsigned int);
+ void (*error)(struct kr_query *, const struct kr_transport *, enum kr_selection_error);
+ struct local_state *local_state;
+};
+typedef int kr_log_level_t;
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+
+kr_layer_t kr_layer_t_static;
+_Bool kr_dbg_assertion_abort;
+int kr_dbg_assertion_fork;
+
+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_query {
+ struct kr_query *parent;
+ knot_dname_t *sname;
+ uint16_t stype;
+ uint16_t sclass;
+ uint16_t id;
+ uint16_t reorder;
+ struct kr_qflags flags;
+ struct kr_qflags forward_flags;
+ uint32_t secret;
+ uint32_t uid;
+ int32_t vld_limit_crypto_remains;
+ uint32_t vld_limit_uid;
+ uint64_t creation_time_mono;
+ uint64_t timestamp_mono;
+ struct timeval timestamp;
+ struct kr_zonecut zone_cut;
+ struct kr_layer_pickle *deferred;
+ int8_t cname_depth;
+ struct kr_query *cname_parent;
+ struct kr_request *request;
+ kr_stale_cb stale_cb;
+ struct kr_server_selection server_selection;
+};
+struct kr_context {
+ struct kr_qflags options;
+ knot_rrset_t *downstream_opt_rr;
+ knot_rrset_t *upstream_opt_rr;
+ trie_t *trust_anchors;
+ trie_t *negative_anchors;
+ int32_t vld_limit_crypto;
+ struct kr_zonecut root_hints;
+ struct kr_cache cache;
+ unsigned int cache_rtt_tout_retry_interval;
+ char _stub[];
+};
+struct kr_transport {
+ knot_dname_t *ns_name;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+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 *);
+void knot_rrset_free(knot_rrset_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);
+knot_rrset_t *kr_request_ensure_edns(struct kr_request *);
+knot_pkt_t *kr_request_ensure_answer(struct kr_request *);
+int kr_request_set_extended_error(struct kr_request *, int, const char *);
+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_forward_add_target(struct kr_request *, const struct sockaddr *);
+_Bool kr_log_is_debug_fun(enum kr_log_group, const struct kr_request *);
+void kr_log_req1(const struct kr_request * const, uint32_t, const unsigned int, enum kr_log_group, const char *, const char *, ...);
+void kr_log_q1(const struct kr_query * const, enum kr_log_group, const char *, const char *, ...);
+const char *kr_log_grp2name(enum kr_log_group);
+void kr_log_fmt(enum kr_log_group, kr_log_level_t, const char *, const char *, const char *, const char *, ...);
+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 *);
+_Bool kr_pkt_has_wire(const knot_pkt_t *);
+_Bool kr_pkt_has_dnssec(const knot_pkt_t *);
+uint16_t kr_pkt_qclass(const knot_pkt_t *);
+uint16_t kr_pkt_qtype(const knot_pkt_t *);
+char *kr_pkt_text(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_cmp(const struct sockaddr *, const struct sockaddr *);
+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, knot_mm_t *);
+int kr_straddr_split(const char *, char * restrict, uint16_t *);
+_Bool kr_rank_test(uint8_t, uint8_t);
+int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *);
+int kr_ranked_rrarray_finalize(ranked_rr_array_t *, 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(void);
+const char *kr_strptime_diff(const char *, const char *, const char *, double *);
+time_t kr_file_mtime(const char *);
+long long kr_fssize(const char *);
+const char *kr_dirent_name(const struct dirent *);
+void lru_free_items_impl(struct lru *);
+struct lru *lru_create_impl(unsigned int, 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(trie_t *, const knot_dname_t *);
+int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t);
+int kr_ta_del(trie_t *, const knot_dname_t *);
+void kr_ta_clear(trie_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, _Bool);
+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_commit(struct kr_cache *);
+uint32_t packet_ttl(const knot_pkt_t *);
+typedef struct {
+ int sock_type;
+ _Bool tls;
+ _Bool http;
+ _Bool xdp;
+ _Bool freebind;
+ const char *kind;
+} endpoint_flags_t;
+typedef struct {
+ char **at;
+ size_t len;
+ size_t cap;
+} addr_array_t;
+typedef struct {
+ int fd;
+ endpoint_flags_t flags;
+} flagged_fd_t;
+typedef struct {
+ flagged_fd_t *at;
+ size_t len;
+ size_t cap;
+} flagged_fd_array_t;
+typedef struct {
+ const char **at;
+ size_t len;
+ size_t cap;
+} config_array_t;
+struct args {
+ addr_array_t addrs;
+ addr_array_t addrs_tls;
+ flagged_fd_array_t fds;
+ int control_fd;
+ int forks;
+ config_array_t config;
+ const char *rundir;
+ _Bool interactive;
+ _Bool quiet;
+ _Bool tty_binary_output;
+};
+typedef struct {
+ const char *zone_file;
+ const char *origin;
+ uint32_t ttl;
+ enum {ZI_STAMP_NOW, ZI_STAMP_MTIM} time_src;
+ _Bool downgrade;
+ _Bool zonemd;
+ const knot_rrset_t *ds;
+ zi_callback cb;
+ void *cb_param;
+} zi_config_t;
+struct args *the_args;
+struct endpoint {
+ void *handle;
+ int fd;
+ int family;
+ uint16_t port;
+ int16_t nic_queue;
+ _Bool engaged;
+ endpoint_flags_t flags;
+};
+struct request_ctx {
+ struct kr_request req;
+ struct worker_ctx *worker;
+ struct qr_task *task;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+struct qr_task {
+ struct request_ctx *ctx;
+ /* beware: hidden stub, to avoid qr_tasklist_t */
+};
+int worker_resolve_exec(struct qr_task *, knot_pkt_t *);
+knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *);
+struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags);
+int zi_zone_import(const zi_config_t);
+struct engine {
+ struct kr_context resolver;
+ char _stub[];
+};
+struct worker_ctx {
+ struct engine *engine;
+ char _stub[];
+};
+struct worker_ctx *the_worker;
+typedef struct {
+ uint8_t *params_position;
+ uint8_t *mandatory_position;
+ uint8_t *param_position;
+ int32_t last_key;
+} zs_svcb_t;
+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;
+typedef 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;
+ uint8_t *item_length2_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;
+ zs_svcb_t svcb;
+ uint8_t addr[16];
+ _Bool long_string;
+ _Bool comma_list;
+ 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 (*comment)(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];
+} zs_scanner_t;
+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-32.lua b/daemon/lua/kres-gen-32.lua
new file mode 100644
index 0000000..31a5c5d
--- /dev/null
+++ b/daemon/lua/kres-gen-32.lua
@@ -0,0 +1,651 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local ffi = require('ffi')
+--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[
+typedef long time_t;
+typedef long __time_t;
+typedef long __suseconds_t;
+struct timeval {
+ __time_t tv_sec;
+ __suseconds_t tv_usec;
+};
+
+typedef struct knot_dump_style knot_dump_style_t;
+extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT;
+struct kr_cdb_api {};
+struct lru {};
+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;
+ uint32_t size;
+ knot_rdata_t *rdata;
+} knot_rdataset_t;
+
+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_request *, const char *);
+typedef void (*trace_callback_f)(struct kr_request *);
+typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen);
+typedef bool (*addr_info_f)(struct sockaddr*);
+typedef void (*zi_callback)(int state, void *param);
+typedef struct {
+ knot_dname_t *_owner;
+ uint32_t _ttl;
+ uint16_t type;
+ uint16_t rclass;
+ knot_rdataset_t rrs;
+ void *additional;
+} knot_rrset_t;
+
+struct kr_module;
+typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *);
+typedef unsigned char knot_dname_storage_t[255];
+typedef struct knot_pkt knot_pkt_t;
+typedef struct {
+ uint8_t *ptr[18];
+} knot_edns_options_t;
+typedef struct {
+ knot_pkt_t *pkt;
+ uint16_t pos;
+ uint16_t count;
+} knot_pktsection_t;
+typedef struct knot_compr {
+ uint8_t *wire;
+ knot_rrinfo_t *rrinfo;
+ struct {
+ uint16_t pos;
+ uint8_t labels;
+ } suffix;
+} 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;
+ knot_dname_storage_t lower_qname;
+};
+typedef struct trie trie_t;
+struct kr_qflags {
+ _Bool NO_MINIMIZE : 1;
+ _Bool NO_IPV6 : 1;
+ _Bool NO_IPV4 : 1;
+ _Bool TCP : 1;
+ _Bool NO_ANSWER : 1;
+ _Bool RESOLVED : 1;
+ _Bool AWAIT_IPV4 : 1;
+ _Bool AWAIT_IPV6 : 1;
+ _Bool AWAIT_CUT : 1;
+ _Bool NO_EDNS : 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;
+ _Bool PKT_IS_SANE : 1;
+ _Bool DNS64_DISABLE : 1;
+};
+typedef 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;
+ _Bool in_progress : 1;
+ _Bool dont_cache : 1;
+ knot_rrset_t *rr;
+} 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 kr_http_header_array_entry {
+ char *name;
+ char *value;
+} kr_http_header_array_entry_t;
+typedef struct {
+ kr_http_header_array_entry_t *at;
+ size_t len;
+ size_t cap;
+} kr_http_header_array_t;
+typedef struct {
+ union kr_sockaddr *at;
+ size_t len;
+ size_t cap;
+} kr_sockaddr_array_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_query *initial;
+ struct kr_request *request;
+ knot_mm_t *pool;
+ uint32_t next_uid;
+};
+struct kr_request_qsource_flags {
+ _Bool tcp : 1;
+ _Bool tls : 1;
+ _Bool http : 1;
+ _Bool xdp : 1;
+};
+struct kr_extended_error {
+ int32_t info_code;
+ const char *extra_text;
+};
+struct kr_request {
+ struct kr_context *ctx;
+ knot_pkt_t *answer;
+ struct kr_query *current_query;
+ struct {
+ const struct sockaddr *addr;
+ const struct sockaddr *comm_addr;
+ const struct sockaddr *dst_addr;
+ const knot_pkt_t *packet;
+ struct kr_request_qsource_flags flags;
+ struct kr_request_qsource_flags comm_flags;
+ size_t size;
+ int32_t stream_id;
+ kr_http_header_array_t headers;
+ } qsource;
+ struct {
+ unsigned int rtt;
+ const struct kr_transport *transport;
+ } 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;
+ _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;
+ struct {
+ addr_info_f is_tls_capable;
+ addr_info_f is_tcp_connected;
+ addr_info_f is_tcp_waiting;
+ kr_sockaddr_array_t forwarding_targets;
+ } selection_context;
+ unsigned int count_no_nsaddr;
+ unsigned int count_fail_row;
+ alloc_wire_f alloc_wire_cb;
+ struct kr_extended_error extended_error;
+};
+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};
+typedef struct kr_cdb * kr_cdb_pt;
+struct kr_cdb_stats {
+ uint64_t open;
+ uint64_t close;
+ uint64_t count;
+ uint64_t count_entries;
+ uint64_t clear;
+ uint64_t commit;
+ uint64_t read;
+ uint64_t read_miss;
+ uint64_t write;
+ uint64_t remove;
+ uint64_t remove_miss;
+ uint64_t match;
+ uint64_t match_miss;
+ uint64_t read_leq;
+ uint64_t read_leq_miss;
+ double usage_percent;
+};
+typedef struct uv_timer_s uv_timer_t;
+struct kr_cache {
+ kr_cdb_pt db;
+ const struct kr_cdb_api *api;
+ struct kr_cdb_stats stats;
+ uint32_t ttl_min;
+ uint32_t ttl_max;
+ struct timeval checkpoint_walltime;
+ uint64_t checkpoint_monotime;
+ uv_timer_t *health_timer;
+};
+typedef struct kr_layer {
+ int state;
+ struct kr_request *req;
+ const struct kr_layer_api *api;
+ knot_pkt_t *pkt;
+ struct sockaddr *dst;
+ _Bool is_stream;
+} kr_layer_t;
+typedef struct kr_layer_api {
+ int (*begin)(kr_layer_t *);
+ int (*reset)(kr_layer_t *);
+ int (*finish)(kr_layer_t *);
+ int (*consume)(kr_layer_t *, knot_pkt_t *);
+ int (*produce)(kr_layer_t *, knot_pkt_t *);
+ int (*checkout)(kr_layer_t *, knot_pkt_t *, struct sockaddr *, int);
+ int (*answer_finalize)(kr_layer_t *);
+ void *data;
+ int cb_slots[];
+} kr_layer_api_t;
+struct kr_prop {
+ kr_prop_cb *cb;
+ const char *name;
+ const char *info;
+};
+struct kr_module {
+ char *name;
+ int (*init)(struct kr_module *);
+ int (*deinit)(struct kr_module *);
+ int (*config)(struct kr_module *, const char *);
+ const kr_layer_api_t *layer;
+ const struct kr_prop *props;
+ void *lib;
+ void *data;
+};
+struct kr_server_selection {
+ _Bool initialized;
+ void (*choose_transport)(struct kr_query *, struct kr_transport **);
+ void (*update_rtt)(struct kr_query *, const struct kr_transport *, unsigned int);
+ void (*error)(struct kr_query *, const struct kr_transport *, enum kr_selection_error);
+ struct local_state *local_state;
+};
+typedef int kr_log_level_t;
+enum kr_log_group {LOG_GRP_UNKNOWN = -1, LOG_GRP_SYSTEM = 1, LOG_GRP_CACHE, LOG_GRP_IO, LOG_GRP_NETWORK, LOG_GRP_TA, LOG_GRP_TLS, LOG_GRP_GNUTLS, LOG_GRP_TLSCLIENT, LOG_GRP_XDP, LOG_GRP_DOH, LOG_GRP_DNSSEC, LOG_GRP_HINT, LOG_GRP_PLAN, LOG_GRP_ITERATOR, LOG_GRP_VALIDATOR, LOG_GRP_RESOLVER, LOG_GRP_SELECTION, LOG_GRP_ZCUT, LOG_GRP_COOKIES, LOG_GRP_STATISTICS, LOG_GRP_REBIND, LOG_GRP_WORKER, LOG_GRP_POLICY, LOG_GRP_TASENTINEL, LOG_GRP_TASIGNALING, LOG_GRP_TAUPDATE, LOG_GRP_DAF, LOG_GRP_DETECTTIMEJUMP, LOG_GRP_DETECTTIMESKEW, LOG_GRP_GRAPHITE, LOG_GRP_PREFILL, LOG_GRP_PRIMING, LOG_GRP_SRVSTALE, LOG_GRP_WATCHDOG, LOG_GRP_NSID, LOG_GRP_DNSTAP, LOG_GRP_TESTS, LOG_GRP_DOTAUTH, LOG_GRP_HTTP, LOG_GRP_CONTROL, LOG_GRP_MODULE, LOG_GRP_DEVEL, LOG_GRP_RENUMBER, LOG_GRP_EDE, LOG_GRP_REQDBG};
+
+kr_layer_t kr_layer_t_static;
+_Bool kr_dbg_assertion_abort;
+int kr_dbg_assertion_fork;
+
+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_query {
+ struct kr_query *parent;
+ knot_dname_t *sname;
+ uint16_t stype;
+ uint16_t sclass;
+ uint16_t id;
+ uint16_t reorder;
+ struct kr_qflags flags;
+ struct kr_qflags forward_flags;
+ uint32_t secret;
+ uint32_t uid;
+ int32_t vld_limit_crypto_remains;
+ uint32_t vld_limit_uid;
+ uint64_t creation_time_mono;
+ uint64_t timestamp_mono;
+ struct timeval timestamp;
+ struct kr_zonecut zone_cut;
+ struct kr_layer_pickle *deferred;
+ int8_t cname_depth;
+ struct kr_query *cname_parent;
+ struct kr_request *request;
+ kr_stale_cb stale_cb;
+ struct kr_server_selection server_selection;
+};
+struct kr_context {
+ struct kr_qflags options;
+ knot_rrset_t *downstream_opt_rr;
+ knot_rrset_t *upstream_opt_rr;
+ trie_t *trust_anchors;
+ trie_t *negative_anchors;
+ int32_t vld_limit_crypto;
+ struct kr_zonecut root_hints;
+ struct kr_cache cache;
+ unsigned int cache_rtt_tout_retry_interval;
+ char _stub[];
+};
+struct kr_transport {
+ knot_dname_t *ns_name;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+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 *);
+void knot_rrset_free(knot_rrset_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);
+knot_rrset_t *kr_request_ensure_edns(struct kr_request *);
+knot_pkt_t *kr_request_ensure_answer(struct kr_request *);
+int kr_request_set_extended_error(struct kr_request *, int, const char *);
+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_forward_add_target(struct kr_request *, const struct sockaddr *);
+_Bool kr_log_is_debug_fun(enum kr_log_group, const struct kr_request *);
+void kr_log_req1(const struct kr_request * const, uint32_t, const unsigned int, enum kr_log_group, const char *, const char *, ...);
+void kr_log_q1(const struct kr_query * const, enum kr_log_group, const char *, const char *, ...);
+const char *kr_log_grp2name(enum kr_log_group);
+void kr_log_fmt(enum kr_log_group, kr_log_level_t, const char *, const char *, const char *, const char *, ...);
+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 *);
+_Bool kr_pkt_has_wire(const knot_pkt_t *);
+_Bool kr_pkt_has_dnssec(const knot_pkt_t *);
+uint16_t kr_pkt_qclass(const knot_pkt_t *);
+uint16_t kr_pkt_qtype(const knot_pkt_t *);
+char *kr_pkt_text(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_cmp(const struct sockaddr *, const struct sockaddr *);
+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, knot_mm_t *);
+int kr_straddr_split(const char *, char * restrict, uint16_t *);
+_Bool kr_rank_test(uint8_t, uint8_t);
+int kr_ranked_rrarray_add(ranked_rr_array_t *, const knot_rrset_t *, uint8_t, _Bool, uint32_t, knot_mm_t *);
+int kr_ranked_rrarray_finalize(ranked_rr_array_t *, 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(void);
+const char *kr_strptime_diff(const char *, const char *, const char *, double *);
+time_t kr_file_mtime(const char *);
+long long kr_fssize(const char *);
+const char *kr_dirent_name(const struct dirent *);
+void lru_free_items_impl(struct lru *);
+struct lru *lru_create_impl(unsigned int, 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(trie_t *, const knot_dname_t *);
+int kr_ta_add(trie_t *, const knot_dname_t *, uint16_t, uint32_t, const uint8_t *, uint16_t);
+int kr_ta_del(trie_t *, const knot_dname_t *);
+void kr_ta_clear(trie_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, _Bool);
+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_commit(struct kr_cache *);
+uint32_t packet_ttl(const knot_pkt_t *);
+typedef struct {
+ int sock_type;
+ _Bool tls;
+ _Bool http;
+ _Bool xdp;
+ _Bool freebind;
+ const char *kind;
+} endpoint_flags_t;
+typedef struct {
+ char **at;
+ size_t len;
+ size_t cap;
+} addr_array_t;
+typedef struct {
+ int fd;
+ endpoint_flags_t flags;
+} flagged_fd_t;
+typedef struct {
+ flagged_fd_t *at;
+ size_t len;
+ size_t cap;
+} flagged_fd_array_t;
+typedef struct {
+ const char **at;
+ size_t len;
+ size_t cap;
+} config_array_t;
+struct args {
+ addr_array_t addrs;
+ addr_array_t addrs_tls;
+ flagged_fd_array_t fds;
+ int control_fd;
+ int forks;
+ config_array_t config;
+ const char *rundir;
+ _Bool interactive;
+ _Bool quiet;
+ _Bool tty_binary_output;
+};
+typedef struct {
+ const char *zone_file;
+ const char *origin;
+ uint32_t ttl;
+ enum {ZI_STAMP_NOW, ZI_STAMP_MTIM} time_src;
+ _Bool downgrade;
+ _Bool zonemd;
+ const knot_rrset_t *ds;
+ zi_callback cb;
+ void *cb_param;
+} zi_config_t;
+struct args *the_args;
+struct endpoint {
+ void *handle;
+ int fd;
+ int family;
+ uint16_t port;
+ int16_t nic_queue;
+ _Bool engaged;
+ endpoint_flags_t flags;
+};
+struct request_ctx {
+ struct kr_request req;
+ struct worker_ctx *worker;
+ struct qr_task *task;
+ /* beware: hidden stub, to avoid hardcoding sockaddr lengths */
+};
+struct qr_task {
+ struct request_ctx *ctx;
+ /* beware: hidden stub, to avoid qr_tasklist_t */
+};
+int worker_resolve_exec(struct qr_task *, knot_pkt_t *);
+knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct kr_qflags *);
+struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags);
+int zi_zone_import(const zi_config_t);
+struct engine {
+ struct kr_context resolver;
+ char _stub[];
+};
+struct worker_ctx {
+ struct engine *engine;
+ char _stub[];
+};
+struct worker_ctx *the_worker;
+typedef struct {
+ uint8_t *params_position;
+ uint8_t *mandatory_position;
+ uint8_t *param_position;
+ int32_t last_key;
+} zs_svcb_t;
+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;
+typedef 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;
+ uint8_t *item_length2_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;
+ zs_svcb_t svcb;
+ uint8_t addr[16];
+ _Bool long_string;
+ _Bool comma_list;
+ 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 (*comment)(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];
+} zs_scanner_t;
+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..70afb40
--- /dev/null
+++ b/daemon/lua/kres-gen.sh
@@ -0,0 +1,353 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# Run with "ninja kres-gen" to re-generate $1
+set -o pipefail -o errexit -o nounset
+
+cd "$(dirname ${0})"
+OUTNAME="$1"
+CDEFS="../../scripts/gen-cdefs.sh"
+LIBKRES="${MESON_BUILD_ROOT}/lib/libkres.so"
+KRESD="${MESON_BUILD_ROOT}/daemon/kresd"
+if [ ! -e "$LIBKRES" ]; then
+ # We probably use static libkres.
+ LIBKRES="$KRESD"
+fi
+
+for REQFILE in "$CDEFS" "$LIBKRES" "$KRESD"
+do
+ test '!' -s "$REQFILE" -a -r "$REQFILE" \
+ && echo "Required file $REQFILE cannot be read, did you build binaries and shared libraries?" \
+ && exit 1
+done
+
+# Write to "$OUTNAME" instead of stdout
+mv "$OUTNAME"{,.bak} ||:
+exec 5<&1- # move stdout into FD 5
+exec 1<>"$OUTNAME" # replace stdout with file
+
+restore() {
+ exec 1>&- # close stdout redirected into "$OUTNAME"
+ exec 1<&5- # restore original stdout
+ mv -v "$OUTNAME"{,.fail} ||:
+ mv -v "$OUTNAME"{.bak,} ||:
+ (>&2 echo "Failed to re-generate $OUTNAME! Missing debugsymbols? Missing shared library?")
+}
+trap restore ERR INT TERM
+
+### 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)
+# - run ninja kres-gen
+# - the knot-dns libraries are found via pkg-config
+# - you also need gdb on $PATH
+
+printf -- "-- SPDX-License-Identifier: GPL-3.0-or-later\n\n"
+printf -- "local ffi = require('ffi')\n"
+printf -- "--[[ This file is generated by ./kres-gen.sh ]] ffi.cdef[[\n"
+
+# Some system dependencies. TODO: this generated part isn't perfectly portable.
+${CDEFS} ${LIBKRES} types <<-EOF
+ typedef time_t
+ __time_t
+ __suseconds_t
+ struct timeval
+EOF
+
+## Various types (mainly), from libknot and libkres
+
+printf "
+typedef struct knot_dump_style knot_dump_style_t;
+extern const knot_dump_style_t KR_DUMP_STYLE_DEFAULT;
+struct kr_cdb_api {};
+struct lru {};
+"
+
+${CDEFS} ${LIBKRES} types <<-EOF
+ knot_section_t
+ knot_rrinfo_t
+ knot_dname_t
+ knot_rdata_t
+ knot_rdataset_t
+EOF
+
+# 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_request *, const char *);
+typedef void (*trace_callback_f)(struct kr_request *);
+typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen);
+typedef bool (*addr_info_f)(struct sockaddr*);
+typedef void (*zi_callback)(int state, void *param);
+"
+
+genResType() {
+ echo "$1" | ${CDEFS} ${LIBKRES} types
+}
+
+# No simple way to fixup this rename in ./kres.lua AFAIK.
+genResType "knot_rrset_t" | sed 's/\<owner\>/_owner/; s/\<ttl\>/_ttl/'
+
+printf "
+struct kr_module;
+typedef char *(kr_prop_cb)(void *, struct kr_module *, const char *);
+typedef unsigned char knot_dname_storage_t[255];
+"
+
+${CDEFS} ${LIBKRES} types <<-EOF
+ #knot_pkt_t contains indirect recursion
+ typedef knot_pkt_t
+ knot_edns_options_t
+ knot_pktsection_t
+ knot_compr_t
+ struct knot_pkt
+ #trie_t inside is private to libknot
+ typedef trie_t
+ # libkres
+ struct kr_qflags
+ ranked_rr_array_entry_t
+ ranked_rr_array_t
+ kr_http_header_array_entry_t
+ kr_http_header_array_t
+ kr_sockaddr_array_t
+ struct kr_zonecut
+ kr_qarray_t
+ struct kr_rplan
+ struct kr_request_qsource_flags
+ struct kr_extended_error
+ struct kr_request
+ enum kr_rank
+ typedef kr_cdb_pt
+ struct kr_cdb_stats
+ typedef uv_timer_t
+ struct kr_cache
+ # lib/layer.h
+ kr_layer_t
+ kr_layer_api_t
+ # lib/module.h
+ struct kr_prop
+ struct kr_module
+ struct kr_server_selection
+ kr_log_level_t
+ enum kr_log_group
+EOF
+
+# static variables; these lines might not be simple to generate
+printf "
+kr_layer_t kr_layer_t_static;
+_Bool kr_dbg_assertion_abort;
+int kr_dbg_assertion_fork;
+"
+
+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_query"
+
+genResType "struct kr_context" | sed '/module_array_t/,$ d'
+printf "\tchar _stub[];\n};\n"
+
+
+echo "struct kr_transport" | ${CDEFS} ${KRESD} types | sed '/union /,$ d'
+printf "\t/* beware: hidden stub, to avoid hardcoding sockaddr lengths */\n};\n"
+
+## libknot API
+${CDEFS} 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_free
+ 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
+${CDEFS} ${LIBKRES} functions <<-EOF
+# Resolution request
+ kr_request_ensure_edns
+ kr_request_ensure_answer
+ kr_request_set_extended_error
+ kr_resolve_plan
+ kr_resolve_pool
+# Resolution plan
+ kr_rplan_push
+ kr_rplan_pop
+ kr_rplan_resolved
+ kr_rplan_last
+# Forwarding
+ kr_forward_add_target
+# Utils
+ kr_log_is_debug_fun
+ kr_log_req1
+ kr_log_q1
+ kr_log_grp2name
+ kr_log_fmt
+ kr_make_query
+ kr_pkt_make_auth_header
+ kr_pkt_put
+ kr_pkt_recycle
+ kr_pkt_clear_payload
+ kr_pkt_has_wire
+ kr_pkt_has_dnssec
+ kr_pkt_qclass
+ kr_pkt_qtype
+ kr_pkt_text
+ 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_cmp
+ kr_sockaddr_len
+ kr_inaddr_port
+ kr_straddr_family
+ kr_straddr_subnet
+ kr_bitcmp
+ kr_family_len
+ kr_straddr_socket
+ kr_straddr_split
+ kr_rank_test
+ kr_ranked_rrarray_add
+ kr_ranked_rrarray_finalize
+ kr_qflags_set
+ kr_qflags_clear
+ kr_zonecut_add
+ kr_zonecut_is_empty
+ kr_zonecut_set
+ kr_now
+ kr_strptime_diff
+ kr_file_mtime
+ kr_fssize
+ kr_dirent_name
+ 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_commit
+ # FIXME: perhaps rename this exported symbol
+ packet_ttl
+EOF
+
+
+## kresd itself: worker stuff
+
+${CDEFS} ${KRESD} types <<-EOF
+ endpoint_flags_t
+ # struct args is a bit complex
+ addr_array_t
+ flagged_fd_t
+ flagged_fd_array_t
+ config_array_t
+ struct args
+ zi_config_t
+EOF
+echo "struct args *the_args;"
+
+echo "struct endpoint" | ${CDEFS} ${KRESD} types | sed 's/uv_handle_t \*/void */'
+echo "struct request_ctx" | ${CDEFS} ${KRESD} types | sed '/struct {/,$ d'
+printf "\t/* beware: hidden stub, to avoid hardcoding sockaddr lengths */\n};\n"
+
+echo "struct qr_task" | ${CDEFS} ${KRESD} types | sed '/pktbuf/,$ d'
+printf "\t/* beware: hidden stub, to avoid qr_tasklist_t */\n};\n"
+
+
+${CDEFS} ${KRESD} functions <<-EOF
+ worker_resolve_exec
+ worker_resolve_mk_pkt
+ worker_resolve_start
+ zi_zone_import
+EOF
+
+echo "struct engine" | ${CDEFS} ${KRESD} types | sed '/struct network/,$ d'
+printf "\tchar _stub[];\n};\n"
+
+echo "struct worker_ctx" | ${CDEFS} ${KRESD} types | sed '/uv_loop_t/,$ d'
+printf "\tchar _stub[];\n};\n"
+
+echo "struct worker_ctx *the_worker;"
+
+
+## libzscanner API for ./zonefile.lua
+if pkg-config libknot --atleast-version=3.1; then
+ echo "zs_svcb_t" | ${CDEFS} libzscanner types
+fi
+${CDEFS} libzscanner types <<-EOF
+ zs_win_t
+ zs_apl_t
+ zs_loc_t
+ zs_state_t
+ #zs_scanner_t contains recursion
+ typedef zs_scanner_t
+ zs_scanner_t
+EOF
+${CDEFS} libzscanner functions <<-EOF
+ zs_deinit
+ zs_init
+ zs_parse_record
+ zs_set_input_file
+ zs_set_input_string
+ zs_strerror
+EOF
+
+printf "]]\n"
+
+rm "$OUTNAME".bak ||:
+(>&2 echo "Successfully re-generated ${PWD}/$OUTNAME")
+
+exit 0
diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua
new file mode 100644
index 0000000..4dc2b40
--- /dev/null
+++ b/daemon/lua/kres.lua
@@ -0,0 +1,1143 @@
+-- LuaJIT ffi bindings for libkres, a DNS resolver library.
+-- SPDX-License-Identifier: GPL-3.0-or-later
+--
+-- @note Since it's statically compiled, it expects to find the symbols in the C namespace.
+
+local kres -- the module
+
+local kluautil = require('kluautil')
+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
+ */
+
+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,
+ ZONEMD = 63,
+ SVCB = 64,
+ HTTPS = 65,
+
+ 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
+}
+local const_extended_error = {
+ NONE = -1,
+ OTHER = 0,
+ DNSKEY_ALG = 1,
+ DS_DIGEST = 2,
+ STALE = 3,
+ FORGED = 4,
+ INDETERMINATE = 5,
+ BOGUS = 6,
+ SIG_EXPIRED = 7,
+ SIG_NOTYET = 8,
+ DNSKEY_MISS = 9,
+ RRSIG_MISS = 10,
+ DNSKEY_BIT = 11,
+ NSEC_MISS = 12,
+ CACHED_ERR = 13,
+ NOT_READY = 14,
+ BLOCKED = 15,
+ CENSORED = 16,
+ FILTERED = 17,
+ PROHIBITED = 18,
+ STALE_NXD = 19,
+ NOTAUTH = 20,
+ NOTSUP = 21,
+ NREACH_AUTH = 22,
+ NETWORK = 23,
+ INV_DATA = 24,
+}
+
+-- 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)
+local const_extended_error_str = itable(const_extended_error)
+
+-- 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]') -- INET6_ADDRSTRLEN + #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, alignment)
+ -- {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, alignment or 1, 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
+
+-- Parse RDATA, from presentation to wire-format.
+-- in: a table of strings, each a line describing RRTYPE+RDATA
+-- out: a table of RDATA strings in wire-format
+local function parse_rdata(strs, nothing)
+ local zonefile = require('zonefile')
+ if type(strs) ~= 'table' or nothing ~= nil then -- accidents like forgetting braces
+ error('a table of string(s) is expected', 2)
+ end
+ local res = {}
+ for _, line in ipairs(strs) do
+ if type(line) ~= 'string' then
+ error('table must contain strings', 2)
+ end
+ local rrs = zonefile.string('. ' .. line)
+ if #rrs == 0 then error('failed to parse line: ' .. line, 2) end
+ for _, rr in ipairs(rrs) do
+ table.insert(res, rr.rdata)
+ end
+ end
+ return res
+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, C.KR_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 C.KR_DUMP_STYLE_DEFAULT)
+ local result = nil
+ if ret >= 0 then
+ result = ffi.string(dump[0], ret)
+ end
+ C.free(dump[0])
+ return result
+ end,
+ txt_fields = function(rr, i)
+ assert(ffi.istype(knot_rrset_t, rr))
+ assert(i >= 0 and i < rr:rdcount())
+ local bufsize = 1024
+ local dump = ffi.new('char *', C.malloc(bufsize))
+ ffi.gc(dump, C.free)
+
+ local ret = knot.knot_rrset_txt_dump_data(rr, i, dump, 1024,
+ C.KR_DUMP_STYLE_DEFAULT)
+ if ret >= 0 then
+ local out = {}
+ out.owner = dname2str(rr:owner())
+ out.ttl = rr:ttl()
+ out.class = kres.tostring.class[rr:class()]
+ out.type = kres.tostring.type[rr.type]
+ out.rdata = ffi.string(dump, ret)
+ return out
+ else
+ panic('knot_rrset_txt_dump_data failure ' .. tostring(ret))
+ end
+ 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
+
+-- 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,
+ -- "do" is a reserved word in Lua; only getter
+ dobit = function(pkt, val)
+ assert(val == nil, 'dobit is getter only')
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return C.kr_pkt_has_dnssec(pkt)
+ end,
+ -- Question
+ qname = function(pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ -- inlined knot_pkt_qname(), basically but not lower-cased
+ 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,
+ -- Checks whether the packet has a wire, i.e. the .size is not
+ -- equal to KR_PKT_SIZE_NOWIRE
+ has_wire = function (pkt)
+ assert(ffi.istype(knot_pkt_t, pkt))
+ return C.kr_pkt_has_wire(pkt)
+ 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 ffi.string(ffi.gc(C.kr_pkt_text(pkt), C.free))
+ 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,
+ },
+})
+
+-- helper for trace_chain_callbacks
+-- ignores return values from successful calls but logs tracebacks for throws
+local function void_xpcall_log_tb(func, req, msg)
+ local ok, err = xpcall(func, debug.traceback, req, msg)
+ if not ok then
+ log_error(ffi.C.LOG_GRP_SYSTEM, 'callback %s req %s msg %s stack traceback:\n%s', func, req, msg, err)
+ end
+end
+
+local function void_xpcall_finish_tb(func, req)
+ local ok, err = xpcall(func, debug.traceback, req)
+ if not ok then
+ log_error(ffi.C.LOG_GRP_SYSTEM, 'callback %s req %s stack traceback:\n%s', func, req, err)
+ end
+end
+
+
+-- Metatype for request
+local kr_request_t = ffi.typeof('struct kr_request')
+ffi.metatype( kr_request_t, {
+ __index = {
+ -- makes sense only when request is finished
+ all_from_cache = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ local rplan = ffi.C.kr_resolve_plan(req)
+ if tonumber(rplan.pending.len) > 0 then
+ -- an unresolved query,
+ -- i.e. something is missing from the cache
+ return false
+ end
+ for idx=0, tonumber(rplan.resolved.len) - 1 do
+ if not rplan.resolved.at[idx].flags.CACHED then
+ return false
+ end
+ end
+ return true
+ end,
+ current = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ if req.current_query == nil then return nil end
+ return req.current_query
+ end,
+ -- returns the initial query that started the request
+ initial = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ local rplan = C.kr_resolve_plan(req)
+ if rplan.initial == nil then return nil end
+ return rplan.initial
+ 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,
+ selected_tostring = function(req)
+ assert(ffi.istype(kr_request_t, req))
+ local buf = {}
+ if #req.answ_selected ~= 0 then
+ table.insert(buf, ';; selected from ANSWER sections:\n')
+ table.insert(buf, tostring(req.answ_selected))
+ end
+ if #req.auth_selected ~= 0 then
+ table.insert(buf, ';; selected from AUTHORITY sections:\n')
+ table.insert(buf, tostring(req.auth_selected))
+ end
+ if #req.add_selected ~= 0 then
+ table.insert(buf, ';; selected from ADDITIONAL sections:\n')
+ table.insert(buf, tostring(req.add_selected))
+ end
+ return table.concat(buf, '')
+ end,
+ set_extended_error = function(req, code, msg)
+ assert(ffi.istype(kr_request_t, req))
+ msg = kluautil.kr_string2c(msg, req.pool)
+ ffi.C.kr_request_set_extended_error(req, code, msg)
+ end,
+
+ -- chain new callbacks after the old ones
+ -- creates new wrapper functions as necessary
+ -- note: callbacks are FFI cdata pointers so tests must
+ -- use explicit "cb == nil", just "if cb" does not work
+ --
+ trace_chain_callbacks = function (req, new_log, new_finish)
+ local log_wrapper
+ if req.trace_log == nil then
+ req.trace_log = new_log
+ else
+ local old_log = req.trace_log
+ log_wrapper = ffi.cast('trace_log_f',
+ function(cbreq, msg)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ void_xpcall_log_tb(old_log, cbreq, msg)
+ void_xpcall_log_tb(new_log, cbreq, msg)
+ end)
+ req.trace_log = log_wrapper
+ end
+ local old_finish = req.trace_finish
+ if not (log_wrapper ~= nil or old_finish ~= nil) then
+ req.trace_finish = new_finish
+ else
+ local fin_wrapper
+ fin_wrapper = ffi.cast('trace_callback_f',
+ function(cbreq)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ if old_finish ~= nil then
+ void_xpcall_finish_tb(old_finish, cbreq)
+ end
+ if new_finish ~= nil then
+ void_xpcall_finish_tb(new_finish, cbreq)
+ end
+ -- beware: finish callbacks can call log callback
+ if log_wrapper ~= nil then
+ log_wrapper:free()
+ end
+ fin_wrapper:free()
+ end)
+ req.trace_finish = fin_wrapper
+ end
+ 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,
+ -- Ensure that answer has EDNS if needed; can't fail.
+ ensure_edns = function (req)
+ assert(ffi.istype(kr_request_t, req))
+ return C.kr_request_ensure_edns(req)
+ end,
+ -- Ensure that answer exists and return it; can't fail.
+ ensure_answer = function (req)
+ assert(ffi.istype(kr_request_t, req))
+ return C.kr_request_ensure_answer(req)
+ 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 a single ranked record array entry (one RRset)
+local function rank_tostring(rank)
+ local names = {}
+ for name, value in pairs(const_rank) do
+ if ffi.C.kr_rank_test(rank, value) then
+ table.insert(names, string.lower(name))
+ end
+ end
+ table.sort(names) -- pairs() above doesn't give a stable ordering
+ return string.format('0%.2o (%s)', rank, table.concat(names, ' '))
+end
+
+local ranked_rr_array_entry_t = ffi.typeof('ranked_rr_array_entry_t')
+ffi.metatype(ranked_rr_array_entry_t, {
+ __tostring = function(self)
+ return string.format('; ranked rrset to_wire %s, rank %s, cached %s, qry_uid %s, revalidations %s\n%s',
+ self.to_wire, rank_tostring(self.rank), self.cached, self.qry_uid,
+ self.revalidation_cnt, string.format('%s', self.rr))
+ end
+})
+
+-- Metatype for ranked record array (array of RRsets)
+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,
+ },
+ __tostring = function(self)
+ local buf = {}
+ for _, rrset in ipairs(self) do
+ table.insert(buf, tostring(rrset))
+ end
+ return table.concat(buf, '')
+ 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, true)
+ if ret ~= 0 then return nil, knot_error_t(ret) end
+ return true
+ end,
+ commit = function (self)
+ assert(ffi.istype(kr_cache_t, self))
+ local ret = C.kr_cache_commit(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,
+ extended_error = const_extended_error,
+
+ -- 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,
+ extended_eror = const_extended_error_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)
+ value_type = value_type or ffi.typeof('uint64_t')
+ local ct = ffi.typeof(typed_lru_t, value_type)
+ return ffi.metatype(ct, lru_metatype)(max_size, ffi.alignof(value_type))
+ 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,
+ parse_rdata = parse_rdata,
+
+ 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.C.the_worker.engine.resolver end,
+
+ knot_pkt_rr = knot_pkt_rr,
+}
+
+return kres
diff --git a/daemon/lua/krprint.lua b/daemon/lua/krprint.lua
new file mode 100644
index 0000000..dd25a9b
--- /dev/null
+++ b/daemon/lua/krprint.lua
@@ -0,0 +1,340 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local base_class = {
+ cur_indent = 0,
+}
+
+-- shared constructor: use as serializer_class:new()
+function base_class.new(class, on_unrepresentable)
+ on_unrepresentable = on_unrepresentable or 'comment'
+ if on_unrepresentable ~= 'comment'
+ and on_unrepresentable ~= 'error' then
+ error('unsupported val2expr on_unrepresentable option '
+ .. tostring(on_unrepresentable))
+ end
+ local inst = {}
+ inst.on_unrepresentable = on_unrepresentable
+ inst.done = {}
+ inst.tab_key_path = {}
+ setmetatable(inst, class.__inst_mt)
+ return inst
+end
+
+-- format comment with leading/ending whitespace if needed
+function base_class.format_note(_, note, ws_prefix, ws_suffix)
+ if note == nil then
+ return ''
+ else
+ return string.format('%s--[[ %s ]]%s',
+ ws_prefix or '', note, ws_suffix or '')
+ end
+end
+
+function base_class.indent_head(self)
+ return string.rep(' ', self.cur_indent)
+end
+
+function base_class.indent_inc(self)
+ self.cur_indent = self.cur_indent + self.indent_step
+end
+
+function base_class.indent_dec(self)
+ self.cur_indent = self.cur_indent - self.indent_step
+end
+
+function base_class._fallback(self, val)
+ if self.on_unrepresentable == 'comment' then
+ return 'nil', string.format('missing %s', val)
+ elseif self.on_unrepresentable == 'error' then
+ local key_path_msg
+ if #self.tab_key_path > 0 then
+ local str_key_path = {}
+ for _, key in ipairs(self.tab_key_path) do
+ table.insert(str_key_path,
+ string.format('%s %s', type(key), self:string(tostring(key))))
+ end
+ local key_path = '[' .. table.concat(str_key_path, '][') .. ']'
+ key_path_msg = string.format(' (found at [%s])', key_path)
+ else
+ key_path_msg = ''
+ end
+ error(string.format('cannot serialize type %s%s', type(val), key_path_msg), 2)
+ end
+end
+
+function base_class.val2expr(self, val)
+ local val_type = type(val)
+ local val_repr = self[val_type]
+ if val_repr then
+ return val_repr(self, val)
+ else
+ return self:_fallback(val)
+ end
+end
+
+-- "nil" is a Lua keyword so assignment below is workaround to create
+-- function base_class.nil(self, val)
+base_class['nil'] = function(_, val)
+ assert(type(val) == 'nil')
+ return 'nil'
+end
+
+function base_class.number(_, val)
+ assert(type(val) == 'number')
+ if val == math.huge then
+ return 'math.huge'
+ elseif val == -math.huge then
+ return '-math.huge'
+ elseif tostring(val) == 'nan' then
+ return 'tonumber(\'nan\')'
+ else
+ return string.format("%.60f", val)
+ end
+end
+
+function base_class.char_is_printable(_, c)
+ -- ASCII (from space to ~) and not ' or \
+ return (c >= 0x20 and c < 0x7f)
+ and c ~= 0x27 and c ~= 0x5C
+end
+
+function base_class.string(self, val)
+ assert(type(val) == 'string')
+ local chars = {'\''}
+ for i = 1, #val do
+ local c = string.byte(val, i)
+ if self:char_is_printable(c) then
+ table.insert(chars, string.char(c))
+ else
+ table.insert(chars, string.format('\\%03d', c))
+ end
+ end
+ table.insert(chars, '\'')
+ return table.concat(chars)
+end
+
+function base_class.boolean(_, val)
+ assert(type(val) == 'boolean')
+ return tostring(val)
+end
+
+local function ordered_iter(unordered_tt)
+ local keys = {}
+ for k in pairs(unordered_tt) do
+ table.insert(keys, k)
+ end
+ table.sort(keys,
+ function (a, b)
+ if type(a) ~= type(b) then
+ return type(a) < type(b)
+ end
+ if type(a) == 'number' then
+ return a < b
+ else
+ return tostring(a) < tostring(b)
+ end
+ end)
+ local i = 0
+ return function()
+ i = i + 1
+ if keys[i] ~= nil then
+ return keys[i], unordered_tt[keys[i]]
+ end
+ end
+end
+
+function base_class.table(self, tab)
+ assert(type(tab) == 'table')
+ if self.done[tab] then
+ error('cyclic reference', 0)
+ end
+ self.done[tab] = true
+
+ local items = {'{'}
+ local previdx = 0
+ self:indent_inc()
+ for idx, val in ordered_iter(tab) do
+ local errors, valok, valexpr, valnote, idxok, idxexpr, idxnote
+ errors = {}
+ -- push current index onto key path stack to make it available to sub-printers
+ table.insert(self.tab_key_path, idx)
+
+ valok, valexpr, valnote = pcall(self.val2expr, self, val)
+ if not valok then
+ table.insert(errors, string.format('value: %s', valexpr))
+ end
+
+ local addidx
+ if previdx and type(idx) == 'number' and idx - 1 == previdx then
+ -- monotonic sequence, do not print key
+ previdx = idx
+ addidx = false
+ else
+ -- end of monotonic sequence
+ -- from now on print keys as well
+ previdx = nil
+ addidx = true
+ end
+
+ if addidx then
+ idxok, idxexpr, idxnote = pcall(self.val2expr, self, idx)
+ if not idxok or idxexpr == 'nil' then
+ table.insert(errors, string.format('key: not serializable', idxexpr))
+ end
+ end
+
+ local item = ''
+ if #errors == 0 then
+ -- finally serialize one [key=]?value expression
+ local indent = self:indent_head()
+ local note
+ if addidx then
+ note = self:format_note(idxnote, nil, self.key_val_sep)
+ item = string.format('%s%s[%s]%s=%s',
+ indent, note,
+ idxexpr, self.key_val_sep, self.key_val_sep)
+ indent = ''
+ end
+ note = self:format_note(valnote, nil, self.item_sep)
+ item = item .. string.format('%s%s%s,', indent, note, valexpr)
+ else
+ local errmsg = string.format('cannot print %s = %s (%s)',
+ self:string(tostring(idx)),
+ self:string(tostring(val)),
+ table.concat(errors, ', '))
+ if self.on_unrepresentable == 'error' then
+ error(errmsg, 0)
+ else
+ errmsg = string.format('--[[ missing %s ]]', errmsg)
+ item = errmsg
+ end
+ end
+ table.insert(items, item)
+ table.remove(self.tab_key_path) -- pop current index from key path stack
+ end -- one key+value
+ self:indent_dec()
+ table.insert(items, self:indent_head() .. '}')
+ return table.concat(items, self.item_sep), string.format('%s follows', tab)
+end
+
+-- machine readable variant, cannot represent all types and repeated references to a table
+local serializer_class = {
+ indent_step = 0,
+ item_sep = ' ',
+ key_val_sep = ' ',
+ __inst_mt = {}
+}
+-- inheritance form base class (for :new())
+setmetatable(serializer_class, { __index = base_class })
+-- class instances with following metatable inherit all class members
+serializer_class.__inst_mt.__index = serializer_class
+
+local function static_serializer(val, on_unrepresentable)
+ local inst = serializer_class:new(on_unrepresentable)
+ local expr, note = inst:val2expr(val)
+ return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr)
+ end
+
+-- human friendly variant, not stable and not intended for machine consumption
+local pprinter_class = {
+ indent_step = 4,
+ item_sep = '\n',
+ key_val_sep = ' ',
+ __inst_mt = {},
+}
+
+-- should be always empty because pretty-printer has fallback for all types
+function pprinter_class.format_note()
+ return ''
+end
+
+function pprinter_class._fallback(self, val)
+ if self.on_unrepresentable == 'error' then
+ base_class._fallback(self, val)
+ end
+ return tostring(val)
+end
+
+function pprinter_class.char_is_printable(_, c)
+ -- ASCII (from space to ~) + tab or newline
+ -- and not ' or \
+ return ((c >= 0x20 and c < 0x7f)
+ or c == 0x09 or c == 0x0A)
+ and c ~= 0x27 and c ~= 0x5C
+end
+
+-- "function" is a Lua keyword so assignment below is workaround to create
+-- function pprinter_class.function(self, f)
+pprinter_class['function'] = function(self, f)
+-- thanks to AnandA777 from StackOverflow! Function funcsign is adapted version of
+-- https://stackoverflow.com/questions/51095022/inspect-function-signature-in-lua-5-1
+ assert(type(f) == 'function', "bad argument #1 to 'funcsign' (function expected)")
+ local debuginfo = debug.getinfo(f)
+ local func_args = {}
+ local args_str
+ if debuginfo.what == 'C' then -- names N/A
+ args_str = '(?)'
+ goto add_name
+ end
+
+ pcall(function()
+ local oldhook
+ local delay = 2
+ local function hook()
+ delay = delay - 1
+ if delay == 0 then -- call this only for the introspected function
+ -- stack depth 2 is the introspected function
+ for i = 1, debuginfo.nparams do
+ local k = debug.getlocal(2, i)
+ table.insert(func_args, k)
+ end
+ if debuginfo.isvararg then
+ table.insert(func_args, "...")
+ end
+ debug.sethook(oldhook)
+ error('aborting the call to introspected function')
+ end
+ end
+ oldhook = debug.sethook(hook, "c") -- invoke hook() on function call
+ f(unpack({})) -- huh?
+ end)
+ args_str = "(" .. table.concat(func_args, ", ") .. ")"
+ ::add_name::
+ local name
+ if #self.tab_key_path > 0 then
+ name = string.format('function %s', self.tab_key_path[#self.tab_key_path])
+ else
+ name = 'function '
+ end
+ return string.format('%s%s: %s', name, args_str, string.sub(tostring(f), 11))
+end
+
+-- default tostring method is better suited for human-intended output
+function pprinter_class.number(_, number)
+ return tostring(number)
+end
+
+local function deserialize_lua(serial)
+ assert(type(serial) == 'string')
+ local deserial_func = loadstring('return ' .. serial)
+ if type(deserial_func) ~= 'function' then
+ panic('input is not a valid Lua expression')
+ end
+ return deserial_func()
+end
+
+setmetatable(pprinter_class, { __index = base_class })
+pprinter_class.__inst_mt.__index = pprinter_class
+
+local function static_pprint(val, on_unrepresentable)
+ local inst = pprinter_class:new(on_unrepresentable)
+ local expr, note = inst:val2expr(val)
+ return string.format('%s%s', inst:format_note(note, nil, inst.item_sep), expr)
+end
+
+local M = {
+ serialize_lua = static_serializer,
+ deserialize_lua = deserialize_lua,
+ pprint = static_pprint
+}
+
+return M
diff --git a/daemon/lua/krprint.test.lua b/daemon/lua/krprint.test.lua
new file mode 100644
index 0000000..9218052
--- /dev/null
+++ b/daemon/lua/krprint.test.lua
@@ -0,0 +1,292 @@
+local serialize_lua = require('krprint').serialize_lua
+local deserialize_lua = require('krprint').deserialize_lua
+
+local function gen_string(maxlen)
+ maxlen = maxlen or 100
+ local len = math.random(0, maxlen)
+ local buf = {}
+ for _=1,len do
+ table.insert(buf, string.char(math.random(0, 255)))
+ end
+ return table.concat(buf)
+end
+
+local function test_de_serialization(orig_val, desc)
+ local serial = serialize_lua(orig_val)
+ ok(type(serial) == 'string' and #serial > 0,
+ 'serialization returns non-empty string: ' .. desc)
+ local deserial_val = deserialize_lua(serial)
+ same(type(orig_val), type(deserial_val),
+ 'deserialized value has the same type: ' .. desc)
+ if type(orig_val) == 'number' then
+ -- nan cannot be compared using == operator
+ if tostring(orig_val) == 'nan' and tostring(deserial_val) == 'nan' then
+ pass('nan value serialized and deserialized')
+ elseif orig_val ~= math.huge and orig_val ~= -math.huge then
+ -- tolerance measured experimentally on x86_64 LuaJIT 2.1.0-beta3
+ local tolerance = 1e-14
+ ok(math.abs(orig_val - deserial_val) <= tolerance,
+ 'deserialized number is within tolerance ' .. tolerance)
+ else
+ same(orig_val, deserial_val, 'deserialization returns the same infinity:' .. desc)
+ end
+ else
+ same(orig_val, deserial_val,
+ 'deserialization returns the same value: ' .. desc)
+ end
+end
+
+local function test_de_serialization_autodesc(orig_val)
+ test_de_serialization(orig_val, tostring(orig_val))
+end
+
+local function test_bool()
+ test_de_serialization_autodesc(true)
+ same('true', table_print(true), 'table_print handles true')
+ test_de_serialization_autodesc(false)
+ same('false', table_print(false), 'table_print handles false')
+end
+
+local function test_nil()
+ test_de_serialization_autodesc(nil)
+ same('nil', table_print(nil), 'table_print handles nil')
+end
+
+local function gen_number_int()
+ local number
+ -- make "small" numbers more likely so they actually happen
+ if math.random() < 0.5 then
+ number = math.random(-2^32, 2^32)
+ else
+ number = math.random(-2^48, 2^48)
+ end
+ return number
+end
+
+local function gen_number_float()
+ return math.random()
+end
+
+local function test_number()
+ test_de_serialization_autodesc(0)
+ same('0', table_print(0), 'table_print handles 0')
+ test_de_serialization_autodesc(-math.huge)
+ same('-inf', table_print(-math.huge), 'table_print handles -infinity')
+ test_de_serialization_autodesc(math.huge)
+ same('inf', table_print(math.huge), 'table_print handles +infinity')
+ test_de_serialization_autodesc(tonumber('nan'))
+ same('nan', table_print(tonumber('nan')), 'table_print handles nan')
+ for _=1,20 do -- integers
+ test_de_serialization_autodesc(gen_number_int())
+ -- bigger numbers might end up with non-exact representation
+ local smallnumber = math.random(-2^32, 2^32)
+ same(tostring(smallnumber), table_print(smallnumber),
+ 'table_print handles small numbers')
+ end
+ for _=1,20 do -- floats
+ local float = math.random()
+ same(tostring(float), table_print(float),
+ 'table_print handles floats')
+ test_de_serialization_autodesc(gen_number_float())
+ end
+end
+
+local function test_string()
+ test_de_serialization('', 'empty string')
+ for _=1,20 do
+ local str = gen_string(1024*10)
+ test_de_serialization(str, 'random string length ' .. #str)
+ end
+end
+
+local function gen_number()
+ -- pure random would not produce special cases often enough
+ local generators = {
+ function() return 0 end,
+ function() return -math.huge end,
+ function() return math.huge end,
+ gen_number_int,
+ gen_number_float,
+ }
+ return generators[math.random(1, #generators)]()
+end
+
+local function gen_boolean()
+ local options = {true, false}
+ return options[math.random(1, #options)]
+end
+
+local function gen_table_atomic()
+ -- nil keys or values are not allowed
+ -- nested tables are handled elsewhere
+ local supported_types = {
+ gen_number,
+ gen_string,
+ gen_boolean,
+ }
+ val = supported_types[math.random(1, #supported_types)]()
+ return val
+end
+
+local function gen_test_tables_supported(level)
+ level = level or 1
+ local max_level = 5
+ local max_items_per_table = 20
+ local t = {}
+ for _=1, math.random(0, max_items_per_table) do
+ local val_as_table = (level <= max_level) and math.random() < 0.1
+ local key, val
+ -- tapered.same method cannot compare keys with type table
+ key = gen_table_atomic()
+ if val_as_table then
+ val = gen_test_tables_supported(level + 1)
+ else
+ val = gen_table_atomic()
+ end
+ t[key] = val
+ end
+ return t
+end
+
+local marker = 'this string must be present somewhere in output'
+local function gen_marker()
+ return marker
+end
+
+local kluautil = require('kluautil')
+local function random_modify_table(t, always, generator)
+ assert(generator)
+ local tab_len = kluautil.kr_table_len(t)
+ local modified = false
+ -- modify some values
+ for key, val in pairs(t) do
+ if math.random(1, tab_len) == 1 then
+ if type(val) == 'table' then
+ modified = modified or random_modify_table(val, false, generator)
+ else
+ t[key] = generator()
+ modified = true
+ end
+ end
+ end
+ if always and not modified then
+ -- fallback, add an unsupported key
+ t[generator()] = true
+ modified = true
+ end
+ return modified
+end
+
+local function test_table_supported()
+ for i=1,10 do
+ local t = gen_test_tables_supported()
+ test_de_serialization(t, 'random table no. ' .. i)
+ assert(random_modify_table(t, true, gen_marker))
+ local str = table_print(t)
+ ok(string.find(str, marker, 1, true),
+ 'table_print works on complex serializable tables')
+ end
+end
+
+local ffi = require('ffi')
+local const_func = tostring
+local const_thread = coroutine.create(tostring)
+local const_userdata = ffi.C
+local const_cdata = ffi.new('int')
+
+local function gen_unsupported_atomic()
+ -- nested tables are handled elsewhere
+ local unsupported_types = {
+ const_func,
+ const_thread,
+ const_userdata,
+ const_cdata
+ }
+ val = unsupported_types[math.random(1, #unsupported_types)]
+ return val
+end
+
+local function test_unsupported(val, desc)
+ desc = desc or string.format('unsupported %s', type(val))
+ return function()
+ boom(serialize_lua, { val, 'error' }, string.format(
+ 'attempt to serialize %s in error mode '
+ .. 'causes error', desc))
+ local output = serialize_lua(val, 'comment')
+ same('string', type(output),
+ string.format('attempt to serialize %s in '
+ .. 'comment mode returned a string',
+ desc))
+ ok(string.find(output, '--', 1, true),
+ 'returned string contains a comment')
+ output = table_print(val)
+ same('string', type(output),
+ string.format('table_print can stringify %s', desc))
+ if type(val) ~= 'table' then
+ ok(string.find(output, type(val), 1, true),
+ 'exotic type is mentioned in table_print output')
+ end
+ end
+end
+
+local function gen_test_tables_unsupported()
+ local t = gen_test_tables_supported()
+ random_modify_table(t, true, gen_unsupported_atomic)
+ return t
+end
+
+local function test_unsupported_table()
+ for i=1,10 do
+ local t = gen_test_tables_unsupported()
+ test_unsupported(t, 'random unsupported table no. ' .. i)()
+ assert(random_modify_table(t, true, gen_marker))
+ local str = table_print(t)
+ ok(string.find(str, marker, 1, true),
+ 'table_print works on complex unserializable tables')
+ end
+end
+
+local function func_2vararg_5ret(arg1, arg2, ...)
+ return select('#', ...), nil, arg1 + arg2, false, nil
+end
+local function func_ret_nil() return nil end
+local function func_ret_nothing() return end
+
+local function test_pprint_func()
+ local t = { [false] = func_2vararg_5ret }
+ local output = table_print(t)
+ ok(string.find(output, 'function false(arg1, arg2, ...)', 1, true),
+ 'function parameters are pretty printed')
+end
+
+local function test_pprint_func_ret()
+ local output = table_print(func_2vararg_5ret(1, 2, 'bla'))
+ local exp = [[
+1 -- result # 1
+nil -- result # 2
+3 -- result # 3
+false -- result # 4
+nil -- result # 5]]
+ same(output, exp, 'multiple return values are pretty printed')
+
+ output = table_print(func_ret_nil())
+ same(output, 'nil', 'single return value does not have extra comments')
+
+ output = table_print(func_ret_nothing())
+ same(output, nil, 'no return values to be printed cause nil output')
+end
+
+return {
+ test_bool,
+ test_nil,
+ test_number,
+ test_string,
+ test_table_supported,
+ test_unsupported(const_func),
+ test_unsupported(const_thread),
+ test_unsupported(const_userdata),
+ test_unsupported(const_cdata),
+ test_unsupported_table,
+ test_pprint_func,
+ test_pprint_func_ret,
+}
diff --git a/daemon/lua/log.test.lua b/daemon/lua/log.test.lua
new file mode 100644
index 0000000..ec5abd2
--- /dev/null
+++ b/daemon/lua/log.test.lua
@@ -0,0 +1,42 @@
+local function test_log_level()
+ same(log_level(), 'notice', 'default level is notice')
+ same(verbose(), false, 'verbose is not set by default')
+ same(log_level('crit'), 'crit', '"crit" level can be set')
+ same(log_level('err'), 'err', '"err" level can be set')
+ same(log_level('warning'), 'warning', '"warning" level can be set')
+ same(log_level('notice'), 'notice', '"notice" level can be set')
+ same(log_level('info'), 'info', '"info" level can be set')
+ same(log_level('debug'), 'debug', '"debug" level can be set')
+ same(verbose(), true, 'verbose is active when debug level is set')
+ same(verbose(false), false, 'verbose can be used to turn off debug level')
+ same(log_level(), 'notice', 'verbose returns log level to notice')
+ boom(log_level, { 'xxx' }, "unknown level can't be used")
+ boom(log_level, { 7 }, "numbered levels aren't supported")
+ boom(log_level, { 1, 2 }, "level doesn't take multiple arguments")
+end
+
+local function test_log_target()
+ same(log_target(), 'stderr', 'default target is stderr')
+ same(log_target('stdout'), 'stdout', 'stdout target can be set')
+ same(log_target('syslog'), 'syslog', 'syslog target can be set')
+ same(log_target('stderr'), 'stderr', 'stderr target can be set')
+ boom(log_level, { 'xxx' }, "unknown target can't be used")
+ boom(log_level, { 'stderr', 'syslog' }, "target doesn't take multiple arguments")
+end
+
+local function test_log_groups()
+ same(log_groups(), {}, 'no groups are logged by default')
+ same(log_groups({'system'}), {'system'}, 'configure "system" group')
+ same(log_groups({'devel'}), {'devel'}, 'another call overrides previously set groups')
+ same(log_groups({'devel', 'system'}), {'system', 'devel'}, 'configure multiple groups')
+ same(log_groups({}), {}, 'clear groups with empty table')
+ same(log_groups({'nonexistent'}), {}, "nonexistent group is ignored")
+ boom(log_groups, { 'string' }, "group argument can't be string")
+ boom(log_groups, { 1, 2 }, "group doesn't take multiple arguments")
+end
+
+return {
+ test_log_level,
+ test_log_target,
+ test_log_groups,
+}
diff --git a/daemon/lua/map.test.integr/deckard.yaml b/daemon/lua/map.test.integr/deckard.yaml
new file mode 100644
index 0000000..2fe920d
--- /dev/null
+++ b/daemon/lua/map.test.integr/deckard.yaml
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+programs:
+- name: kresd3
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - daemon/lua/map.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ - tests/config/tapered/src/tapered.lua
+ configs:
+ - config
+ - hints
+ - tapered.lua
+- name: kresd2
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - daemon/lua/map.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ - tests/config/tapered/src/tapered.lua
+ configs:
+ - config
+ - hints
+ - tapered.lua
+- name: kresd1
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - daemon/lua/map.test.integr/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ - tests/config/tapered/src/tapered.lua
+ configs:
+ - config
+ - hints
+ - tapered.lua
diff --git a/daemon/lua/map.test.integr/kresd_config.j2 b/daemon/lua/map.test.integr/kresd_config.j2
new file mode 100644
index 0000000..ae403c7
--- /dev/null
+++ b/daemon/lua/map.test.integr/kresd_config.j2
@@ -0,0 +1,193 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+local ffi = require('ffi')
+log_info(ffi.C.LOG_GRP_TESTS, 'my PID = %d', worker.pid)
+
+trust_anchors.remove('.')
+
+cache.size = 2*MB
+
+net = { '{{SELF_ADDR}}' }
+
+{% if QMIN == "false" %}
+option('NO_MINIMIZE', true)
+{% else %}
+option('NO_MINIMIZE', false)
+{% endif %}
+
+-- Self-checks on globals
+assert(help() ~= nil)
+assert(worker.id ~= nil)
+-- Self-checks on facilities
+assert(cache.count() == 0)
+assert(cache.stats() ~= nil)
+assert(cache.backends() ~= nil)
+assert(worker.stats() ~= nil)
+assert(net.interfaces() ~= nil)
+-- Self-checks on loaded stuff
+assert(#modules.list() > 0)
+-- Self-check timers
+ev = event.recurrent(1 * sec, function (ev) return 1 end)
+event.cancel(ev)
+
+local kluautil = require('kluautil')
+local tap = require('tapered')
+local checks_total = 16
+local n_instances = 3 -- must match deckard.yaml
+
+worker.control_path = worker.cwd .. '/../kresd3/control/'
+net.listen(worker.control_path .. worker.pid, nil, {kind = 'control'})
+assert(#net.list() >= 3) -- UDP, TCP, control
+
+-- debug, kept for future use
+--log_level("debug")
+log_debug(ffi.C.LOG_GRP_TESTS, '%s', worker.control_path)
+log_debug(ffi.C.LOG_GRP_TESTS, '%s', table_print(net.list()))
+
+function wait_for_sockets()
+ log_info(ffi.C.LOG_GRP_TESTS, 'waiting for control sockets')
+ local timeout = 5000 -- ms
+ local start_time = tonumber(ffi.C.kr_now())
+ local now
+ while true do
+ now = tonumber(ffi.C.kr_now())
+ if now > start_time + timeout then
+ log_info(ffi.C.LOG_GRP_TESTS, 'timeout while waiting for control sockets to appear')
+ os.exit(3)
+ end
+ local pids = kluautil.list_dir(worker.control_path)
+ if #pids == n_instances then
+ -- debug, kept for future use
+ log_debug(ffi.C.LOG_GRP_TESTS, 'got control sockets:')
+ log_debug(ffi.C.LOG_GRP_TESTS, table_print(pids))
+ break
+ else
+ worker.sleep(0.1)
+ end
+ end
+ log_info(ffi.C.LOG_GRP_TESTS, 'PIDs are visible now (waiting took %d ms)', now - start_time)
+end
+
+-- expression should throw Lua error:
+-- wrap it in a function which runs the expression on leader and follower
+-- separately so we can guarantee both cases are covered
+function boom_follower_and_leader(boom_expr, desc)
+ local variants = {leader = '~=', follower = '=='}
+ for name, operator in pairs(variants) do
+ -- beware, newline is not allowed in expr
+ local full_expr = string.format(
+ 'if (worker.pid %s %s) then return true '
+ .. 'else return %s end',
+ operator, worker.pid, boom_expr)
+ local full_desc = name .. ': '
+ if desc then
+ full_desc = full_desc .. desc .. ' (' .. boom_expr .. ')'
+ else
+ full_desc = full_desc .. boom_expr
+ end
+ tap.boom(map, {full_expr}, full_desc)
+ end
+end
+
+function tests()
+ -- add delay to each test to force scheduler to interleave tests and DNS queries
+ local test_delay = 20 / 1000 -- seconds
+ log_info(ffi.C.LOG_GRP_TESTS, 'starting map() tests now')
+
+ tap.boom(map, {'1 ++ 1'}, 'syntax error in command is detected')
+ worker.sleep(test_delay)
+
+ -- array of integers
+ local pids = map('worker.pid')
+ tap.same(pids.n, n_instances, 'all pids were obtained')
+ table.sort(pids)
+ worker.sleep(test_delay)
+
+ -- expression produces array of integers
+ local pids_plus_one = map('worker.pid + 1')
+ tap.same(pids_plus_one.n, n_instances, 'all pids were obtained')
+ table.sort(pids_plus_one)
+ for idx=1,n_instances do
+ tap.same(pids[idx] + 1, pids_plus_one[idx],
+ 'increment expression worked')
+ end
+ worker.sleep(test_delay)
+
+ -- error detection
+ boom_follower_and_leader('error("explosion")')
+ worker.sleep(test_delay)
+
+ -- unsupported number of return values
+ boom_follower_and_leader('1, 2')
+ worker.sleep(test_delay)
+ boom_follower_and_leader('unpack({})')
+ worker.sleep(test_delay)
+
+ -- unsupported return type
+ boom_follower_and_leader(
+ 'function() print("this cannot be serialized") end')
+ worker.sleep(test_delay)
+
+ tap.same({n = n_instances}, map('nil'),
+ 'nil values are counted as returned')
+ worker.sleep(test_delay)
+
+ local exp = {n = n_instances}
+ for i=1,n_instances do
+ table.insert(exp, {nil, 2, nil, n=3})
+ end
+ local got = map('require("kluautil").kr_table_pack(nil, 2, nil)')
+ tap.same(got, exp, 'kr_table_pack handles nil values')
+ worker.sleep(test_delay)
+end
+
+local started = false
+function tests_start()
+ -- just in case, duplicates should not happen
+ if started then
+ log_info(ffi.C.LOG_GRP_TESTS, 'huh? duplicate test invocation ignored, a retransmit?')
+ return
+ end
+ started = true
+ log_info(ffi.C.LOG_GRP_TESTS, 'start query triggered, scheduling tests')
+
+ -- DNS queries and map() commands must be serviced while sleep is running
+ worker.coroutine(function() worker.sleep(3600) end)
+
+ worker.coroutine(tests)
+end
+-- Deckard query will trigger tests
+policy.add(policy.suffix(tests_start, {'\5start\0'}))
+
+function tests_done()
+ print('final query triggered')
+ event.after(0, function()
+ tap.done(checks_total)
+ end)
+end
+-- Deckard query will execute tap.done() which will call os.exit()
+-- i.e. this callback has to be called only after answer to Deckard was sent
+policy.add(policy.suffix(tests_done, {'\4done\0'}), true)
+
+-- add delay to each query to force scheduler to interleave tests and DNS queries
+policy.add(policy.all(
+ function()
+ local delay = 10 -- ms
+ log_info(ffi.C.LOG_GRP_TESTS, 'packet delayed by %d ms', delay)
+ worker.sleep(delay / 1000)
+ end))
+
+wait_for_sockets()
+
+{% if DAEMON_NAME == "kresd1" %}
+
+-- forward to Deckard test server
+policy.add(policy.all(policy.FORWARD('192.0.2.1')))
+
+{% else %}
+
+-- forward to next kresd instance in chain
+{# find out IP address of kresd instance with lower number,
+ i.e. kresd2 forwards to kresd1 #}
+policy.add(policy.all(policy.FORWARD('{{ PROGRAMS[ "kresd" ~ (DAEMON_NAME[-1]|int() - 1)]["address"] }}')))
+
+{% endif %}
diff --git a/daemon/lua/map.test.integr/query-while-map-is-running.rpl b/daemon/lua/map.test.integr/query-while-map-is-running.rpl
new file mode 100644
index 0000000..8590fc8
--- /dev/null
+++ b/daemon/lua/map.test.integr/query-while-map-is-running.rpl
@@ -0,0 +1,312 @@
+; does not make any practical difference so we limit ourselves to single test run
+query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN Empty answers to any query - forwarding without validation
+
+; forwarding target
+RANGE_BEGIN 1 1000000
+ ADDRESS 192.0.2.1
+
+; NODATA to everything
+ENTRY_BEGIN
+MATCH opcode
+ADJUST copy_id copy_query
+REPLY NOERROR QR
+SECTION QUESTION
+. IN SOA
+SECTION ANSWER
+. 86400 IN SOA rootns. you.test. 2017071100 1800 900 604800 86400
+ENTRY_END
+RANGE_END
+
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+start. IN TXT
+ENTRY_END
+
+STEP 11 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+start. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+
+STEP 1001 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1001. IN TXT
+ENTRY_END
+
+STEP 1002 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1001. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1003 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1003. IN TXT
+ENTRY_END
+
+STEP 1004 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1003. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1005 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1005. IN TXT
+ENTRY_END
+
+STEP 1006 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1005. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1007 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1007. IN TXT
+ENTRY_END
+
+STEP 1008 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1007. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1009 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1009. IN TXT
+ENTRY_END
+
+STEP 1010 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1009. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1011 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1011. IN TXT
+ENTRY_END
+
+STEP 1012 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1011. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1013 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1013. IN TXT
+ENTRY_END
+
+STEP 1014 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1013. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1015 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1015. IN TXT
+ENTRY_END
+
+STEP 1016 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1015. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1017 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1017. IN TXT
+ENTRY_END
+
+STEP 1018 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1017. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1019 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1019. IN TXT
+ENTRY_END
+
+STEP 1020 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1019. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1021 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1021. IN TXT
+ENTRY_END
+
+STEP 1022 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1021. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1023 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1023. IN TXT
+ENTRY_END
+
+STEP 1024 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1023. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1025 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1025. IN TXT
+ENTRY_END
+
+STEP 1026 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1025. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1027 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1027. IN TXT
+ENTRY_END
+
+STEP 1028 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1027. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1029 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1029. IN TXT
+ENTRY_END
+
+STEP 1030 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1029. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1031 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test1031. IN TXT
+ENTRY_END
+
+STEP 1032 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+test1031. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+STEP 1033 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+done. IN TXT
+ENTRY_END
+
+STEP 1034 CHECK_ANSWER
+ENTRY_BEGIN
+REPLY NOERROR QR RD RA
+MATCH opcode rcode flags question answer
+SECTION QUESTION
+done. IN TXT
+SECTION ANSWER
+ENTRY_END
+
+SCENARIO_END
diff --git a/daemon/lua/meson.build b/daemon/lua/meson.build
new file mode 100644
index 0000000..b19777c
--- /dev/null
+++ b/daemon/lua/meson.build
@@ -0,0 +1,118 @@
+# daemon: lua modules
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+config_tests += [
+ ['controlsock', files('controlsock.test.lua')],
+ ['krprint', files('krprint.test.lua')],
+ ['log', files('log.test.lua')],
+ ['ta', files('trust_anchors.test/ta.test.lua')],
+ ['ta_bootstrap', files('trust_anchors.test/bootstrap.test.lua'), ['y2k38']],
+]
+
+integr_tests += [
+ ['map', meson.current_source_dir() / 'map.test.integr'],
+]
+
+lua_config = configuration_data()
+lua_config.set('keyfile_default', keyfile_default)
+lua_config.set('etc_dir', etc_dir)
+lua_config.set('run_dir', run_dir)
+lua_config.set('systemd_cache_dir', systemd_cache_dir)
+lua_config.set('unmanaged', managed_ta ? 'false' : 'true')
+
+trust_anchors = configure_file(
+ input: 'trust_anchors.lua.in',
+ output: 'trust_anchors.lua',
+ configuration: lua_config,
+)
+
+sandbox = configure_file(
+ input: 'sandbox.lua.in',
+ output: 'sandbox.lua',
+ configuration: lua_config,
+)
+
+distro_preconfig = configure_file(
+ input: 'distro-preconfig.lua.in',
+ output: 'distro-preconfig.lua',
+ configuration: lua_config,
+)
+
+# Unfortunately the different ABI implies different contents of 'kres-gen.lua'.
+if libknot.version().version_compare('>= 3.2')
+ kres_gen_fname = 'kres-gen-32.lua'
+elif libknot.version().version_compare('>= 3.1')
+ kres_gen_fname = 'kres-gen-31.lua'
+else
+ kres_gen_fname = 'kres-gen-30.lua'
+endif
+
+kres_gen_lua = configure_file(
+ input: kres_gen_fname,
+ output: 'kres-gen.lua',
+ copy: true,
+)
+
+run_target( # run manually to re-generate kres-gen.lua
+ 'kres-gen',
+ command: [ find_program('./kres-gen.sh'), kres_gen_fname ],
+)
+
+# A simple config test: check that sizes of some structures match
+# in C and pre-generated lua bindings.
+# The point is that regeneration is quite expensive in time and dependencies,
+# but this basic sanity check could be ran always, except for cross compilation,
+# as we *run* luajit to find out the real sizes.
+if get_option('kres_gen_test') and not meson.is_cross_build()
+ types_to_check = [
+ { 'tname': 'time_t', 'incl': '#include <sys/time.h>' },
+ { 'tname': 'struct timeval', 'incl' : '#include <sys/time.h>' },
+ { 'tname': 'zs_scanner_t', 'incl': '#include <libzscanner/scanner.h>', 'dep': libzscanner },
+ { 'tname': 'knot_pkt_t', 'incl' : '#include <libknot/packet/pkt.h>', 'dep': libknot },
+ ]
+ # Construct the lua tester as a meson string.
+ kres_gen_test_luastr = '''
+ dofile('@0@')
+ local ffi = require('ffi')
+ '''.format(meson.current_source_dir() / kres_gen_fname)
+ foreach ttc: types_to_check
+ # We're careful with adding just includes; otherwise it's more fragile (e.g. linking flags).
+ if 'dep' in ttc
+ dep = ttc.get('dep').partial_dependency(includes: true, compile_args: true)
+ else
+ dep = []
+ endif
+ tsize = meson.get_compiler('c').sizeof(ttc.get('tname'), prefix: ttc.get('incl'),
+ dependencies: dep)
+ kres_gen_test_luastr += '''
+ assert(ffi.sizeof(ffi.typeof('@0@')) == @1@,
+ 'Lua binding for C type ' .. '@0@' .. ' has incorrect size: '
+ .. ffi.sizeof(ffi.typeof('@0@'))
+ )
+ '''.format(ttc.get('tname'), tsize)
+ endforeach
+ # Now feed it directly into luajit.
+ kres_gen_test = run_command(find_program('luajit'), '-e', kres_gen_test_luastr, check: false)
+ if kres_gen_test.returncode() != 0
+ error('if you use released Knot* versions, please contact us: https://www.knot-resolver.cz/contact/\n'
+ + kres_gen_test.stderr().strip())
+ endif
+endif
+
+lua_src = [
+ files('postconfig.lua'),
+ files('kres.lua'),
+ kres_gen_lua,
+ sandbox,
+ trust_anchors,
+ files('zonefile.lua'),
+ files('kluautil.lua'),
+ files('krprint.lua'),
+ distro_preconfig,
+]
+
+# install daemon lua sources
+install_data(
+ lua_src,
+ install_dir: lib_dir,
+)
diff --git a/daemon/lua/postconfig.lua b/daemon/lua/postconfig.lua
new file mode 100644
index 0000000..ac71660
--- /dev/null
+++ b/daemon/lua/postconfig.lua
@@ -0,0 +1,70 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local ffi = require('ffi')
+local C = ffi.C
+
+local function count_sockets()
+ local dns_socks = 0
+ local control_socks = 0
+ for _, socket in ipairs(net.list()) do
+ if socket.kind == 'control' then
+ control_socks = control_socks + 1
+ elseif (socket.kind == 'dns' or
+ socket.kind == 'xdp' or
+ socket.kind == 'tls' or
+ socket.kind == 'doh_legacy' or
+ socket.kind == 'doh2') then
+ dns_socks = dns_socks + 1
+ end
+ end
+ return dns_socks, control_socks
+end
+
+local n_dns_socks, n_control_socks = count_sockets()
+
+-- Check and set control sockets path
+worker.control_path = worker.control_path or (worker.cwd .. '/control/')
+
+-- Bind to control socket by default
+if n_control_socks == 0 and not env.KRESD_NO_LISTEN then
+ local path = worker.control_path..worker.pid
+ local ok, err = pcall(net.listen, path, nil, { kind = 'control' })
+ if not ok then
+ log_warn(C.LOG_GRP_NETWORK, 'bind to '..path..' failed '..err)
+ end
+end
+
+-- Listen on localhost
+if n_dns_socks == 0 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
+ -- Binding to other ifaces may fail
+ ok, err = pcall(net.listen, '127.0.0.1', 853)
+ if not ok then
+ log_info(ffi.C.LOG_GRP_NETWORK, 'bind to 127.0.0.1@853 '..err)
+ end
+ ok, err = pcall(net.listen, '::1')
+ if not ok then
+ log_info(ffi.C.LOG_GRP_NETWORK, 'bind to ::1@53 '..err)
+ end
+ ok, err = pcall(net.listen, '::1', 853)
+ if not ok then
+ log_info(ffi.C.LOG_GRP_NETWORK, 'bind to ::1@853 '..err)
+ end
+ -- Exit when kresd isn't listening on any interfaces
+ n_dns_socks, _ = count_sockets()
+ if n_dns_socks == 0 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 C.kr_zonecut_is_empty(kres.context().root_hints) then
+ _hint_root_file()
+end
diff --git a/daemon/lua/sandbox.lua.in b/daemon/lua/sandbox.lua.in
new file mode 100644
index 0000000..7c6a818
--- /dev/null
+++ b/daemon/lua/sandbox.lua.in
@@ -0,0 +1,833 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local debug = require('debug')
+local ffi = require('ffi')
+local kluautil = require('kluautil')
+local krprint = require("krprint")
+
+-- 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
+
+-- from syslog.h
+LOG_CRIT = 2
+LOG_ERR = 3
+LOG_WARNING = 4
+LOG_NOTICE = 5
+LOG_INFO = 6
+LOG_DEBUG = 7
+
+local function curr_file() return debug.getinfo(4,'S').source end
+local function curr_line() return debug.getinfo(4,'l').currentline end
+
+local function log_fmt(grp, level, fmt, ...)
+ ffi.C.kr_log_fmt(grp, level,
+ 'CODE_FILE='..curr_file(), 'CODE_LINE='..curr_line(), 'CODE_FUNC=',
+ '[%-6s] %s\n', ffi.C.kr_log_grp2name(grp), string.format(fmt, ...))
+end
+
+function log_req(req, qry_uid, indent, grp, fmt, ...)
+ ffi.C.kr_log_req1(req, qry_uid, indent, grp, ffi.C.kr_log_grp2name(grp),
+ '%s\n', string.format(fmt, ...))
+end
+
+function log_qry(qry, grp, fmt, ...)
+ ffi.C.kr_log_q1(qry, grp, ffi.C.kr_log_grp2name(grp),
+ '%s\n', string.format(fmt, ...))
+end
+
+function panic(fmt, ...)
+ print(debug.traceback('error occurred here (config filename:lineno is '
+ .. 'at the bottom, if config is involved):', 2))
+ error(string.format('ERROR: '.. fmt, ...), 0)
+end
+
+function log_error(grp, fmt, ...)
+ log_fmt(grp, LOG_ERR, fmt, ...)
+end
+
+function log_warn(grp, fmt, ...)
+ log_fmt(grp, LOG_WARNING, fmt, ...)
+end
+
+function log_notice(grp, fmt, ...)
+ log_fmt(grp, LOG_NOTICE, fmt, ...)
+end
+
+function log_info(grp, fmt, ...)
+ log_fmt(grp, LOG_INFO, fmt, ...)
+end
+
+function log_debug(grp, fmt, ...)
+ log_fmt(grp, LOG_DEBUG, fmt, ...)
+end
+
+function log(fmt, ...)
+ log_notice(ffi.C.LOG_GRP_MODULE, fmt, ...)
+end
+
+-- Resolver bindings
+kres = require('kres')
+if rawget(kres, 'str2dname') ~= nil then
+ todname = kres.str2dname
+end
+
+worker.resolve_pkt = function (pkt, options, finish, init)
+ options = kres.mk_qflags(options)
+ local task = ffi.C.worker_resolve_start(pkt, options)
+
+ -- Deal with finish and init callbacks
+ if finish ~= nil then
+ local finish_cb
+ finish_cb = ffi.cast('trace_callback_f',
+ function (req)
+ jit.off(true, true) -- JIT for (C -> lua)^2 nesting isn't allowed
+ finish(req.answer, req)
+ finish_cb:free()
+ end)
+ task.ctx.req.trace_finish = finish_cb
+ end
+ if init ~= nil then
+ init(task.ctx.req)
+ end
+
+ return ffi.C.worker_resolve_exec(task, pkt) == 0
+end
+
+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
+ qclass = t.class
+ options = t.options
+ finish = t.finish
+ init = t.init
+ end
+ qtype = qtype or kres.type.A
+ qclass = qclass or kres.class.IN
+ options = kres.mk_qflags(options)
+ -- LATER: nicer errors for rubbish in qname, qtype, qclass?
+ local pkt = ffi.C.worker_resolve_mk_pkt(qname, qtype, qclass, options)
+ if pkt == nil then
+ panic('failure in worker.resolve(); probably invalid qname "%s"', qname)
+ end
+ local ret = worker.resolve_pkt(pkt, options, finish, init)
+ ffi.C.knot_pkt_free(pkt);
+ return ret
+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
+})
+
+debugging = {}
+setmetatable(debugging, {
+ __index = function(_, k)
+ if k == 'assertion_abort' then return ffi.C.kr_dbg_assertion_abort
+ elseif k == 'assertion_fork' then return ffi.C.kr_dbg_assertion_fork
+ else panic('invalid debugging option: ' .. tostring(k))
+ end
+ end,
+ __newindex = function(_, k, v)
+ if k == 'assertion_abort' then ffi.C.kr_dbg_assertion_abort = v
+ elseif k == 'assertion_fork' then ffi.C.kr_dbg_assertion_fork = v
+ else panic('invalid debugging option: ' .. tostring(k))
+ end
+ 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
+})
+
+-- Set up lua table for a C module. (Internal function.)
+function modules_create_table_for_c(kr_module_ud)
+ local kr_module = ffi.cast('struct kr_module **', kr_module_ud)[0]
+ --- Set up the global table named according to the module.
+ if kr_module.config == nil and kr_module.props == nil then
+ return
+ end
+ local module = {}
+ local module_name = ffi.string(kr_module.name)
+ _G[module_name] = module
+
+ --- Construct lua functions for properties.
+ if kr_module.props ~= nil then
+ local i = 0
+ while true do
+ local prop = kr_module.props[i]
+ local cb = prop.cb
+ if cb == nil then break; end
+ module[ffi.string(prop.name)] =
+ function (arg) -- lua wrapper around kr_prop_cb function typedef
+ local arg_conv
+ if type(arg) == 'table' or type(arg) == 'boolean' then
+ arg_conv = tojson(arg)
+ elseif arg ~= nil then
+ arg_conv = tostring(arg)
+ end
+ local ret_cstr = cb(ffi.C.the_worker.engine, kr_module, arg_conv)
+ if ret_cstr == nil then
+ return nil
+ end
+ -- LATER(optim.): superfluous copying
+ local ret_str = ffi.string(ret_cstr)
+ -- This is a bit ugly, but the API is that invalid JSON
+ -- should be just returned as string :-(
+ local status, ret = pcall(fromjson, ret_str)
+ if not status then ret = ret_str end
+ ffi.C.free(ret_cstr)
+ return ret
+ end
+ i = i + 1
+ end
+ end
+
+ --- Construct lua function for config().
+ if kr_module.config ~= nil then
+ module.config =
+ function (arg)
+ local arg_conv
+ if type(arg) == 'table' or type(arg) == 'boolean' then
+ arg_conv = tojson(arg)
+ elseif arg ~= nil then
+ arg_conv = tostring(arg)
+ end
+ return kr_module.config(kr_module, arg_conv)
+ end
+ end
+
+ --- Add syntactic sugar for get() and set() properties.
+ --- That also "catches" any commands like `moduleName.foo = bar`.
+ local m_index, m_newindex
+ local get_f = rawget(module, 'get')
+ if get_f ~= nil then
+ m_index = function (_, key)
+ return get_f(key)
+ end
+ else
+ m_index = function ()
+ error('module ' .. module_name .. ' does not support indexing syntax sugar')
+ end
+ end
+ local set_f = rawget(module, 'set')
+ if set_f ~= nil then
+ m_newindex = function (_, key, value)
+ -- This will produce a nasty error on some non-string parameters.
+ -- Still, we already use it with integer values, e.g. in predict module :-/
+ return set_f(key .. ' ' .. value)
+ end
+ else
+ m_newindex = function ()
+ error('module ' .. module_name .. ' does not support assignment syntax sugar')
+ end
+ end
+ setmetatable(module, {
+ -- note: the two functions only get called for *missing* indices
+ __index = m_index,
+ __newindex = m_newindex,
+ })
+end
+
+local layer_ctx = ffi.C.kr_layer_t_static
+-- Utilities internal for lua layer glue; see ../ffimodule.c
+modules_ffi_layer_wrap1 = function (layer_cb)
+ return layer_cb(layer_ctx.state, layer_ctx.req)
+end
+modules_ffi_layer_wrap2 = function (layer_cb)
+ return layer_cb(layer_ctx.state, layer_ctx.req, layer_ctx.pkt)
+end
+modules_ffi_layer_wrap_checkout = function (layer_cb)
+ return layer_cb(layer_ctx.state, layer_ctx.req, layer_ctx.pkt,
+ layer_ctx.dst, layer_ctx.is_stream)
+end
+modules_ffi_wrap_modcb = function (cb, kr_module_ud) -- this one isn't for layer
+ local kr_module = ffi.cast('struct kr_module **', kr_module_ud)[0]
+ return cb(kr_module)
+end
+
+-- Return filesystem size where the cache resides.
+cache.fssize = function ()
+ local path = cache.current_storage or '.'
+ -- As it is now, `path` may or may not include the lmdb:// prefix.
+ if string.sub(path, 1, 7) == 'lmdb://' then
+ path = string.sub(path, 8)
+ end
+ if #path == 0 then
+ path = '.'
+ end
+ local size = tonumber(ffi.C.kr_fssize(path))
+ if size < 0 then
+ panic('cache.fssize(): %s', ffi.string(ffi.C.knot_strerror(size)))
+ else
+ return size
+ 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_info(ffi.C.LOG_GRP_CACHE, 'asynchronous 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
+})
+
+-- Make sandboxed environment
+local function make_sandbox(defined)
+ local __protected = {
+ worker = true, env = true, debugging = 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
+_G = make_sandbox(getfenv(0))
+setfenv(0, _G)
+
+-- Load default modules
+trust_anchors = require('trust_anchors')
+modules.load('ta_update')
+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')
+modules.load('refuse_nord')
+modules.load('watchdog')
+modules.load('extended_error')
+
+-- Load keyfile_default
+trust_anchors.add_file('@keyfile_default@', @unmanaged@)
+
+local function eval_cmd_compile(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
+ return chunk, err
+end
+
+-- Interactive command evaluation
+function eval_cmd(line, raw)
+ local chunk, err = eval_cmd_compile(line, raw)
+ if not err then
+ return chunk()
+ else
+ error(err)
+ end
+end
+
+-- Pretty printing
+local pprint = require('krprint').pprint
+function table_print(...)
+ local strs = {}
+ local nargs = select('#', ...)
+ if nargs == 0 then
+ return nil
+ end
+ for n=1,nargs do
+ local arg = select(n, ...)
+ local arg_str = pprint(arg)
+ if nargs > 1 then
+ table.insert(strs, string.format("%s\t-- result # %d", arg_str, n))
+ else
+ table.insert(strs, arg_str)
+ end
+ end
+ return table.concat(strs, '\n')
+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
+ log_warn(ffi.C.LOG_GRP_SYSTEM, '%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('Lua library cqueues is required for asynchronous execution (luaJIT requires library for Lua 5.1)')
+ end
+ worker.sleep = disabled
+ worker.map = disabled
+ worker.coroutine = disabled
+ worker.bg_worker = setmetatable({}, { __index = disabled })
+end
+
+-- Global commands for map()
+
+-- must be public because it is called from eval_cmd()
+-- when map() commands are read from control socket
+function _map_luaobj_call_wrapper(cmd)
+ local func = eval_cmd_compile(cmd, true)
+ local ret = kluautil.kr_table_pack(xpcall(func, debug.traceback))
+ local ok, serial = pcall(krprint.serialize_lua, ret, 'error')
+ if not ok then
+ log_error(ffi.C.LOG_GRP_SYSTEM, 'failed to serialize map() response %s (%s)',
+ table_print(ret), serial)
+ return krprint.serialize_lua(
+ kluautil.kr_table_pack(false, "returned values cannot be serialized: "
+ .. serial))
+ else
+ return serial
+ end
+end
+
+local function _sock_errmsg(path, desc)
+ return string.format(
+ 'map() error while communicating with %s: %s',
+ path, desc)
+end
+
+local function _sock_check(sock, call, params, path, desc)
+ local errprefix = _sock_errmsg(path, desc) .. ': '
+ local retvals = kluautil.kr_table_pack(pcall(call, unpack(params)))
+ local ok = retvals[1]
+ if not ok then
+ error(errprefix .. tostring(retvals[2]))
+ end
+ local rerr, werr = sock:error()
+ if rerr or werr then
+ error(string.format('%sread error %s; write error %s', errprefix, rerr, werr))
+ end
+ if retvals[2] == nil then
+ error(errprefix .. 'unexpected nil result')
+ end
+ return unpack(retvals, 2, retvals.n)
+end
+
+local function _sock_assert(condition, path, desc)
+ if not condition then
+ error(_sock_errmsg(path, desc))
+ end
+end
+
+local function map_send_recv(cmd, path)
+ local bit = require('bit')
+ local socket = require('cqueues.socket')
+ local s = socket.connect({ path = path })
+ s:setmaxerrs(0)
+ s:setmode('bn', 'bn')
+ local status, err = pcall(s.connect, s)
+ if not status then
+ log_error(ffi.C.LOG_GRP_NETWORK, 'map() error while connecting to control socket %s: '
+ .. '%s (ignoring this socket)', path, err)
+ return nil
+ end
+ local ret = _sock_check(s, s.write, {s, '__binary\n'}, path,
+ 'write __binary')
+ _sock_assert(ret, path,
+ 'write __binary result')
+ local recv = _sock_check(s, s.read, {s, 2}, path,
+ 'read reply to __binary')
+ _sock_assert(recv and recv == '> ', path,
+ 'unexpected reply to __binary')
+ _sock_check(s, s.write, {s, cmd..'\n'}, path,
+ 'command write')
+ recv = _sock_check(s, s.read, {s, 4}, path,
+ 'response length read')
+ _sock_assert(recv and #recv == 4, path,
+ 'length of response length preamble does not match')
+ local len = tonumber(recv:byte(1))
+ for i=2,4 do
+ len = bit.bor(bit.lshift(len, 8), tonumber(recv:byte(i)))
+ end
+ ret = _sock_check(s, s.read, {s, len}, path,
+ 'read response')
+ _sock_assert(ret and #ret == len, path,
+ 'actual response length does not match length in preamble')
+ s:close()
+ return ret
+end
+
+-- internal use only
+-- Call cmd on each instance via control sockets.
+-- @param format - "luaobj" if individual results should be Lua objects
+-- - "strings" for eval_cmd output for each instance
+-- @returns table with results, one item per instance + key n=number of instances
+-- (order of return values is undefined)
+-- @throws Lua error if:
+-- - communication failed in the middle of transaction
+-- - a result is not serializable
+-- - individual call throws an error
+-- - number of return values != 1 per instance per call
+-- - cmd execution state is undefined after an error
+-- Connection errors at the beginning are ignored to paper over leftover dead sockets.
+function map(cmd, format)
+ local local_sockets = {}
+ local results = {}
+
+ if (type(cmd) ~= 'string') then
+ panic('map() command must be a string') end
+ if string.find(cmd, '\n', 1, true) then
+ panic('map() command cannot contain literal \\n, escape it with \\010') end
+ if (#cmd <= 0) then
+ panic('map() command must be non-empty') end
+ -- syntax check on input command to detect typos early
+ local chunk, err = eval_cmd_compile(cmd, false)
+ if not chunk then
+ panic('failure when compiling map() command: %s', err)
+ end
+
+ format = format or 'luaobj'
+ if (format ~= 'luaobj' and format ~= 'strings') then
+ panic('map() output format must be luaobj or strings') end
+ if format == 'luaobj' then
+ cmd = '_map_luaobj_call_wrapper([=====[' .. cmd .. ']=====])'
+ end
+
+ -- find out control socket paths
+ for _,v in pairs(net.list()) do
+ if (v['kind'] == 'control') and (v['transport']['family'] == 'unix') then
+ table.insert(local_sockets, string.match(v['transport']['path'], '^.*/([^/]+)$'))
+ end
+ end
+ local filetab = kluautil.list_dir(worker.control_path)
+ if next(filetab) == nil then
+ panic('no control sockets found in directory %s',
+ worker.control_path)
+ end
+
+ local result_count = 0
+ -- finally execute it on all instances
+ for _, file in ipairs(filetab) do
+ local local_exec = false
+ for _, lsoc in ipairs(local_sockets) do
+ if file == lsoc then
+ local_exec = true
+ end
+ end
+ local path = worker.control_path..file
+ local path_name = (local_exec and 'this instance') or path
+ log_info(ffi.C.LOG_GRP_SYSTEM, 'executing map() on %s: command %s', path_name, cmd)
+ local ret
+ if local_exec then
+ ret = eval_cmd(cmd)
+ else
+ ret = map_send_recv(cmd, path)
+ -- skip dead sockets (leftovers from dead instances)
+ if ret == nil then
+ goto continue
+ end
+ end
+ result_count = result_count + 1
+ -- return value is output from eval_cmd
+ -- i.e. string including "quotes" and Lua escaping in between
+ assert(type(ret) == 'string', 'map() protocol error, '
+ .. 'string not retured by follower')
+ assert(#ret >= 2 and
+ string.sub(ret, 1, 1) == "'"
+ and string.sub(ret, -1, -1) == "'",
+ 'map() protocol error, value returned by follower does '
+ .. 'not look like a string')
+ -- deserialize string: remove "quotes" and de-escape bytes
+ ret = krprint.deserialize_lua(ret)
+ if format == 'luaobj' then
+ -- ret should be table with xpcall results serialized into string
+ ret = krprint.deserialize_lua(ret)
+ assert(type(ret) == 'table', 'map() protocol error, '
+ .. 'table with results not retured by follower')
+ if (ret.n ~= 2) then
+ log_error(ffi.C.LOG_GRP_SYSTEM, 'got unsupported map() response: %s', table_print(ret))
+ panic('unexpected number of return values in map() response: '
+ .. 'only single return value is allowed, '
+ .. 'use kluautil.kr_table_pack() helper')
+ end
+ local ok, retval = ret[1], ret[2]
+ if ok == false then
+ panic('error when executing map() command on control socket %s: '
+ .. '%s. command execution state is now undefined!',
+ path, retval)
+ end
+ -- drop wrapper table and return only the actual return value
+ ret = retval
+ end
+ results[result_count] = ret
+ ::continue::
+ end
+ results.n = result_count
+ return results
+end
diff --git a/daemon/lua/trust_anchors.lua.in b/daemon/lua/trust_anchors.lua.in
new file mode 100644
index 0000000..56a7f95
--- /dev/null
+++ b/daemon/lua/trust_anchors.lua.in
@@ -0,0 +1,532 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+-- 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
+
+-- RFC5011 state table
+local key_state = {
+ Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
+ Missing = 'Missing', Revoked = 'Revoked', Removed = 'Removed'
+}
+
+local function upgrade_required(msg)
+ if msg then
+ msg = msg .. '\n'
+ else
+ msg = ''
+ end
+ panic('Configuration upgrade required: ' .. msg .. 'Please refer to ' ..
+ 'https://knot-resolver.readthedocs.io/en/stable/upgrading.html')
+end
+
+-- TODO: Move bootstrap to a separate module or even its own binary
+-- 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_info(ffi.C.LOG_GRP_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)
+ local kluautil = require('kluautil')
+ local file = io.tmpfile()
+ -- 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 rcode, errmsg = kluautil.kr_https_fetch(url, file, ca)
+ if rcode == nil then
+ file:close()
+ return false, string.format('[ ta ] fetch of "%s" failed: %s', url, errmsg)
+ end
+
+ local xml = file:read("*a")
+ file:close()
+
+ -- 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 ] Bootstrapped root trust anchors are:'
+ .. rrset
+ return rrset, msg
+end
+
+local function bootstrap_write(rrstr, filename)
+ local fname_tmp = filename .. '.lock.' .. tostring(worker.pid);
+ local file = assert(io.open(fname_tmp, 'w'))
+ file:write(rrstr)
+ file:close()
+ assert(os.rename(fname_tmp, filename))
+end
+-- Bootstrap end
+
+-- Update ta.comment and return decorated line representing the RR
+-- This is meant to be in zone-file format.
+local function ta_rr_str(ta)
+ 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
+ return rr_str
+end
+
+-- Write keyset to a file. States and timers are stored in comments.
+local function keyset_write(keyset)
+ if not keyset.managed then -- not to be persistent, this is an error!
+ panic('internal error: keyset_write called for an unmanaged TA')
+ end
+ local fname_tmp = keyset.filename .. '.lock.' .. tostring(worker.pid);
+ local file = assert(io.open(fname_tmp, 'w'))
+ for i = 1, #keyset do
+ file:write(ta_rr_str(keyset[i]))
+ end
+ file:close()
+ assert(os.rename(fname_tmp, keyset.filename))
+end
+
+-- Search the values of a table and return the corresponding 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 xor a string. (This includes the key states and timers.)
+local function keyset_read(path, str)
+ if (path == nil) == (str == nil) then -- exactly one of them must be nil
+ return nil, "internal ERROR: incorrect call to TA's keyset_read"
+ end
+ -- First load the regular entries, trusting them.
+ local zonefile = require('zonefile')
+ local tas, err
+ if path ~= nil then
+ tas, err = zonefile.file(path)
+ else
+ tas, err = zonefile.string(str)
+ end
+ 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.
+ local line_iter
+ if path ~= nil then
+ line_iter = io.lines(path)
+ else
+ line_iter = string.gmatch(str, "[^\n]+")
+ end
+ for line in line_iter 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
+
+ -- Fill tas[*].key_tag
+ 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
+
+ -- Fill tas.owner
+ if not tas[1] then
+ return nil, "empty TA set"
+ end
+ local owner = tas[1].owner
+ for _, ta in ipairs(tas) do
+ if ta.owner ~= owner then
+ return nil, string.format("do not mix %s and %s TAs in single file/string",
+ kres.dname2str(ta.owner), kres.dname2str(owner))
+ end
+ end
+ tas.owner = owner
+
+ return tas
+end
+
+-- Replace current TAs for given owner by the "trusted" ones from passed keyset.
+-- Return true iff no TA errored out and at least one is in VALID state.
+local function keyset_publish(keyset)
+ local store = kres.context().trust_anchors
+ local count = 0
+ local has_error = false
+ 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
+ else
+ ta.state = 'ERROR'
+ has_error = true
+ end
+ end
+ end
+ if count == 0 then
+ log_error(ffi.C.LOG_GRP_TA, 'ERROR: no anchors are trusted for ' ..
+ kres.dname2str(keyset.owner) .. ' !')
+ end
+ return count > 0 and not has_error
+end
+
+local function add_file(path, unmanaged)
+ local managed = not unmanaged
+ if not ta_update then
+ modules.load('ta_update')
+ end
+ if managed 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 TA for root zone if keyfile doesn't exist
+ if managed and not io.open(path, 'r') then
+ if trust_anchors.keysets['\0'] then
+ error(string.format(
+ "[ ta ] keyfile '%s' doesn't exist and root key is already installed, "
+ .. "cannot bootstrap; provide a path to valid file with keys", path))
+ end
+ log_info(ffi.C.LOG_GRP_TA, "keyfile '%s': doesn't exist, bootstrapping", path);
+ local rrstr, msg = bootstrap(trust_anchors.bootstrap_url, trust_anchors.bootstrap_ca)
+ if not rrstr then
+ msg = msg .. '\n'
+ .. '[ ta ] Failed to bootstrap root trust anchors!'
+ error(msg)
+ end
+ print(msg)
+ bootstrap_write(rrstr, path)
+ -- continue as if the keyfile was there
+ 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
+ keyset.filename = path
+ keyset.managed = managed
+
+ local owner = keyset.owner
+ local owner_str = kres.dname2str(owner)
+ local keyset_orig = trust_anchors.keysets[owner]
+ if keyset_orig then
+ log_warn(ffi.C.LOG_GRP_TA, 'warning: overriding previously set trust anchors for ' .. owner_str)
+ if keyset_orig.managed and ta_update then
+ ta_update.stop(owner)
+ end
+ end
+ trust_anchors.keysets[owner] = keyset
+
+ -- Replace the TA store used for validation
+ if keyset_publish(keyset) then
+ log_info(ffi.C.LOG_GRP_TA, 'installed trust anchors for domain ' .. owner_str .. ' are:\n'
+ .. trust_anchors.summary(owner))
+ end
+ -- TODO: if failed and for root, try to rebootstrap?
+
+ ta_update.start(owner, managed)
+end
+
+local function remove(zname)
+ local owner = kres.str2dname(zname)
+ if not trust_anchors.keysets[owner] then
+ return false
+ end
+
+ if ta_update then
+ ta_update.stop(owner)
+ end
+ trust_anchors.keysets[owner] = nil
+ local store = kres.context().trust_anchors
+ C.kr_ta_del(store, owner)
+ return true
+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 .. ta_rr_str(ta)
+ 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,
+ -- implying unmanaged TA if nil
+ -- The RR tables also contain some additional TA-specific fields.
+ keysets = {},
+
+ -- Documented properties:
+ insecure = {},
+
+ bootstrap_url = 'https://data.iana.org/root-anchors/root-anchors.xml',
+ bootstrap_ca = '@etc_dir@/icann-ca.pem',
+
+ -- 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 = function() upgrade_required('trust_anchors.config was removed, use trust_anchors.add_file()') end,
+ remove = remove,
+
+ keyset_publish = keyset_publish,
+ keyset_write = keyset_write,
+ key_state = key_state,
+
+ -- Add DS/DNSKEY record(s) (unmanaged)
+ add = function (keystr)
+ local keyset, err = keyset_read(nil, keystr)
+ if keyset ~= nil then
+ local owner = keyset.owner
+ local owner_str = kres.dname2str(owner)
+ local keyset_orig = trust_anchors.keysets[owner]
+ -- Set up trust_anchors.keysets[owner]
+ if keyset_orig then
+ if keyset_orig.managed then
+ panic('[ ta ] it is impossible to add an unmanaged TA for zone '
+ .. owner_str .. ' which already has a managed TA')
+ end
+ log_warn(ffi.C.LOG_GRP_TA, 'warning: extending previously set trust anchors for '
+ .. owner_str)
+ for _, ta in ipairs(keyset) do
+ table.insert(keyset_orig, ta)
+ end
+ end
+ -- Replace the TA store used for validation
+ if not keyset_publish(keyset) then
+ err = "when publishing the TA set"
+ -- trust_anchors.keysets[owner] was already updated to the
+ -- (partially) failing state, but I'm not sure how much to improve this
+ end
+ keyset.managed = false
+ trust_anchors.keysets[owner] = keyset
+
+ end
+ log_info(ffi.C.LOG_GRP_TA, 'New TA state:\n' .. trust_anchors.summary())
+ if err then
+ panic('[ ta ] .add() failed: ' .. err)
+ end
+ 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
+ for i = 1, #list do
+ local dname = kres.str2dname(list[i])
+ if trust_anchors.keysets[dname] then
+ error('cannot add NTA '..list[i]..' because it is TA. Use trust_anchors.remove() instead')
+ end
+ end
+
+ 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,
+ -- Return textual representation of all TAs (incl. negative)
+ -- It's meant for human consumption.
+ 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
+ upgrade_required('trust_anchors.file was removed, use trust_anchors.add_file()')
+ elseif k == 'negative' then
+ upgrade_required('trust_anchors.negative was removed, use trust_anchors.set_insecure()')
+ elseif k == 'keyfile_default' then
+ upgrade_required('trust_anchors.keyfile_default is now compiled in, see trust_anchors.remove()')
+ else rawset(t, k, v) end
+ end,
+})
+
+return trust_anchors
diff --git a/daemon/lua/trust_anchors.rst b/daemon/lua/trust_anchors.rst
new file mode 100644
index 0000000..40f79b6
--- /dev/null
+++ b/daemon/lua/trust_anchors.rst
@@ -0,0 +1,123 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. warning:: Options in this section are intended only for expert users and
+ normally should not be needed.
+
+Since version 4.0, **DNSSEC validation is enabled by default**.
+If you really need to turn DNSSEC off and are okay with lowering security of your
+system by doing so, add the following snippet to your configuration file.
+
+.. code-block:: lua
+
+ -- turns off DNSSEC validation
+ trust_anchors.remove('.')
+
+The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates
+and :rfc:`7646` negative trust anchors. Depending on your distribution, DNSSEC
+trust anchors should be either maintained in accordance with the distro-wide
+policy, or automatically maintained by the resolver itself.
+
+In practice this means that you can forget about it and your favorite Linux
+distribution will take care of it for you.
+
+Following functions allow to modify DNSSEC configuration *if you really have to*:
+
+
+.. function:: trust_anchors.add_file(keyfile[, readonly = false])
+
+ :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.
+ If you want to use bootstrapping, install `lua-http`_ library.
+
+ 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
+
+.. function:: trust_anchors.remove(zonename)
+
+ Remove specified trust anchor from trusted key set. Removing trust anchor for the root zone effectively disables DNSSEC validation (unless you configured another trust anchor).
+
+ .. code-block:: lua
+
+ > trust_anchors.remove('.')
+ true
+
+ If you want to disable DNSSEC validation for a particular domain but keep it enabled for the rest of DNS tree, use :func:`trust_anchors.set_insecure`.
+
+.. envvar:: trust_anchors.hold_down_time = 30 * day
+
+ :return: int (default: 30 * day)
+
+ Modify RFC5011 hold-down timer to given value. Intended only for testing purposes. 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.
+ Intended only for testing purposes.
+ 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 *negative trust anchor* (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.
+ If you want to disable DNSSEC validation completely use :func:`trust_anchors.remove` function instead.
+
+ Example output:
+
+ .. code-block:: lua
+
+ > trust_anchors.set_insecure({ '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.
+
+ .. note:: Static keys are very error-prone and should not be used in production. Use :func:`trust_anchors.add_file` instead.
+
+ 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.
+
+.. _lua-http: https://luarocks.org/modules/daurnimator/http
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..7dd248b
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/bootstrap.test.lua
@@ -0,0 +1,112 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+modules.load('ta_update')
+
+-- check prerequisites
+local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
+if not has_http then
+ -- skipping bootstrap tests because http module is not not installed
+ os.exit(77)
+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 >/dev/null 2>&1 &')
+ -- 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) < 10 do
+ local con = socket.connect("localhost", 8080)
+ connected, msg = pcall(con.connect, con, 3)
+ 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.remove('.')
+
+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'
+ trust_anchors.remove('.')
+ 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..9e7dac1
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/regen.sh
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+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/root.keys b/daemon/lua/trust_anchors.test/root.keys
new file mode 100644
index 0000000..e292b5a
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/root.keys
@@ -0,0 +1 @@
+. IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
diff --git a/daemon/lua/trust_anchors.test/ta.test.lua b/daemon/lua/trust_anchors.test/ta.test.lua
new file mode 100644
index 0000000..b977bc9
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/ta.test.lua
@@ -0,0 +1,85 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+trust_anchors.remove('.')
+
+local ffi = require('ffi')
+
+-- count warning messages
+warn_msg = {}
+overriding_msg="warning: overriding previously set trust anchors for ."
+warn_msg[overriding_msg] = 0
+function log_warn(grp, fmt, ...) --luacheck: no unused args
+ msg = string.format(fmt, ...)
+ if warn_msg[msg] ~= nil then
+ warn_msg[msg] = warn_msg[msg] + 1
+ end
+end
+
+-- Test that adding a revoked DNSKEY is refused.
+local function test_revoked_key()
+ local ta_c = kres.context().trust_anchors
+ same(ffi.C.kr_ta_del(ta_c, '\0'), 0, 'remove root TAs if any')
+ -- same() doesn't consider nil and typed NULL pointer equal, so we work around:
+ same(ffi.C.kr_ta_get(ta_c, '\0') == nil, true, 'no TA for root is used')
+ local key_crypto = 'AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFV'
+ .. 'QUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37'
+ .. 'NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAz'
+ .. 'vN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7O'
+ .. 'yQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0='
+ boom(trust_anchors.add, { '. 3600 DNSKEY 385 3 8 ' .. key_crypto }, 'refuse revoked key')
+ same(ffi.C.kr_ta_get(ta_c, '\0') == nil, true, 'no TA for root is used')
+ -- Test that we don't have another problem in the key
+ trust_anchors.add('. 3600 DNSKEY 257 3 8 ' .. key_crypto)
+ local root_ta = ffi.C.kr_ta_get(ta_c, '\0')
+ same(root_ta == nil, false, 'we got non-NULL TA RRset')
+ same(root_ta.rrs.count, 1, 'the root TA set contains one RR')
+end
+
+local function test_remove()
+ -- uses root key from the previous test
+ assert(trust_anchors.keysets['\0'], 'root key must be there from previous test')
+ local ta_c = kres.context().trust_anchors
+ local root_ta = ffi.C.kr_ta_get(ta_c, '\0')
+ assert(root_ta ~= nil, 'we got non-NULL TA RRset')
+ assert(root_ta.rrs.count, 1, 'we have a root TA set to be deleted')
+
+ trust_anchors.remove('.')
+
+ same(trust_anchors.keysets['\0'], nil, 'Lua interface does not have the removed key')
+ root_ta = ffi.C.kr_ta_get(ta_c, '\0')
+ same(root_ta == nil, true, 'C interface does not have the removed key')
+end
+
+local function test_add_file()
+ boom(trust_anchors.add_file, {'nonwriteable/root.keys', false},
+ "Managed trust anchor in non-writeable directory")
+
+ boom(trust_anchors.add_file, {'nonexistent.keys', true},
+ "Nonexistent unmanaged trust anchor file")
+
+ is(warn_msg[overriding_msg], 0, "No override warning messages at start of test")
+ trust_anchors.add_file('root.keys', true)
+ trust_anchors.add_file('root.keys', true)
+ is(warn_msg[overriding_msg], 1, "Warning message when override trust anchors")
+
+ is(trust_anchors.keysets['\0'][1].key_tag, 20326,
+ "Loaded KeyTag from root.keys")
+end
+
+local function test_nta()
+ assert(trust_anchors.keysets['\0'], 'root key must be there from previous tests')
+
+ trust_anchors.set_insecure({'example.com'})
+ is(trust_anchors.insecure[1], 'example.com', 'Add example.com to NTA list')
+ boom(trust_anchors.set_insecure, {{'.'}}, 'Got error when adding TA . to NTA list')
+ is(#trust_anchors.insecure, 1, 'Check one item in NTA list')
+ is(trust_anchors.insecure[1], 'example.com', 'Check previous NTA list')
+end
+
+return {
+ test_revoked_key,
+ test_remove,
+ test_add_file,
+ test_nta,
+}
+
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..c108bba
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/webserv.lua
@@ -0,0 +1,236 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+-- 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 8453
+ 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..2e95b23
--- /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:9e:ee:f2:d8:41:ae:2c:93:8a:01:1f:88:5b:d6:85
+ 29:2f:91:9d:37:fc:35:88:7f:53:71:87:fc:17:71:e7
+ 15:57:06:2d:54:fb:19:98:80:82:ec:1a:99:2d:57:cb
+ 5f:dd:28:26:d8:95:fb:65:b2:be:e1:11:86:69:14:7e
+ 32:5c:c0:02:0b:5d:11:78:69:50:20:25:3e:15:fb:8a
+ 46:d1:83:f9:3b:84:46:9c:69:21:44:d5:09:1d:7a:04
+ cc:f3:6a:ea:4c:1b:da:7c:40:dd:1c:6f:f6:85:b4:ea
+ 75:98:34:79:11:fb:cf:d3:18:70:64:25:33:8a:31:b6
+ 93:67:d4:32:67:61:1c:d0:7b:85:61:54:c6:fb:51:b6
+ 87:1d:d4:b8:58:40:a9:c5:32:ce:e0:b9:90:37:0d:58
+ e4:33:70:c5:c5:91:f2:18:f5:e0:08:ad:17:8b:cf:72
+ f1:26:6c:9c:88:d2:9e:06:4c:02:5d:4e:7c:93:af:8d
+ 72:93:75:1d:60:0c:f7:34:09:a8:e6:f2:80:4a:14:81
+ 24:40:4b:45:19:85:2e:ad:8e:97:4c:ff:ec:d0:9f:e6
+ a0:b7:c0:a9:a0:ad:d2:02:2d:13:55:f3:df:f8:f9:f1
+ f3:3e:35:e9:08:2b:db:11:93:57:13:55:c6:ba:c6:d7
+ ff:7d:e1:fa:8c:47:5d:da:bf:31:56:80:aa:34:97:43
+ bb:9e:ff:d3:e6:13:a9:c2:99:49:c2:1e:da:f2:c7:d2
+ d6:f7:5f:70:36:91:2f:ea:36:e9:88:44:08:a3:1a:0a
+ c0:e0:4b:48:82:9a:c9:72:29:9c:09:24:63:b3:c2:9f
+ 2a:f6:e8:3a:c4:46:03:8d:70:ae:14:bb:3a:d6:c6:62
+ 93:24:7f:bc:0a:c8:a2:20:53:3c:9f:5c:15:45:05:3d
+ 1b:38:17:d4:fe:6b:6a:c2:16:f3:14:73:c2:c3:c7:36
+ e3:f1:f8:e5:28:84:4e:37:d4:68:e8:82:70:20:53:fc
+ 01:
+
+public exponent:
+ 01:00:01:
+
+private exponent:
+ 44:4a:68:0e:84:2a:52:fd:12:4f:69:3d:2e:38:fe:fe
+ b3:71:de:1c:30:42:d0:63:e5:76:e2:f7:6f:1b:82:2f
+ a9:34:fe:45:85:9f:79:e7:be:59:b5:14:1c:67:9c:fb
+ 94:0b:ac:a5:63:cc:a1:e6:2c:1e:89:69:37:bd:96:7c
+ 0d:5e:73:82:6e:7b:13:42:2d:2b:a2:d5:0a:9d:0a:cd
+ 63:39:51:de:40:f8:16:3d:16:0e:7d:7b:6d:2f:00:e1
+ 0f:b6:e0:f5:d3:02:0e:61:d0:a0:67:7b:85:f8:36:c6
+ 50:a0:3b:65:7e:cd:cd:e6:b2:64:55:97:cf:c9:8c:a9
+ c9:f3:63:b5:08:05:59:8f:b9:c0:18:ad:67:4f:b5:1e
+ 59:b3:0d:82:de:46:14:75:c0:6e:cf:4d:28:5a:93:d7
+ 7a:42:fa:b7:e9:fe:1c:bb:89:88:30:d7:ed:3b:36:28
+ 68:5a:42:e8:87:97:5f:1d:49:e6:cd:d2:b9:a2:b5:23
+ d8:df:5d:cf:c6:98:9a:e0:99:7a:33:52:75:22:ce:ca
+ 85:eb:d9:92:6a:d5:49:c0:cb:df:b1:a2:98:b5:6c:37
+ 85:c2:e1:6a:13:48:22:72:02:a7:e2:e0:f3:f3:0c:ed
+ 42:f6:83:ba:71:f0:ef:8f:ce:6a:59:30:be:9d:5f:23
+ 06:c3:0e:49:5c:8f:6a:8d:c2:c3:c5:07:45:55:78:f0
+ bd:29:01:cb:ac:ec:b1:40:7d:78:cc:4d:cb:f9:60:a4
+ a2:f5:aa:21:0b:3e:da:1b:d9:f0:99:19:44:57:21:09
+ ba:0f:f9:05:8e:ee:59:4f:59:08:b1:67:51:02:80:4e
+ 34:c7:5d:25:79:8c:84:f7:be:15:02:28:9c:f9:b9:ca
+ fc:6a:ed:d0:5f:df:be:ce:c4:96:63:23:2e:db:e1:85
+ 1e:45:16:2c:24:b0:5e:7a:62:bf:36:00:8b:c8:90:61
+ c2:68:4b:95:b0:ce:41:77:a3:a0:5d:09:72:01:a0:01
+
+
+prime1:
+ 00:ca:fe:eb:14:07:13:a9:ef:b6:d5:6c:52:02:39:b6
+ 6e:55:b6:dd:70:fc:c6:04:7c:07:81:9b:98:a4:da:db
+ f1:66:b8:33:91:fd:00:15:6e:72:0d:ab:0b:6f:be:34
+ c8:d9:82:58:7e:09:7f:e4:6f:c0:70:99:53:68:c7:53
+ d2:8f:97:22:f5:e8:e4:be:5f:e1:29:0f:27:a7:66:74
+ b2:cc:96:a1:d2:ca:2a:40:4b:70:cc:7a:16:4f:c9:4f
+ 49:16:11:d5:f8:da:f6:92:06:1c:45:c3:f8:17:c4:1d
+ 65:9a:2a:3f:33:be:33:f8:84:03:26:49:d8:52:25:f8
+ 19:ce:31:00:c0:b6:55:71:74:03:53:e8:0c:ef:85:64
+ 54:d4:8f:68:08:87:da:cb:9b:55:6a:2e:2b:c2:95:36
+ c4:dd:09:62:c0:6f:9e:e9:cc:ea:96:4e:e6:2d:6c:72
+ c1:54:92:11:29:91:af:4d:cd:08:7c:f8:6f:28:9d:ca
+ c5:
+
+prime2:
+ 00:c8:6e:b7:af:c3:c6:b9:df:49:ad:ea:b8:62:b7:43
+ e6:04:d9:5a:df:c3:f9:a3:0d:b5:e6:2d:9d:f7:c4:ff
+ 38:c0:cb:03:9c:c1:d1:6f:b4:fb:cf:81:c4:9e:94:2a
+ d5:e2:a1:77:a8:7b:8f:d1:34:7a:c2:f5:38:ec:0d:35
+ a0:5e:3c:af:e9:2d:f4:f9:32:ae:da:c1:1e:62:74:e5
+ ab:3d:3b:3d:d0:88:fc:53:59:0f:21:30:ed:24:ac:a7
+ 5a:a5:b7:f4:cc:5a:96:ad:79:a3:41:74:56:ad:39:14
+ 0a:27:a4:10:18:19:33:f2:1a:aa:b4:36:9d:fa:3f:fc
+ 71:42:1e:a0:96:8e:0f:de:46:87:ba:6c:38:17:d0:7e
+ c8:4a:cb:4a:29:1c:44:b9:88:29:c7:6f:b7:4f:3f:00
+ cd:0c:6b:0f:77:a1:5a:f1:80:21:91:b3:68:ca:0d:b5
+ c9:6d:04:f4:98:94:9f:09:f9:a8:58:ea:34:9c:d8:f0
+ 0d:
+
+coefficient:
+ 00:bf:7b:93:68:64:ea:5e:b5:f4:b6:8c:91:49:aa:2b
+ b0:a5:74:40:73:45:23:b6:74:ae:7a:55:ae:9d:8a:bb
+ 3b:6d:3a:7d:c4:7a:c0:82:7f:0e:ef:57:1c:86:e2:56
+ 30:5b:0c:d9:d1:52:cf:df:10:4f:c8:4a:75:b1:b8:b1
+ 59:9b:01:02:a2:4d:29:aa:63:e5:11:0a:17:ae:1e:79
+ ed:5d:10:fc:f0:8a:8d:f6:77:f8:78:17:1e:07:ee:d1
+ de:59:ed:d7:fb:94:bf:c9:7c:f7:f3:a0:8d:66:d3:94
+ 9a:7f:d1:7a:89:87:71:17:96:90:4e:be:7b:54:5e:51
+ 03:c6:35:af:5f:ea:5d:cc:31:ab:56:4b:75:6a:14:b6
+ c0:1a:bf:fb:e9:54:ba:ad:c3:52:e9:85:03:db:b2:e8
+ 0b:18:60:37:19:f9:07:87:e7:b2:d8:3a:0d:c9:d5:f0
+ f0:73:60:fc:9e:e0:9e:b1:ea:52:71:c7:fd:27:0a:22
+ 42:
+
+exp1:
+ 04:a5:69:04:00:55:76:e2:41:b1:08:d5:a7:af:62:79
+ 8c:04:af:74:d2:94:45:ae:01:0d:fa:5d:b8:08:3a:58
+ 80:1d:5f:30:cc:35:a0:47:f2:dc:55:39:e1:c8:dc:b4
+ 6c:26:0b:98:76:e7:32:77:4c:54:47:6e:1d:4b:d2:a3
+ 53:1e:06:72:d2:6d:c9:dd:af:ed:9c:a7:2f:b1:ac:a2
+ 1b:04:a7:97:87:81:08:0f:b1:f9:3c:22:1e:99:60:f3
+ 2f:4a:21:37:9c:eb:5f:d4:3b:f9:6c:ce:d4:dc:6c:3f
+ d3:13:7c:76:d6:b7:a1:cc:83:b5:f0:a2:be:de:97:9b
+ 1f:99:07:87:61:a4:fa:ff:c8:c3:b6:df:f4:eb:7c:ac
+ 64:61:13:e4:7e:17:87:e9:7a:3e:ff:e7:88:80:99:cc
+ 4f:b2:d3:4b:cc:42:3a:df:b1:ce:d6:e7:75:ca:b1:a5
+ b3:25:d4:b6:ba:da:e4:50:f9:0c:c3:32:e8:1f:14:71
+
+
+exp2:
+ 00:9d:9d:bb:a3:63:b0:96:20:8a:5f:52:f2:b6:e6:69
+ 65:ac:30:84:ee:ec:bd:16:45:44:e3:02:c2:73:c2:9d
+ f5:b2:5e:b3:b3:85:13:3e:e6:33:13:66:78:09:40:79
+ 43:03:5a:78:af:ac:a3:57:20:0e:dd:db:5b:6c:fd:a2
+ 4b:3f:70:37:e1:85:fb:5c:30:48:22:cc:b5:29:35:c6
+ 1a:58:27:8b:1f:bf:69:b5:dd:96:31:42:b9:6a:1a:bf
+ ec:5f:df:7e:89:69:3f:8e:a1:d6:09:36:04:a7:69:f8
+ 61:57:f5:03:96:ff:d2:c2:b3:c7:c3:ba:23:97:54:d4
+ 1e:f8:a0:ff:26:06:07:62:83:52:5e:fe:95:49:dc:f2
+ a6:6d:72:da:19:e4:1a:03:50:99:92:35:3f:10:f9:79
+ 96:c6:0a:36:fa:9b:8f:d9:d4:2a:11:da:e5:2f:e7:82
+ 2a:29:2a:39:72:f7:84:ed:a2:3f:89:d4:7b:95:50:5f
+ cd:
+
+
+Public Key PIN:
+ pin-sha256:u7TPTyh/innOijbJFG3Y4pWghApErLvhCQUZNXBlVFU=
+Public Key ID:
+ sha256:bbb4cf4f287f8a79ce8a36c9146dd8e295a0840a44acbbe10905193570655455
+ sha1:92b7d0c4d107e2a73f827b87866aef9ff4379cc8
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIG5AIBAAKCAYEAnu7y2EGuLJOKAR+IW9aFKS+RnTf8NYh/U3GH/Bdx5xVXBi1U
++xmYgILsGpktV8tf3Sgm2JX7ZbK+4RGGaRR+MlzAAgtdEXhpUCAlPhX7ikbRg/k7
+hEacaSFE1QkdegTM82rqTBvafEDdHG/2hbTqdZg0eRH7z9MYcGQlM4oxtpNn1DJn
+YRzQe4VhVMb7UbaHHdS4WECpxTLO4LmQNw1Y5DNwxcWR8hj14AitF4vPcvEmbJyI
+0p4GTAJdTnyTr41yk3UdYAz3NAmo5vKAShSBJEBLRRmFLq2Ol0z/7NCf5qC3wKmg
+rdICLRNV89/4+fHzPjXpCCvbEZNXE1XGusbX/33h+oxHXdq/MVaAqjSXQ7ue/9Pm
+E6nCmUnCHtryx9LW919wNpEv6jbpiEQIoxoKwOBLSIKayXIpnAkkY7PCnyr26DrE
+RgONcK4UuzrWxmKTJH+8CsiiIFM8n1wVRQU9GzgX1P5rasIW8xRzwsPHNuPx+OUo
+hE431GjognAgU/wBAgMBAAECggGAREpoDoQqUv0ST2k9Ljj+/rNx3hwwQtBj5Xbi
+928bgi+pNP5FhZ95575ZtRQcZ5z7lAuspWPMoeYsHolpN72WfA1ec4JuexNCLSui
+1QqdCs1jOVHeQPgWPRYOfXttLwDhD7bg9dMCDmHQoGd7hfg2xlCgO2V+zc3msmRV
+l8/JjKnJ82O1CAVZj7nAGK1nT7UeWbMNgt5GFHXAbs9NKFqT13pC+rfp/hy7iYgw
+1+07NihoWkLoh5dfHUnmzdK5orUj2N9dz8aYmuCZejNSdSLOyoXr2ZJq1UnAy9+x
+opi1bDeFwuFqE0gicgKn4uDz8wztQvaDunHw74/Oalkwvp1fIwbDDklcj2qNwsPF
+B0VVePC9KQHLrOyxQH14zE3L+WCkovWqIQs+2hvZ8JkZRFchCboP+QWO7llPWQix
+Z1ECgE40x10leYyE974VAiic+bnK/Grt0F/fvs7ElmMjLtvhhR5FFiwksF56Yr82
+AIvIkGHCaEuVsM5Bd6OgXQlyAaABAoHBAMr+6xQHE6nvttVsUgI5tm5Vtt1w/MYE
+fAeBm5ik2tvxZrgzkf0AFW5yDasLb740yNmCWH4Jf+RvwHCZU2jHU9KPlyL16OS+
+X+EpDyenZnSyzJah0soqQEtwzHoWT8lPSRYR1fja9pIGHEXD+BfEHWWaKj8zvjP4
+hAMmSdhSJfgZzjEAwLZVcXQDU+gM74VkVNSPaAiH2subVWouK8KVNsTdCWLAb57p
+zOqWTuYtbHLBVJIRKZGvTc0IfPhvKJ3KxQKBwQDIbrevw8a530mt6rhit0PmBNla
+38P5ow215i2d98T/OMDLA5zB0W+0+8+BxJ6UKtXioXeoe4/RNHrC9TjsDTWgXjyv
+6S30+TKu2sEeYnTlqz07PdCI/FNZDyEw7SSsp1qlt/TMWpateaNBdFatORQKJ6QQ
+GBkz8hqqtDad+j/8cUIeoJaOD95Gh7psOBfQfshKy0opHES5iCnHb7dPPwDNDGsP
+d6Fa8YAhkbNoyg21yW0E9JiUnwn5qFjqNJzY8A0CgcAEpWkEAFV24kGxCNWnr2J5
+jASvdNKURa4BDfpduAg6WIAdXzDMNaBH8txVOeHI3LRsJguYducyd0xUR24dS9Kj
+Ux4GctJtyd2v7ZynL7GsohsEp5eHgQgPsfk8Ih6ZYPMvSiE3nOtf1Dv5bM7U3Gw/
+0xN8dta3ocyDtfCivt6Xmx+ZB4dhpPr/yMO23/TrfKxkYRPkfheH6Xo+/+eIgJnM
+T7LTS8xCOt+xztbndcqxpbMl1La62uRQ+QzDMugfFHECgcEAnZ27o2OwliCKX1Ly
+tuZpZawwhO7svRZFROMCwnPCnfWyXrOzhRM+5jMTZngJQHlDA1p4r6yjVyAO3dtb
+bP2iSz9wN+GF+1wwSCLMtSk1xhpYJ4sfv2m13ZYxQrlqGr/sX99+iWk/jqHWCTYE
+p2n4YVf1A5b/0sKzx8O6I5dU1B74oP8mBgdig1Je/pVJ3PKmbXLaGeQaA1CZkjU/
+EPl5lsYKNvqbj9nUKhHa5S/ngiopKjly94Ttoj+J1HuVUF/NAoHBAL97k2hk6l61
+9LaMkUmqK7CldEBzRSO2dK56Va6dirs7bTp9xHrAgn8O71cchuJWMFsM2dFSz98Q
+T8hKdbG4sVmbAQKiTSmqY+URCheuHnntXRD88IqN9nf4eBceB+7R3lnt1/uUv8l8
+9/OgjWbTlJp/0XqJh3EXlpBOvntUXlEDxjWvX+pdzDGrVkt1ahS2wBq/++lUuq3D
+UumFA9uy6AsYYDcZ+QeH57LYOg3J1fDwc2D8nuCesepSccf9JwoiQg==
+-----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..e3c3ca2
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/ca.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEGTCCAoGgAwIBAgIUXWsAXOOaZw+h37N9gUc/XLw3KHwwDQYJKoZIhvcNAQEL
+BQAwIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZlciB0ZXN0aW5nIENBMCAXDTIwMDEw
+NzA5MzQwOVoYDzk5OTkxMjMxMjM1OTU5WjAjMSEwHwYDVQQDExhLbm90IFJlc29s
+dmVyIHRlc3RpbmcgQ0EwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCe
+7vLYQa4sk4oBH4hb1oUpL5GdN/w1iH9TcYf8F3HnFVcGLVT7GZiAguwamS1Xy1/d
+KCbYlftlsr7hEYZpFH4yXMACC10ReGlQICU+FfuKRtGD+TuERpxpIUTVCR16BMzz
+aupMG9p8QN0cb/aFtOp1mDR5EfvP0xhwZCUzijG2k2fUMmdhHNB7hWFUxvtRtocd
+1LhYQKnFMs7guZA3DVjkM3DFxZHyGPXgCK0Xi89y8SZsnIjSngZMAl1OfJOvjXKT
+dR1gDPc0Cajm8oBKFIEkQEtFGYUurY6XTP/s0J/moLfAqaCt0gItE1Xz3/j58fM+
+NekIK9sRk1cTVca6xtf/feH6jEdd2r8xVoCqNJdDu57/0+YTqcKZScIe2vLH0tb3
+X3A2kS/qNumIRAijGgrA4EtIgprJcimcCSRjs8KfKvboOsRGA41wrhS7OtbGYpMk
+f7wKyKIgUzyfXBVFBT0bOBfU/mtqwhbzFHPCw8c24/H45SiETjfUaOiCcCBT/AEC
+AwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwQAMB0GA1Ud
+DgQWBBSSt9DE0Qfipz+Ce4eGau+f9DecyDANBgkqhkiG9w0BAQsFAAOCAYEAA45p
+Ak7ebzk2ss5FHhJDvHrhoTZG2/esNEQhtv70nuRPnm3j8UGDxYyydwjf4+W9DT1v
+53QNfvbOIPcOUsGArAItmI7K6ltkBSS6ymO8T1ZY4vYw+77jJZ1EeYS6kjdan7dK
+f2Zz23CVbuq8BOc/Ob6ChepEq/MIb3g/Y6FowuWqeC85s61GW9MKr5GeG0oSyYAO
+UZdFnwa8QLCZ2IzQcwnolkAw2A/5TDxovINy9Lb5U3kyphC9vhjPqr8PJ5q/KVuK
+vcHvEsrsSNPvW/WcxkziV1oJTnjvr/69mwAme8+xjjF90GhrNaQF1YOoijuZuQaS
+Q+0qmwZbsMtcqAABKQALHfLGsGAA5MKip49khIQWuIAS8P2vb+hzbqQRLjq1uW7B
+dEGvBHF0QebDZOXJeXEYK/b7btWa9kNedD2FvBx5c9QNiWwh7jZENkICKnhI7E+n
+d5/gsKVa1glKwbMagZBSJgFtjZe/eo/LcoK82m4VuOUCJSe0Kd0McrSZ7XZX
+-----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..ed801af
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/ca.tmpl
@@ -0,0 +1,4 @@
+cn = Knot Resolver testing CA
+ca
+cert_signing_key
+expiration_days = -1
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..7251f12
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/gen.sh
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# 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..9eaef8a
--- /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:c3:46:2a:27:c8:39:e4:de:fa:24:45:6c:00:26:80
+ 61:ca:dd:a1:24:34:1b:93:1c:13:c8:5a:cf:af:6a:ef
+ 34:b9:89:83:02:76:51:ad:67:bf:ed:39:ee:0a:15:57
+ 91:6e:fa:68:60:78:22:62:fa:0a:55:12:03:b3:0c:8e
+ b4:ca:cd:2b:9d:a2:43:b5:5a:48:a0:3d:4a:1f:77:a4
+ a6:d4:87:eb:79:99:df:d4:b4:a3:cf:91:03:a0:c5:82
+ 39:f5:75:20:4b:90:b9:3b:72:65:a7:75:39:a6:62:58
+ 65:b0:9c:40:5c:c7:c4:4c:d3:1e:cc:74:18:74:15:23
+ 44:fd:51:59:b2:b7:70:95:6b:a0:be:d5:e4:72:59:2b
+ df:a5:a2:06:c8:e1:bd:17:80:25:b3:cf:8e:e8:ad:b2
+ f7:04:b1:9e:b8:72:0b:c5:dc:cd:a5:b6:f5:c9:1a:eb
+ 63:78:75:9c:5d:c5:03:a9:4b:7b:d6:cd:5c:5f:8d:2e
+ d0:b4:0d:96:55:c7:1e:c7:ac:13:46:b8:ec:9c:36:b9
+ 6a:1d:f0:7c:41:00:c6:bd:1f:81:7e:1d:48:1d:59:bc
+ e2:61:a6:d1:2d:52:10:3f:63:93:a9:14:d8:03:27:21
+ b0:d4:07:24:78:04:2b:86:c8:2b:0f:eb:a7:b3:3e:e2
+ 81:62:2a:4a:07:d9:fd:f6:77:7f:50:88:ee:bb:7d:31
+ 53:a8:97:bf:30:07:37:41:e9:52:16:15:74:a8:64:ed
+ 93:46:38:56:b4:89:d9:0c:62:4b:64:a9:64:ff:fc:9a
+ d6:19:a7:84:98:28:04:b4:95:76:ac:4a:42:6a:fb:67
+ 5b:b4:37:e6:e6:e2:52:d3:e9:38:8b:76:10:55:f1:e6
+ 8e:d8:73:eb:17:d1:54:41:d4:5b:76:2d:70:7f:f5:0d
+ 7d:d2:d6:f8:05:33:18:ab:dd:10:8a:5b:21:ee:3d:78
+ 9d:cd:c9:c0:c6:98:4e:a6:0a:41:f0:97:91:83:c2:c8
+ 4b:
+
+public exponent:
+ 01:00:01:
+
+private exponent:
+ 23:88:1f:e1:8f:40:61:91:e5:28:36:6d:99:75:68:04
+ e3:5a:02:99:48:d5:ff:a5:ab:3f:d8:ae:53:b7:fc:80
+ b6:85:fc:0d:b5:a3:d5:0e:bc:d0:98:aa:e4:b0:cf:77
+ 4a:1f:4c:60:c9:5b:50:71:38:f2:13:ce:12:85:65:6e
+ 26:3c:c1:03:f4:e3:a7:1b:1f:7d:f0:c9:0d:02:c3:36
+ 0c:14:13:57:d4:14:f3:6a:4f:28:54:b5:b9:4a:57:10
+ de:c6:0a:33:55:c9:2e:b4:f9:24:48:63:4e:10:35:0f
+ 83:dc:5a:a5:c7:3f:c3:ce:e2:9a:c7:41:2f:d6:2c:cd
+ bf:de:4e:99:03:61:fb:fe:52:88:86:f9:03:89:90:3f
+ 28:af:5b:d6:af:a4:ad:a3:06:b9:3a:3a:41:c2:61:7f
+ 2b:1e:7a:c8:0b:10:73:57:63:20:15:33:91:fd:50:f9
+ 8f:90:ae:fc:2c:fe:26:8e:f2:a0:ba:4b:65:a3:95:f1
+ d8:30:d4:fa:8d:12:1b:8b:58:1d:66:10:cd:41:22:1e
+ b0:7a:f4:e6:0f:76:3f:0a:0f:9c:44:e2:19:cf:c6:4d
+ de:3a:f4:96:70:c7:e6:2d:98:27:0e:ac:3b:32:41:37
+ 4e:05:b2:22:af:7b:38:92:16:40:fb:5c:96:b0:86:da
+ 96:c6:77:c3:66:78:07:80:5c:2a:46:dc:9a:bf:fc:0c
+ 2f:ee:f7:a1:b3:77:b4:50:75:a2:b7:36:9d:28:73:ee
+ 7a:ab:a6:0c:f6:92:18:8f:ff:16:28:90:7f:16:4f:f1
+ 6d:77:99:dd:a6:46:95:6a:6c:7a:15:48:53:b3:17:0b
+ 30:aa:0d:c8:68:33:2b:4d:40:da:74:cf:9b:73:1b:cd
+ 5d:f0:a9:d1:00:6f:db:de:55:ec:d4:24:96:bb:da:50
+ b4:d5:e1:87:35:5c:d4:50:c3:03:d5:d5:ee:03:65:4b
+ 68:9c:07:5c:59:28:78:bd:d1:4b:cb:8d:85:8b:5b:c1
+
+
+prime1:
+ 00:cc:8d:55:38:2d:57:cb:d2:4b:57:5b:3f:a2:6d:91
+ 4b:9c:54:29:98:9d:1d:bb:36:a6:e8:ba:e9:50:db:83
+ cf:c1:45:24:16:70:e5:51:40:eb:23:6b:fa:be:d5:d5
+ 00:27:ed:99:c7:7c:6b:16:79:77:0b:f3:ff:58:35:4e
+ 6c:58:68:51:d3:20:3c:57:b7:7d:bc:6b:fd:a7:c3:38
+ 9a:f2:7a:8a:b6:71:a1:6e:5e:64:7e:a8:c5:7c:58:70
+ fb:8d:63:b3:27:cc:1a:97:1e:04:da:d5:34:b5:d1:aa
+ f1:96:39:89:5e:cb:e7:75:ab:7e:ac:8c:fe:62:3e:cc
+ 93:66:88:d7:cd:c6:2d:db:9e:2f:f7:d1:6e:96:99:d2
+ 32:61:f4:9b:f5:48:fe:e7:90:b7:a2:ab:89:90:c1:ae
+ 67:5d:18:7a:c1:a3:84:97:09:47:13:df:d2:85:46:46
+ c7:77:3b:9f:b5:74:5c:f6:ec:a0:a7:66:0e:d1:d7:a4
+ e1:
+
+prime2:
+ 00:f4:63:70:fa:dd:7e:3d:1a:2b:5b:47:79:56:e9:c7
+ 7f:6b:50:41:60:45:af:59:e0:77:b2:76:4e:40:ff:f8
+ 55:9d:77:3b:c1:00:6a:c6:84:6a:09:a5:45:e6:fc:e6
+ e6:92:72:32:fc:93:8f:93:d9:db:fc:8c:43:d2:7a:ea
+ 4b:0e:ee:1d:dc:e0:27:08:83:16:aa:de:37:59:39:c0
+ 21:26:b5:34:49:f2:1f:7b:0c:d4:3c:0f:e5:06:ac:23
+ 7b:85:b1:39:35:44:ec:70:48:c5:10:86:02:ea:36:4a
+ f1:20:a2:b2:c9:8d:d3:f6:5a:86:72:4b:8b:28:07:04
+ 39:8d:01:fa:75:3a:35:40:c2:21:c3:ac:50:da:2f:3e
+ 30:ee:ab:f7:7d:81:a3:77:5e:b7:03:be:52:fb:a4:70
+ 92:5d:fd:09:ae:52:33:b8:7b:9c:e2:2a:77:f7:23:4d
+ c5:4b:82:f1:fb:0a:09:62:e6:5f:32:1e:7b:c7:c6:66
+ ab:
+
+coefficient:
+ 00:c3:2d:d8:18:32:30:a1:fa:2c:23:d0:ea:b4:60:0f
+ 29:67:50:4a:5a:61:aa:6d:15:0e:4b:66:43:35:ee:39
+ 4c:e7:8d:31:73:b0:bb:04:4d:e5:bc:28:ea:dc:77:81
+ 35:bb:f7:80:13:96:04:4c:45:9c:43:6f:64:e0:a3:51
+ 4b:7e:6c:b6:7d:c2:a6:e0:94:e6:6c:34:4e:62:71:ea
+ c0:c0:ab:30:30:c1:3a:39:0e:cd:f0:cc:0e:31:b5:fc
+ 61:64:1e:29:1c:cd:fc:69:c0:02:7c:2a:fe:86:d5:e2
+ 7b:8d:fe:ae:3d:3a:6c:1b:b3:b4:0d:b1:1b:d1:4d:37
+ 36:ea:d7:15:f3:6e:02:b1:86:98:51:02:fc:62:df:30
+ f2:de:9f:03:6d:27:45:d7:c7:a8:04:ba:76:18:01:09
+ 34:d2:57:f9:10:50:ea:ae:0e:ae:c6:a4:cd:f9:fd:b1
+ 25:b2:45:20:bc:50:2d:9b:80:c1:39:08:97:d2:75:9b
+ f7:
+
+exp1:
+ 00:8a:b8:6a:8b:cf:8c:54:08:c8:d9:74:63:82:67:25
+ fb:0e:08:b1:b0:f3:14:7f:ab:3f:a4:63:65:e9:55:05
+ 5e:36:a7:0a:23:41:ea:f2:a0:c1:16:63:9b:48:22:41
+ f2:7a:21:93:81:8a:ea:20:f2:bc:fe:59:39:d8:fb:45
+ b5:0a:7b:ac:ca:2e:79:5d:cf:6d:b1:03:d7:a1:17:2e
+ e3:3e:00:46:e4:15:c9:b1:cc:c8:00:71:ba:84:6a:82
+ 2c:c6:a5:4f:91:74:c4:af:a9:47:07:95:41:ca:f0:67
+ 2a:b1:83:51:9a:fd:53:7a:24:94:a2:b6:77:a9:ef:06
+ d4:0b:dc:4f:e6:18:39:6f:50:27:1d:bc:65:70:32:df
+ 2f:15:e9:4a:7f:1d:42:e9:8d:e6:4b:a6:63:83:cd:25
+ d6:a9:76:f9:81:2a:c0:b7:a1:2e:17:d7:59:b0:d2:89
+ 1b:aa:cb:bf:b2:d2:38:5d:a8:fa:06:ac:9a:ee:4a:7d
+ 61:
+
+exp2:
+ 00:92:66:af:db:d8:ac:33:36:66:1a:bc:6a:78:22:7c
+ 1c:5c:d1:2b:18:dd:25:fa:95:79:9f:33:38:15:c0:41
+ a8:28:38:b1:57:21:44:d5:bf:a5:36:3a:07:f2:24:36
+ be:91:a4:4f:de:f7:16:df:df:76:e5:87:b1:69:79:b0
+ b9:5e:2c:4f:3f:6e:18:74:04:f3:a3:50:93:9f:a3:f4
+ f0:e7:1b:4e:43:ae:04:25:d6:bd:9d:6d:78:29:d3:1d
+ 3e:76:0c:80:d4:e4:81:2f:92:a8:5b:09:ac:dd:59:c0
+ f3:4a:35:ad:1d:09:15:9d:53:05:8f:9a:a9:b6:44:dd
+ c7:0c:2d:cf:38:42:b2:7c:24:cf:cd:44:80:fa:f3:aa
+ 31:ee:08:9e:ae:54:e6:f4:2f:8d:3b:74:dc:89:5b:2d
+ 04:c1:c1:3f:f7:69:cf:0a:09:23:26:69:82:8c:4e:5d
+ dc:7f:2b:e6:82:18:b5:1e:c6:1a:e9:0f:51:df:8f:7f
+ 19:
+
+
+Public Key PIN:
+ pin-sha256:pFSHHHovr50DJ04K3wEJcyxth+nszZdClOet/CRN9cU=
+Public Key ID:
+ sha256:a454871c7a2faf9d03274e0adf0109732c6d87e9eccd974294e7adfc244df5c5
+ sha1:5137ef343399ccf38d6566803ddce123da640553
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIG5QIBAAKCAYEAw0YqJ8g55N76JEVsACaAYcrdoSQ0G5McE8haz69q7zS5iYMC
+dlGtZ7/tOe4KFVeRbvpoYHgiYvoKVRIDswyOtMrNK52iQ7VaSKA9Sh93pKbUh+t5
+md/UtKPPkQOgxYI59XUgS5C5O3Jlp3U5pmJYZbCcQFzHxEzTHsx0GHQVI0T9UVmy
+t3CVa6C+1eRyWSvfpaIGyOG9F4Als8+O6K2y9wSxnrhyC8XczaW29cka62N4dZxd
+xQOpS3vWzVxfjS7QtA2WVccex6wTRrjsnDa5ah3wfEEAxr0fgX4dSB1ZvOJhptEt
+UhA/Y5OpFNgDJyGw1AckeAQrhsgrD+unsz7igWIqSgfZ/fZ3f1CI7rt9MVOol78w
+BzdB6VIWFXSoZO2TRjhWtInZDGJLZKlk//ya1hmnhJgoBLSVdqxKQmr7Z1u0N+bm
+4lLT6TiLdhBV8eaO2HPrF9FUQdRbdi1wf/UNfdLW+AUzGKvdEIpbIe49eJ3NycDG
+mE6mCkHwl5GDwshLAgMBAAECggGAI4gf4Y9AYZHlKDZtmXVoBONaAplI1f+lqz/Y
+rlO3/IC2hfwNtaPVDrzQmKrksM93Sh9MYMlbUHE48hPOEoVlbiY8wQP046cbH33w
+yQ0CwzYMFBNX1BTzak8oVLW5SlcQ3sYKM1XJLrT5JEhjThA1D4PcWqXHP8PO4prH
+QS/WLM2/3k6ZA2H7/lKIhvkDiZA/KK9b1q+kraMGuTo6QcJhfyseesgLEHNXYyAV
+M5H9UPmPkK78LP4mjvKguktlo5Xx2DDU+o0SG4tYHWYQzUEiHrB69OYPdj8KD5xE
+4hnPxk3eOvSWcMfmLZgnDqw7MkE3TgWyIq97OJIWQPtclrCG2pbGd8NmeAeAXCpG
+3Jq//Awv7vehs3e0UHWitzadKHPuequmDPaSGI//FiiQfxZP8W13md2mRpVqbHoV
+SFOzFwswqg3IaDMrTUDadM+bcxvNXfCp0QBv295V7NQklrvaULTV4Yc1XNRQwwPV
+1e4DZUtonAdcWSh4vdFLy42Fi1vBAoHBAMyNVTgtV8vSS1dbP6JtkUucVCmYnR27
+NqbouulQ24PPwUUkFnDlUUDrI2v6vtXVACftmcd8axZ5dwvz/1g1TmxYaFHTIDxX
+t328a/2nwzia8nqKtnGhbl5kfqjFfFhw+41jsyfMGpceBNrVNLXRqvGWOYley+d1
+q36sjP5iPsyTZojXzcYt254v99FulpnSMmH0m/VI/ueQt6KriZDBrmddGHrBo4SX
+CUcT39KFRkbHdzuftXRc9uygp2YO0dek4QKBwQD0Y3D63X49GitbR3lW6cd/a1BB
+YEWvWeB3snZOQP/4VZ13O8EAasaEagmlReb85uaScjL8k4+T2dv8jEPSeupLDu4d
+3OAnCIMWqt43WTnAISa1NEnyH3sM1DwP5QasI3uFsTk1ROxwSMUQhgLqNkrxIKKy
+yY3T9lqGckuLKAcEOY0B+nU6NUDCIcOsUNovPjDuq/d9gaN3XrcDvlL7pHCSXf0J
+rlIzuHuc4ip39yNNxUuC8fsKCWLmXzIee8fGZqsCgcEAirhqi8+MVAjI2XRjgmcl
++w4IsbDzFH+rP6RjZelVBV42pwojQeryoMEWY5tIIkHyeiGTgYrqIPK8/lk52PtF
+tQp7rMoueV3PbbED16EXLuM+AEbkFcmxzMgAcbqEaoIsxqVPkXTEr6lHB5VByvBn
+KrGDUZr9U3oklKK2d6nvBtQL3E/mGDlvUCcdvGVwMt8vFelKfx1C6Y3mS6Zjg80l
+1ql2+YEqwLehLhfXWbDSiRuqy7+y0jhdqPoGrJruSn1hAoHBAJJmr9vYrDM2Zhq8
+angifBxc0SsY3SX6lXmfMzgVwEGoKDixVyFE1b+lNjoH8iQ2vpGkT973Ft/fduWH
+sWl5sLleLE8/bhh0BPOjUJOfo/Tw5xtOQ64EJda9nW14KdMdPnYMgNTkgS+SqFsJ
+rN1ZwPNKNa0dCRWdUwWPmqm2RN3HDC3POEKyfCTPzUSA+vOqMe4Inq5U5vQvjTt0
+3IlbLQTBwT/3ac8KCSMmaYKMTl3cfyvmghi1HsYa6Q9R349/GQKBwQDDLdgYMjCh
++iwj0Oq0YA8pZ1BKWmGqbRUOS2ZDNe45TOeNMXOwuwRN5bwo6tx3gTW794ATlgRM
+RZxDb2Tgo1FLfmy2fcKm4JTmbDROYnHqwMCrMDDBOjkOzfDMDjG1/GFkHikczfxp
+wAJ8Kv6G1eJ7jf6uPTpsG7O0DbEb0U03NurXFfNuArGGmFEC/GLfMPLenwNtJ0XX
+x6gEunYYAQk00lf5EFDqrg6uxqTN+f2xJbJFILxQLZuAwTkIl9J1m/c=
+-----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..b42f07e
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/server.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEfTCCAuWgAwIBAgIUIREQSLx52Sc9PFWI6Nwe3YzRp3MwDQYJKoZIhvcNAQEL
+BQAwIzEhMB8GA1UEAxMYS25vdCBSZXNvbHZlciB0ZXN0aW5nIENBMCAXDTIwMDEw
+NzA5MzQwOVoYDzk5OTkxMjMxMjM1OTU5WjA8MRIwEAYDVQQDEwlsb2NhbGhvc3Qx
+JjAkBgNVBAoTHUZha2UgRE5TIHJvb3Qgb3JnIHRlc3Qgc2VydmVyMIIBojANBgkq
+hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAw0YqJ8g55N76JEVsACaAYcrdoSQ0G5Mc
+E8haz69q7zS5iYMCdlGtZ7/tOe4KFVeRbvpoYHgiYvoKVRIDswyOtMrNK52iQ7Va
+SKA9Sh93pKbUh+t5md/UtKPPkQOgxYI59XUgS5C5O3Jlp3U5pmJYZbCcQFzHxEzT
+Hsx0GHQVI0T9UVmyt3CVa6C+1eRyWSvfpaIGyOG9F4Als8+O6K2y9wSxnrhyC8Xc
+zaW29cka62N4dZxdxQOpS3vWzVxfjS7QtA2WVccex6wTRrjsnDa5ah3wfEEAxr0f
+gX4dSB1ZvOJhptEtUhA/Y5OpFNgDJyGw1AckeAQrhsgrD+unsz7igWIqSgfZ/fZ3
+f1CI7rt9MVOol78wBzdB6VIWFXSoZO2TRjhWtInZDGJLZKlk//ya1hmnhJgoBLSV
+dqxKQmr7Z1u0N+bm4lLT6TiLdhBV8eaO2HPrF9FUQdRbdi1wf/UNfdLW+AUzGKvd
+EIpbIe49eJ3NycDGmE6mCkHwl5GDwshLAgMBAAGjgY0wgYowDAYDVR0TAQH/BAIw
+ADAUBgNVHREEDTALgglsb2NhbGhvc3QwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYD
+VR0PAQH/BAUDAwegADAdBgNVHQ4EFgQUUTfvNDOZzPONZWaAPdzhI9pkBVMwHwYD
+VR0jBBgwFoAUkrfQxNEH4qc/gnuHhmrvn/Q3nMgwDQYJKoZIhvcNAQELBQADggGB
+AFfFgv5J1eb8h33tnvDJ/dLBSA7Soz1NXK8iha18CH1uxW2lo+6iJl7g191WD/3W
+m/LdpRU3h5ewbD6An3FSA0I25cYQD1vlH7vdI+xu3hIuFhQVnkxGbwISzlM5vat8
+1Ry7z/RpHmQA4V4z4R/PuYcQHQG5tINMPySmbfHBK/Ju+nnmSTJ/p3Z7sVaSCfNN
+l37me0w197QU3ovNtA61xHa77VUSJeaAC+zOOXUBZ8Rc5PqhYOf6AJbIBk7tPNei
+XH5Yyg3UT0i7V09vUViXK8EXbMX1VWsw59Et4Ro1YouS6TN34i2w8FtKg1+amQLr
+UXmQW1lkzx23FdGG4T0fFPtWuJCL6ioc0J6vS7xt0xkbrri9U2thC7gvrKLGCJ6J
+hWTGoKwjcBHpoLsT62XogHlctagkyXfjJ1Piik7k2JjqvmyteFlDDkOToLQmaCuI
+LIBmOnO9mEig3y7T72cnL8QM+nb+c70cssfCW3LBTHb893J4QAOt5RN6LQUlFc49
+sQ==
+-----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..8021616
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/server.tmpl
@@ -0,0 +1,7 @@
+organization = Fake DNS root org test server
+cn = localhost
+tls_www_server
+encryption_key
+signing_key
+dns_name = localhost
+expiration_days = -1
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..1ddc1ad
--- /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:bb:d7:47:1f:55:ed:c0:08:af:1d:32:d2:69:ef:77
+ d2:f3:f6:86:7e:f3:97:e2:35:72:d4:0a:87:1e:75:76
+ bf:59:29:be:cd:e6:ad:6d:7d:62:47:19:fb:ed:24:94
+ 7f:2b:d6:0c:68:cf:cd:ee:f3:5e:b2:db:11:44:4b:7f
+ 30:ce:d2:a7:75:a7:37:83:c0:41:d6:a1:87:22:48:fa
+ ef:d1:15:ed:c9:d2:73:ab:e1:7c:94:4d:b2:96:80:cf
+ 5a:5c:7e:96:f6:02:fa:a4:8b:b1:05:b0:27:f5:d7:38
+ bd:20:37:ed:12:c0:22:07:a9:a6:5e:47:bd:1d:33:27
+ a2:cd:4c:0c:70:ba:6e:d9:13:6f:7b:a1:72:e8:f4:be
+ e3:86:1b:a2:b3:a1:07:cf:93:e8:3a:26:51:3e:af:bc
+ da:80:b1:92:56:8b:21:e7:1d:d9:f9:0c:a9:68:b7:04
+ d8:6d:1f:6f:98:90:fb:fb:35:18:71:3c:50:73:b1:45
+ b1:e7:ee:7b:84:5d:57:95:33:37:b0:0f:eb:85:8f:8d
+ b0:7f:10:17:80:03:99:1b:62:0c:1d:72:6f:e5:77:38
+ c8:75:96:61:36:4b:28:ae:17:a4:f9:81:90:4d:4b:85
+ 61:39:be:6c:ca:c0:a9:cd:4e:45:27:47:84:82:3d:7f
+ c6:a7:00:d7:90:64:7c:a5:e9:f8:f6:92:d2:72:54:a7
+ 95:5f:fc:93:1d:c9:1a:78:6e:3a:1a:1f:8f:a2:41:d2
+ 04:5c:19:32:54:16:f2:97:6f:7c:f9:24:d7:a6:e2:07
+ cf:9f:9e:64:27:81:5f:5a:77:65:4f:7b:b2:81:78:3f
+ a3:22:17:d3:ba:06:71:d5:09:6a:c2:85:ba:35:f7:71
+ 01:b4:63:c7:70:62:98:58:80:a2:40:27:c0:e2:d5:fd
+ 60:e0:5a:7a:9c:bf:7b:e6:34:78:f1:16:e8:28:d9:92
+ dc:e6:2e:b6:d7:1a:83:4b:86:92:d6:81:ce:8e:50:0a
+ d5:
+
+public exponent:
+ 01:00:01:
+
+private exponent:
+ 7a:27:5e:66:1f:60:54:60:91:58:80:a3:5b:26:d2:9a
+ 89:f2:88:b6:68:3d:1e:6b:39:b8:70:fc:3b:af:91:c0
+ 90:00:58:c7:d7:ba:72:98:76:5f:dc:a2:fb:2d:ad:b0
+ 21:d6:ba:0d:33:0e:2d:d5:70:81:09:7b:6a:19:5a:a6
+ 67:e9:8f:e3:30:12:27:08:d1:07:fd:d5:3e:53:8d:74
+ 85:59:28:60:f6:0e:28:f9:a3:25:62:7d:bf:e8:16:70
+ 21:f4:64:c1:a9:60:4b:bf:58:28:65:cd:26:cf:86:63
+ 5f:5f:5f:39:b1:5e:af:f3:00:71:11:60:07:6c:2b:db
+ 70:7c:83:1e:8f:ee:e4:16:02:8a:b8:8c:5c:b8:44:a6
+ fb:a0:5f:27:47:92:27:c8:7c:dd:cb:eb:4b:c3:c7:21
+ a5:4d:54:e8:18:e4:bc:42:aa:6c:8e:72:60:d9:9c:3a
+ 0e:84:c1:f2:ca:5e:43:97:dc:c4:4e:bf:d6:ec:b2:70
+ 08:41:13:01:48:bc:36:a2:eb:5e:67:b6:6a:a4:b6:4a
+ 24:fa:fd:6d:ef:5b:77:bc:0c:7d:95:9a:84:ec:3f:97
+ aa:7c:07:76:80:f5:3a:49:f4:99:ee:cf:17:12:83:e8
+ db:ef:22:60:67:62:f8:3e:f9:bc:18:2b:84:fc:a9:82
+ 95:8d:91:27:8e:ba:87:15:65:1e:9f:b3:95:5f:dc:40
+ 2f:15:eb:7e:0a:d7:69:80:7b:8a:e2:29:89:3a:2e:eb
+ a9:05:c1:1e:5d:23:0d:a0:d7:c4:95:4d:09:85:8c:af
+ 90:23:36:04:66:a9:16:d7:d4:e2:aa:5a:6d:44:5a:6c
+ c8:e8:a0:08:fa:de:19:20:5f:e3:06:17:e5:65:c6:55
+ ef:0f:0d:ff:3e:1c:c5:98:ee:34:d3:07:81:11:fe:e9
+ 15:87:e6:9a:76:44:bd:cb:a0:38:63:9a:af:d1:7c:a7
+ db:26:e2:cd:4a:a2:8a:7f:b8:dc:7a:55:00:4c:20:c1
+
+
+prime1:
+ 00:c9:f5:14:59:49:3b:95:1f:15:b0:0c:83:cb:f4:6a
+ 48:60:2a:af:8b:d5:83:16:aa:71:5a:af:11:63:c6:c1
+ 0a:91:af:5b:bd:6e:9c:cb:d7:eb:bf:c7:31:9f:22:46
+ 01:cf:3b:3c:cb:ba:7d:ad:e5:bb:d8:7c:d2:5d:52:20
+ 14:ea:70:08:9e:29:98:31:20:78:9e:b6:3e:90:e8:ef
+ c8:2f:45:d4:35:04:71:a1:84:18:50:a9:a5:12:b7:14
+ 4e:42:3e:93:50:9d:2f:c1:bd:45:f3:4e:86:61:0b:bc
+ 3b:ed:78:c7:2b:ba:4b:a0:ef:e6:0e:a9:9a:f4:aa:73
+ 23:b8:51:c7:d3:dd:fd:a7:1c:c1:69:32:ea:26:32:6d
+ 40:b0:0a:cd:0d:fa:b4:f4:56:ed:e8:d4:96:08:80:fd
+ 43:44:8c:fb:bb:af:81:d7:bb:71:c6:7c:3a:d2:a7:83
+ e6:28:2d:2f:00:05:82:d7:cc:59:db:d9:e5:4f:a4:67
+ 05:
+
+prime2:
+ 00:ee:1b:2a:48:37:fa:7c:94:35:36:ac:83:5f:2c:98
+ e3:07:43:d1:2c:80:0e:a2:b8:7a:eb:e2:70:f6:49:77
+ b3:42:05:fe:06:cf:3f:ca:0f:0d:44:1c:74:0a:77:f7
+ 31:9f:30:fb:d9:44:71:11:e6:4a:ff:ef:ae:77:98:3e
+ 73:a0:77:21:a6:e0:66:9a:cf:5f:eb:3b:39:62:0b:ba
+ 1b:9b:1a:a5:58:4c:7e:17:fc:64:61:93:89:f0:c0:0f
+ ce:55:18:7e:d4:33:87:32:0e:53:51:5f:03:b4:05:4a
+ 5c:e7:5b:10:e5:b7:88:e5:04:b2:53:45:98:2f:9d:fb
+ 32:f5:2f:d9:59:54:ce:91:83:4c:37:ee:ab:5a:05:40
+ 85:05:03:ae:b4:3d:96:c2:67:6b:28:25:91:87:ed:d1
+ 3a:0f:4b:38:a5:81:b3:5b:6f:3e:33:27:1e:9a:4a:e6
+ 3c:7c:be:9f:45:72:5b:eb:e3:dd:6c:73:ae:0d:07:bd
+ 91:
+
+coefficient:
+ 45:53:87:ab:71:9c:14:af:6c:00:44:bb:de:d5:72:ed
+ e9:21:f2:19:e5:4d:30:92:8e:9b:b7:f6:db:9e:ea:71
+ b3:c2:89:01:4a:49:1f:2e:f8:34:57:e0:36:9a:20:84
+ a8:b0:8a:0b:2a:d6:da:36:22:c2:ac:a2:85:99:f7:5d
+ 3f:2e:71:ab:e5:f7:bd:b2:8c:6f:44:33:aa:2d:cf:38
+ 8c:d6:77:c7:d5:68:88:f1:f9:80:c2:e2:b8:58:26:bd
+ de:d6:8d:d5:c9:43:dc:e2:af:2e:d3:c5:19:4e:d5:14
+ 33:bc:15:58:6f:05:eb:8d:0d:fa:40:a3:b7:77:24:4b
+ 30:a7:c2:8b:89:08:24:4d:fb:2e:3c:ad:ff:e3:d7:8b
+ 9c:f2:07:0d:79:3c:5e:f5:83:94:32:e2:16:dc:a9:22
+ b4:f4:09:6a:f6:af:7d:9c:41:dc:be:23:7e:c4:6d:d6
+ f9:e6:8e:3c:2d:00:fa:ac:d2:c8:6e:c5:6d:52:74:cd
+
+
+exp1:
+ 4d:20:f9:2d:84:47:6a:13:1e:10:47:27:4a:8c:44:ce
+ f1:53:3c:09:d6:78:22:fe:e3:1d:b4:00:9b:2f:7b:e8
+ 12:6d:7b:46:e4:68:a3:7d:09:ff:0b:0f:0b:6c:66:7a
+ 28:6f:c2:2f:38:40:e9:59:f4:9c:a0:47:22:f6:cb:63
+ d1:89:09:f1:85:87:27:33:f4:7d:00:b2:f2:5a:d3:c0
+ 8b:35:4a:ef:18:8c:61:17:f6:c5:4f:94:c8:89:fd:0a
+ 4a:48:65:b0:82:e7:8b:41:42:e6:c2:15:96:18:8a:42
+ 04:d6:7c:92:59:aa:aa:83:14:44:83:47:b7:ab:25:1f
+ fe:33:d5:72:37:b4:b8:ce:c5:9a:ec:a3:fa:04:86:2f
+ 0f:4c:80:b5:97:0a:e6:ca:10:40:3c:78:34:35:37:04
+ 2a:b9:01:26:d3:c7:6d:e1:9b:79:27:56:bb:be:d8:23
+ dd:32:2c:62:00:b8:d0:bb:ad:91:c6:2c:ca:76:ca:15
+
+
+exp2:
+ 30:d8:19:c0:5e:db:5f:9a:f7:9f:93:9c:0f:76:12:96
+ df:f2:a5:82:3f:72:c1:26:9e:f0:ac:af:07:96:e2:9b
+ 3f:3c:03:74:5a:27:77:c7:c6:ac:e6:39:57:bc:6c:55
+ 1d:96:ea:d3:13:1b:2e:d4:d3:25:d5:81:30:bf:66:70
+ 49:c6:a6:7c:99:23:f3:35:ff:33:3e:1e:f3:61:fc:77
+ 95:45:ce:0d:63:03:aa:df:f7:a7:9c:a0:7b:66:aa:d7
+ 64:d5:75:8f:0a:52:fd:8d:ba:c1:c2:7f:fb:f9:e9:db
+ 4d:0a:7d:58:e2:61:8e:b9:7b:eb:61:27:6a:fd:39:7e
+ a6:95:7e:3c:b9:0c:f7:04:bc:29:ed:27:f1:7b:8a:54
+ bf:46:96:1c:1b:56:45:e2:f9:34:6f:20:7f:85:e5:99
+ c7:71:62:d9:70:d5:de:37:df:c6:96:8b:cc:92:f8:d0
+ 07:b7:02:ed:38:1c:6b:33:7f:44:b4:26:4c:3d:fe:41
+
+
+
+Public Key PIN:
+ pin-sha256:UOonm3sEw21t/nC/tr24q9sX/HPV9mo0/M3Ya8rAwLs=
+Public Key ID:
+ sha256:50ea279b7b04c36d6dfe70bfb6bdb8abdb17fc73d5f66a34fccdd86bcac0c0bb
+ sha1:b963cfb8eb202ccad2bb988dfa9e00cc52c1a4ba
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIG4gIBAAKCAYEAu9dHH1XtwAivHTLSae930vP2hn7zl+I1ctQKhx51dr9ZKb7N
+5q1tfWJHGfvtJJR/K9YMaM/N7vNestsRREt/MM7Sp3WnN4PAQdahhyJI+u/RFe3J
+0nOr4XyUTbKWgM9aXH6W9gL6pIuxBbAn9dc4vSA37RLAIgeppl5HvR0zJ6LNTAxw
+um7ZE297oXLo9L7jhhuis6EHz5PoOiZRPq+82oCxklaLIecd2fkMqWi3BNhtH2+Y
+kPv7NRhxPFBzsUWx5+57hF1XlTM3sA/rhY+NsH8QF4ADmRtiDB1yb+V3OMh1lmE2
+SyiuF6T5gZBNS4VhOb5sysCpzU5FJ0eEgj1/xqcA15BkfKXp+PaS0nJUp5Vf/JMd
+yRp4bjoaH4+iQdIEXBkyVBbyl298+STXpuIHz5+eZCeBX1p3ZU97soF4P6MiF9O6
+BnHVCWrChbo193EBtGPHcGKYWICiQCfA4tX9YOBaepy/e+Y0ePEW6CjZktzmLrbX
+GoNLhpLWgc6OUArVAgMBAAECggGAeideZh9gVGCRWICjWybSmonyiLZoPR5rObhw
+/DuvkcCQAFjH17pymHZf3KL7La2wIda6DTMOLdVwgQl7ahlapmfpj+MwEicI0Qf9
+1T5TjXSFWShg9g4o+aMlYn2/6BZwIfRkwalgS79YKGXNJs+GY19fXzmxXq/zAHER
+YAdsK9twfIMej+7kFgKKuIxcuESm+6BfJ0eSJ8h83cvrS8PHIaVNVOgY5LxCqmyO
+cmDZnDoOhMHyyl5Dl9zETr/W7LJwCEETAUi8NqLrXme2aqS2SiT6/W3vW3e8DH2V
+moTsP5eqfAd2gPU6SfSZ7s8XEoPo2+8iYGdi+D75vBgrhPypgpWNkSeOuocVZR6f
+s5Vf3EAvFet+CtdpgHuK4imJOi7rqQXBHl0jDaDXxJVNCYWMr5AjNgRmqRbX1OKq
+Wm1EWmzI6KAI+t4ZIF/jBhflZcZV7w8N/z4cxZjuNNMHgRH+6RWH5pp2RL3LoDhj
+mq/RfKfbJuLNSqKKf7jcelUATCDBAoHBAMn1FFlJO5UfFbAMg8v0akhgKq+L1YMW
+qnFarxFjxsEKka9bvW6cy9frv8cxnyJGAc87PMu6fa3lu9h80l1SIBTqcAieKZgx
+IHietj6Q6O/IL0XUNQRxoYQYUKmlErcUTkI+k1CdL8G9RfNOhmELvDvteMcrukug
+7+YOqZr0qnMjuFHH0939pxzBaTLqJjJtQLAKzQ36tPRW7ejUlgiA/UNEjPu7r4HX
+u3HGfDrSp4PmKC0vAAWC18xZ29nlT6RnBQKBwQDuGypIN/p8lDU2rINfLJjjB0PR
+LIAOorh66+Jw9kl3s0IF/gbPP8oPDUQcdAp39zGfMPvZRHER5kr/7653mD5zoHch
+puBmms9f6zs5Ygu6G5sapVhMfhf8ZGGTifDAD85VGH7UM4cyDlNRXwO0BUpc51sQ
+5beI5QSyU0WYL537MvUv2VlUzpGDTDfuq1oFQIUFA660PZbCZ2soJZGH7dE6D0s4
+pYGzW28+MycemkrmPHy+n0VyW+vj3Wxzrg0HvZECgcBNIPkthEdqEx4QRydKjETO
+8VM8CdZ4Iv7jHbQAmy976BJte0bkaKN9Cf8LDwtsZnoob8IvOEDpWfScoEci9stj
+0YkJ8YWHJzP0fQCy8lrTwIs1Su8YjGEX9sVPlMiJ/QpKSGWwgueLQULmwhWWGIpC
+BNZ8klmqqoMURINHt6slH/4z1XI3tLjOxZrso/oEhi8PTIC1lwrmyhBAPHg0NTcE
+KrkBJtPHbeGbeSdWu77YI90yLGIAuNC7rZHGLMp2yhUCgcAw2BnAXttfmvefk5wP
+dhKW3/Klgj9ywSae8KyvB5bimz88A3RaJ3fHxqzmOVe8bFUdlurTExsu1NMl1YEw
+v2ZwScamfJkj8zX/Mz4e82H8d5VFzg1jA6rf96ecoHtmqtdk1XWPClL9jbrBwn/7
++enbTQp9WOJhjrl762Enav05fqaVfjy5DPcEvCntJ/F7ilS/RpYcG1ZF4vk0byB/
+heWZx3Fi2XDV3jffxpaLzJL40Ae3Au04HGszf0S0Jkw9/kECgcBFU4ercZwUr2wA
+RLve1XLt6SHyGeVNMJKOm7f2257qcbPCiQFKSR8u+DRX4DaaIISosIoLKtbaNiLC
+rKKFmfddPy5xq+X3vbKMb0Qzqi3POIzWd8fVaIjx+YDC4rhYJr3e1o3VyUPc4q8u
+08UZTtUUM7wVWG8F640N+kCjt3ckSzCnwouJCCRN+y48rf/j14uc8gcNeTxe9YOU
+MuIW3KkitPQJavavfZxB3L4jfsRt1vnmjjwtAPqs0shuxW1SdM0=
+-----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..fc3e43f
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/wrongca.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEETCCAnmgAwIBAgIUNVTN+if8IQU0I1n4qyVF9qqhuo0wDQYJKoZIhvcNAQEL
+BQAwHzEdMBsGA1UEAxMUQW5vdGhlciB1bnJlbGF0ZWQgQ0EwIBcNMjAwMTA3MDkz
+NDA5WhgPOTk5OTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFEFub3RoZXIgdW5yZWxh
+dGVkIENBMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAu9dHH1XtwAiv
+HTLSae930vP2hn7zl+I1ctQKhx51dr9ZKb7N5q1tfWJHGfvtJJR/K9YMaM/N7vNe
+stsRREt/MM7Sp3WnN4PAQdahhyJI+u/RFe3J0nOr4XyUTbKWgM9aXH6W9gL6pIux
+BbAn9dc4vSA37RLAIgeppl5HvR0zJ6LNTAxwum7ZE297oXLo9L7jhhuis6EHz5Po
+OiZRPq+82oCxklaLIecd2fkMqWi3BNhtH2+YkPv7NRhxPFBzsUWx5+57hF1XlTM3
+sA/rhY+NsH8QF4ADmRtiDB1yb+V3OMh1lmE2SyiuF6T5gZBNS4VhOb5sysCpzU5F
+J0eEgj1/xqcA15BkfKXp+PaS0nJUp5Vf/JMdyRp4bjoaH4+iQdIEXBkyVBbyl298
++STXpuIHz5+eZCeBX1p3ZU97soF4P6MiF9O6BnHVCWrChbo193EBtGPHcGKYWICi
+QCfA4tX9YOBaepy/e+Y0ePEW6CjZktzmLrbXGoNLhpLWgc6OUArVAgMBAAGjQzBB
+MA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcEADAdBgNVHQ4EFgQUuWPP
+uOsgLMrSu5iN+p4AzFLBpLowDQYJKoZIhvcNAQELBQADggGBAEobHXRbd6wfUmyf
+P5v6qdJQMqtNGlU8eYzizEyuyovSlL+g9wgQ/91RAYK26FXzOuRz9Cg/ZWYVHqiG
+rRWWwcfzY7qHo3HGkpDSIjD53TAoK46ICD4+EreG+JBvy1P3Ij/VX7M07swIg8Ff
+6O4CnJpKAFaSr9wT8Ac3oCu+vymgLajMocNYV/UFVND+TLi6sx0zcMfCgW2vhSWk
+PRulxL76xq97vjWoveqDiFS41cPOAghd4hUmzRFByX6XPBx6YZddSUF+QZt92K4Z
+YEU4UbKqhbiBoZMGaQ8DzM2T44WPISrRZ0QpeS+pXwVjbDfoUbBWYAjFA8EHhPOi
+oewIIYnarItI3z3iccErOeKPPVQh5QW3/CwO4XSnvTEBkhf2EjG25UAHZ8LZy0t8
+Sw1raGJPYJV/qNVeIzLKd3tYmNpcmddYqS+ei2yBOoO5UPdbYaH1gTAZ4BbOhOml
+BJKJWcekpJrZAVTBNRectxsMXB8fHYL65Wa+w3cRqsZRjTbTEg==
+-----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..0e8491b
--- /dev/null
+++ b/daemon/lua/trust_anchors.test/x509/wrongca.tmpl
@@ -0,0 +1,4 @@
+cn = Another unrelated CA
+ca
+cert_signing_key
+expiration_days = -1
diff --git a/daemon/lua/zonefile.lua b/daemon/lua/zonefile.lua
new file mode 100644
index 0000000..8ea3a08
--- /dev/null
+++ b/daemon/lua/zonefile.lua
@@ -0,0 +1,93 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+-- 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..41a55ad
--- /dev/null
+++ b/daemon/main.c
@@ -0,0 +1,613 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "kresconfig.h"
+
+#include "contrib/ucw/mempool.h"
+#include "daemon/engine.h"
+#include "daemon/io.h"
+#include "daemon/network.h"
+#include "daemon/udp_queue.h"
+#include "daemon/worker.h"
+#include "lib/defines.h"
+#include "lib/dnssec.h"
+#include "lib/log.h"
+
+#include <arpa/inet.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#if ENABLE_CAP_NG
+#include <cap-ng.h>
+#endif
+
+#include <lua.h>
+#include <uv.h>
+#if ENABLE_LIBSYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+#include <libknot/error.h>
+
+#if ENABLE_JEMALLOC
+/* Make the jemalloc library needed.
+ *
+ * The problem is with --as-needed for linker which is added by default by meson.
+ * If we don't use any jemalloc-specific calls, linker will decide that
+ * it is not needed and won't link it. Making it needed seems better than
+ * trying to override the flag which might be useful in some other cases, etc.
+ *
+ * Exporting the function is a very easy way of ensuring that it's not optimized out.
+ */
+#include <jemalloc/jemalloc.h>
+KR_EXPORT void kr_jemalloc_unused(void)
+{
+ malloc_stats_print(NULL, NULL, NULL);
+}
+/* We don't use threads (or rarely in some parts), so multiple arenas don't make sense.
+ https://jemalloc.net/jemalloc.3.html
+ */
+KR_EXPORT const char *malloc_conf = "narenas:1";
+#endif
+
+struct args the_args_value; /** Static allocation for the_args singleton. */
+
+static void signal_handler(uv_signal_t *handle, int signum)
+{
+ switch (signum) {
+ case SIGINT: /* Fallthrough. */
+ case SIGTERM:
+ uv_stop(uv_default_loop());
+ uv_signal_stop(handle);
+ break;
+ case SIGCHLD:
+ /* Wait for all dead processes. */
+ while (waitpid(-1, NULL, WNOHANG) > 0);
+ break;
+ default:
+ kr_log_error(SYSTEM, "unhandled signal: %d\n", signum);
+ break;
+ }
+}
+
+/** 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
+}
+
+
+/*
+ * Server operation.
+ */
+
+static int fork_workers(int forks)
+{
+ /* Fork subprocesses if requested */
+ while (--forks > 0) {
+ int pid = fork();
+ if (pid < 0) {
+ perror("[system] fork");
+ return kr_error(errno);
+ }
+
+ /* Forked process */
+ if (pid == 0) {
+ return forks;
+ }
+ }
+ 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:kind] Listen on given fd (handed out by supervisor, :kind is optional).\n"
+ " -c, --config=[path] Config file path (relative to [rundir]) (default: config).\n"
+ " -n, --noninteractive Don't start the read-eval-print loop for stdin+stdout.\n"
+ " -q, --quiet No command prompt in interactive mode.\n"
+ " -v, --verbose Increase logging to debug level.\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, 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 '-n' if you want non-interactive mode. "
+ "Commands can be simply added to your configuration file or sent over the control socket.\n"
+ );
+ return EXIT_FAILURE;
+ }
+
+ /* Control sockets or TTY */
+ uv_pipe_t *pipe = malloc(sizeof(*pipe));
+ if (!pipe)
+ return EXIT_FAILURE;
+ uv_pipe_init(loop, pipe, 0);
+ if (args->interactive) {
+ if (!args->quiet)
+ printf("Interactive mode:\n" "> ");
+ pipe->data = io_tty_alloc_data();
+ uv_pipe_open(pipe, 0);
+ uv_read_start((uv_stream_t*)pipe, io_tty_alloc, io_tty_process_input);
+ } else if (args->control_fd != -1 && uv_pipe_open(pipe, args->control_fd) == 0) {
+ uv_listen((uv_stream_t *)pipe, 16, io_tty_accept);
+ }
+
+ /* Notify supervisor. */
+#if ENABLE_LIBSYSTEMD
+ sd_notify(0, "READY=1");
+#endif
+ /* Run event loop */
+ uv_run(loop, UV_RUN_DEFAULT);
+ /* Free pipe's data. Seems OK even on the stopped loop.
+ * In interactive case it may have been done in callbacks already (single leak). */
+ if (!args->interactive) {
+ uv_close((uv_handle_t *)pipe, NULL);
+ free(pipe);
+ }
+ return EXIT_SUCCESS;
+}
+
+static void args_init(struct args *args)
+{
+ memset(args, 0, sizeof(struct args));
+ /* Zeroed arrays are OK. */
+ args->forks = 1;
+ args->control_fd = -1;
+ args->interactive = true;
+ args->quiet = false;
+}
+
+/* Free pointed-to resources. */
+static void args_deinit(struct args *args)
+{
+ array_clear(args->addrs);
+ array_clear(args->addrs_tls);
+ for (int i = 0; i < args->fds.len; ++i)
+ free_const(args->fds.at[i].flags.kind);
+ array_clear(args->fds);
+ array_clear(args->config);
+}
+
+/** 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'},
+ {"config", required_argument, 0, 'c'},
+ {"forks", required_argument, 0, 'f'},
+ {"noninteractive", no_argument, 0, 'n'},
+ {"verbose", no_argument, 0, 'v'},
+ {"quiet", no_argument, 0, 'q'},
+ {"version", no_argument, 0, 'V'},
+ {"help", no_argument, 0, 'h'},
+ {"fd", required_argument, 0, 'S'},
+ {0, 0, 0, 0}
+ };
+ while ((c = getopt_long(argc, argv, "a:t:c:f:nvqVhS:", opts, &li)) != -1) {
+ switch (c)
+ {
+ case 'a':
+ kr_require(optarg);
+ array_push(args->addrs, optarg);
+ break;
+ case 't':
+ kr_require(optarg);
+ array_push(args->addrs_tls, optarg);
+ break;
+ case 'c':
+ kr_require(optarg);
+ array_push(args->config, optarg);
+ break;
+ case 'f':
+ kr_require(optarg);
+ args->forks = strtol(optarg, NULL, 10);
+ if (args->forks == 1) {
+ kr_log_deprecate(SYSTEM, "use --noninteractive instead of --forks=1\n");
+ } else {
+ kr_log_deprecate(SYSTEM, "support for running multiple --forks will be removed\n");
+ }
+ if (args->forks <= 0) {
+ kr_log_error(SYSTEM, "error '-f' requires a positive"
+ " number, not '%s'\n", optarg);
+ return EXIT_FAILURE;
+ }
+ /* fall through */
+ case 'n':
+ args->interactive = false;
+ break;
+ case 'v':
+ kr_log_level_set(LOG_DEBUG);
+ break;
+ case 'q':
+ args->quiet = true;
+ break;
+ case 'V':
+ printf("Knot Resolver, version %s\n", PACKAGE_VERSION);
+ return EXIT_SUCCESS;
+ case 'h':
+ case '?':
+ help(argc, argv);
+ return EXIT_SUCCESS;
+ default:
+ help(argc, argv);
+ return EXIT_FAILURE;
+ case 'S':
+ kr_require(optarg);
+ flagged_fd_t ffd = { 0 };
+ char *endptr;
+ ffd.fd = strtol(optarg, &endptr, 10);
+ if (endptr != optarg && endptr[0] == '\0') {
+ /* Plain DNS */
+ ffd.flags.tls = false;
+ } else if (endptr[0] == ':' && strcasecmp(endptr + 1, "tls") == 0) {
+ /* DoT */
+ ffd.flags.tls = true;
+ /* We know what .sock_type should be but it wouldn't help. */
+ } else if (endptr[0] == ':' && endptr[1] != '\0') {
+ /* Some other kind; no checks here. */
+ ffd.flags.kind = strdup(endptr + 1);
+ } else {
+ kr_log_error(SYSTEM, "incorrect value passed to '-S/--fd': %s\n",
+ optarg);
+ return EXIT_FAILURE;
+ }
+ array_push(args->fds, ffd);
+ break;
+ }
+ }
+ if (optind < argc) {
+ args->rundir = argv[optind];
+ }
+ return -1;
+}
+
+/** Just convert addresses to file-descriptors; clear *addrs on success.
+ * @note AF_UNIX is supported (starting with '/').
+ * @return zero or exit code for main()
+ */
+static int bind_sockets(addr_array_t *addrs, bool tls, flagged_fd_array_t *fds)
+{
+ bool has_error = false;
+ for (size_t i = 0; i < addrs->len; ++i) {
+ /* Get port and separate address string. */
+ uint16_t port = tls ? KR_DNS_TLS_PORT : KR_DNS_PORT;
+ char addr_buf[INET6_ADDRSTRLEN + 1];
+ int ret;
+ const char *addr_str;
+ const int family = kr_straddr_family(addrs->at[i]);
+ if (family == AF_UNIX) {
+ ret = 0;
+ addr_str = addrs->at[i];
+ } else { /* internet socket (or garbage) */
+ ret = kr_straddr_split(addrs->at[i], addr_buf, &port);
+ addr_str = addr_buf;
+ }
+ /* Get sockaddr. */
+ struct sockaddr *sa = NULL;
+ if (ret == 0) {
+ sa = kr_straddr_socket(addr_str, port, NULL);
+ if (!sa) ret = kr_error(EINVAL); /* could be ENOMEM but unlikely */
+ }
+ flagged_fd_t ffd = { .flags = { .tls = tls } };
+ if (ret == 0 && !tls && family != AF_UNIX) {
+ /* AF_UNIX can do SOCK_DGRAM, but let's not support that *here*. */
+ ffd.fd = io_bind(sa, SOCK_DGRAM, NULL);
+ if (ffd.fd < 0)
+ ret = ffd.fd;
+ else if (array_push(*fds, ffd) < 0)
+ ret = kr_error(ENOMEM);
+ }
+ if (ret == 0) { /* common for TCP and TLS, including AF_UNIX cases */
+ ffd.fd = io_bind(sa, SOCK_STREAM, NULL);
+ if (ffd.fd < 0)
+ ret = ffd.fd;
+ else if (array_push(*fds, ffd) < 0)
+ ret = kr_error(ENOMEM);
+ }
+ free(sa);
+ if (ret != 0) {
+ kr_log_error(NETWORK, "bind to '%s'%s: %s\n",
+ addrs->at[i], tls ? " (TLS)" : "", kr_strerror(ret));
+ has_error = true;
+ }
+ }
+ array_clear(*addrs);
+ return has_error ? EXIT_FAILURE : kr_ok();
+}
+
+static int start_listening(struct network *net, flagged_fd_array_t *fds) {
+ int some_bad_ret = 0;
+ for (size_t i = 0; i < fds->len; ++i) {
+ flagged_fd_t *ffd = &fds->at[i];
+ int ret = network_listen_fd(net, ffd->fd, ffd->flags);
+ if (ret != 0) {
+ some_bad_ret = ret;
+ /* TODO: try logging address@port. It's not too important,
+ * because typical problems happen during binding already.
+ * (invalid address, permission denied) */
+ kr_log_error(NETWORK, "listen on fd=%d: %s\n",
+ ffd->fd, kr_strerror(ret));
+ /* Continue printing all of these before exiting. */
+ } else {
+ ffd->flags.kind = NULL; /* ownership transferred */
+ }
+ }
+ return some_bad_ret;
+}
+
+/* Drop POSIX 1003.1e capabilities. */
+static void drop_capabilities(void)
+{
+#if ENABLE_CAP_NG
+ /* Drop all capabilities when running under non-root user. */
+ if (geteuid() == 0) {
+ kr_log_debug(SYSTEM, "running as root, no capabilities dropped\n");
+ return;
+ }
+ if (capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) {
+ capng_clear(CAPNG_SELECT_BOTH);
+
+ /* Apply. */
+ if (capng_apply(CAPNG_SELECT_BOTH) < 0) {
+ kr_log_error(SYSTEM, "failed to set process capabilities: %s\n",
+ strerror(errno));
+ } else {
+ kr_log_debug(SYSTEM, "all capabilities dropped\n");
+ }
+ } else {
+ /* If user() was called, the capabilities were already dropped along with SETPCAP. */
+ kr_log_debug(SYSTEM, "process not allowed to set capabilities, skipping\n");
+ }
+#endif /* ENABLE_CAP_NG */
+}
+
+int main(int argc, char **argv)
+{
+ kr_log_group_reset();
+ if (setvbuf(stdout, NULL, _IONBF, 0) || setvbuf(stderr, NULL, _IONBF, 0)) {
+ kr_log_error(SYSTEM, "failed to to set output buffering (ignored): %s\n",
+ strerror(errno));
+ fflush(stderr);
+ }
+ if (strcmp("linux", OPERATING_SYSTEM) != 0)
+ kr_log_warning(SYSTEM, "Knot Resolver is tested on Linux, other platforms might exhibit bugs.\n"
+ "Please report issues to https://gitlab.nic.cz/knot/knot-resolver/issues/\n"
+ "Thank you for your time and interest!\n");
+
+ the_args = &the_args_value;
+ args_init(the_args);
+ int ret = parse_args(argc, argv, the_args);
+ if (ret >= 0) goto cleanup_args;
+
+ ret = bind_sockets(&the_args->addrs, false, &the_args->fds);
+ if (ret) goto cleanup_args;
+ ret = bind_sockets(&the_args->addrs_tls, true, &the_args->fds);
+ if (ret) goto cleanup_args;
+
+ /* Switch to rundir. */
+ if (the_args->rundir != NULL) {
+ /* FIXME: access isn't a good way if we start as root and drop privileges later */
+ if (access(the_args->rundir, W_OK) != 0
+ || chdir(the_args->rundir) != 0) {
+ kr_log_error(SYSTEM, "rundir '%s': %s\n",
+ the_args->rundir, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Select which config files to load and verify they are read-able. */
+ bool load_defaults = true;
+ size_t i = 0;
+ while (i < the_args->config.len) {
+ const char *config = the_args->config.at[i];
+ if (strcmp(config, "-") == 0) {
+ load_defaults = false;
+ array_del(the_args->config, i);
+ continue; /* don't increment i */
+ } else if (access(config, R_OK) != 0) {
+ char cwd[PATH_MAX];
+ get_workdir(cwd, sizeof(cwd));
+ kr_log_error(SYSTEM, "config '%s' (workdir '%s'): %s\n",
+ config, cwd, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ i++;
+ }
+ if (the_args->config.len == 0 && access("config", R_OK) == 0)
+ array_push(the_args->config, "config");
+ if (load_defaults)
+ array_push(the_args->config, LIBDIR "/postconfig.lua");
+
+ /* File-descriptor count limit: soft->hard. */
+ struct rlimit rlim;
+ ret = getrlimit(RLIMIT_NOFILE, &rlim);
+ if (ret == 0 && rlim.rlim_cur != rlim.rlim_max) {
+ kr_log_debug(SYSTEM, "increasing file-descriptor limit: %ld -> %ld\n",
+ (long)rlim.rlim_cur, (long)rlim.rlim_max);
+ rlim.rlim_cur = rlim.rlim_max;
+ ret = setrlimit(RLIMIT_NOFILE, &rlim);
+ }
+ if (ret) {
+ kr_log_error(SYSTEM, "failed to get or set file-descriptor limit: %s\n",
+ strerror(errno));
+ } else if (rlim.rlim_cur < 512*1024) {
+ kr_log_warning(SYSTEM, "warning: hard limit for number of file-descriptors is only %ld but recommended value is 524288\n",
+ (long)rlim.rlim_cur);
+ }
+
+ /* Fork subprocesses if requested */
+ int fork_id = fork_workers(the_args->forks);
+ if (fork_id < 0) {
+ return EXIT_FAILURE;
+ }
+
+ kr_crypto_init();
+
+ /* Create a server engine. */
+ knot_mm_t pool;
+ mm_ctx_mempool(&pool, MM_DEFAULT_BLKSIZE);
+ static 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;
+ }
+ /* Initialize the worker */
+ ret = worker_init(&engine, the_args->forks);
+ if (ret != 0) {
+ kr_log_error(SYSTEM, "failed to initialize worker: %s\n", kr_strerror(ret));
+ return EXIT_FAILURE;
+ }
+
+ uv_loop_t *loop = uv_default_loop();
+ /* Catch some signals. */
+ uv_signal_t sigint, sigterm, sigchld;
+ if (true) ret = uv_signal_init(loop, &sigint);
+ if (!ret) ret = uv_signal_init(loop, &sigterm);
+ if (!ret) ret = uv_signal_init(loop, &sigchld);
+ if (!ret) ret = uv_signal_start(&sigint, signal_handler, SIGINT);
+ if (!ret) ret = uv_signal_start(&sigterm, signal_handler, SIGTERM);
+ if (!ret) ret = uv_signal_start(&sigchld, signal_handler, SIGCHLD);
+ /* 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;
+ }
+ /* Profiling: avoid SIGPROF waking up the event loop. Otherwise the profiles
+ * (of the usual type) may skew results, e.g. epoll_pwait() taking lots of time. */
+ ret = uv_loop_configure(loop, UV_LOOP_BLOCK_SIGNAL, SIGPROF);
+ if (ret) {
+ kr_log_info(SYSTEM, "failed to block SIGPROF in event loop, ignoring: %s\n",
+ uv_strerror(ret));
+ }
+
+ /* Start listening, in the sense of network_listen_fd(). */
+ if (start_listening(&engine.net, &the_args->fds) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ ret = udp_queue_init_global(loop);
+ if (ret) {
+ kr_log_error(SYSTEM, "failed to initialize UDP queue: %s\n",
+ kr_strerror(ret));
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* Start the scripting engine */
+ if (engine_load_sandbox(&engine) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ for (i = 0; i < the_args->config.len; ++i) {
+ const char *config = the_args->config.at[i];
+ if (engine_loadconf(&engine, config) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+ lua_settop(engine.L, 0);
+ }
+
+ drop_capabilities();
+
+ if (engine_start(&engine) != 0) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ if (network_engage_endpoints(&engine.net)) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* Run the event loop */
+ ret = run_worker(loop, &engine, fork_id == 0, the_args);
+
+cleanup:/* Cleanup. */
+ engine_deinit(&engine);
+ worker_deinit();
+ if (loop != NULL) {
+ uv_loop_close(loop);
+ }
+ mp_delete(pool.ctx);
+cleanup_args:
+ args_deinit(the_args);
+ kr_crypto_cleanup();
+ return ret;
+}
diff --git a/daemon/meson.build b/daemon/meson.build
new file mode 100644
index 0000000..68a2646
--- /dev/null
+++ b/daemon/meson.build
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+# daemon
+
+kresd_src = files([
+ 'bindings/cache.c',
+ 'bindings/event.c',
+ 'bindings/impl.c',
+ 'bindings/modules.c',
+ 'bindings/net.c',
+ 'bindings/worker.c',
+ 'engine.c',
+ 'ffimodule.c',
+ 'io.c',
+ 'main.c',
+ 'network.c',
+ 'proxyv2.c',
+ 'session.c',
+ 'tls.c',
+ 'tls_ephemeral_credentials.c',
+ 'tls_session_ticket-srv.c',
+ 'udp_queue.c',
+ 'worker.c',
+ 'zimport.c',
+])
+if nghttp2.found()
+ kresd_src += files(['http.c'])
+endif
+
+c_src_lint += kresd_src
+
+config_tests += [
+ ['cache.clear', files('cache.test/clear.test.lua')],
+ ['zimport', files('zimport.test/zimport.test.lua')],
+]
+
+integr_tests += [
+ ['cache_insert_ns', meson.current_source_dir() / 'cache.test' / 'insert_ns.test.integr'],
+ ['proxyv2', meson.current_source_dir() / 'proxyv2.test']
+]
+
+kresd_deps = [
+ contrib_dep,
+ kresconfig_dep,
+ libkres_dep,
+ libknot,
+ libzscanner,
+ libdnssec,
+ libuv,
+ luajit,
+ gnutls,
+ libsystemd,
+ capng,
+ nghttp2,
+ malloc,
+]
+
+
+subdir('lua')
+
+
+kresd = executable(
+ 'kresd',
+ kresd_src,
+ dependencies: kresd_deps,
+ export_dynamic: true,
+ install: true,
+ install_dir: get_option('sbindir'),
+)
diff --git a/daemon/network.c b/daemon/network.c
new file mode 100644
index 0000000..a20b1e4
--- /dev/null
+++ b/daemon/network.c
@@ -0,0 +1,928 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/network.h"
+
+#include "contrib/cleanup.h"
+#include "daemon/bindings/impl.h"
+#include "daemon/io.h"
+#include "daemon/tls.h"
+#include "daemon/worker.h"
+#include "lib/utils.h"
+
+#if ENABLE_XDP
+ #include <libknot/xdp/eth.h>
+#endif
+
+#include <libgen.h>
+#include <net/if.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+/** Determines the type of `struct endpoint_key`. */
+enum endpoint_key_type
+{
+ ENDPOINT_KEY_SOCKADDR = 1,
+ ENDPOINT_KEY_IFNAME = 2,
+};
+
+/** Used as a key in the `struct network::endpoints` trie. */
+struct endpoint_key {
+ enum endpoint_key_type type;
+ char data[];
+};
+
+struct __attribute__((packed)) endpoint_key_sockaddr {
+ enum endpoint_key_type type;
+ struct kr_sockaddr_key_storage sa_key;
+};
+
+struct __attribute__((packed)) endpoint_key_ifname {
+ enum endpoint_key_type type;
+ char ifname[128];
+};
+
+/** Used for reserving enough storage for `endpoint_key`. */
+struct endpoint_key_storage {
+ union {
+ enum endpoint_key_type type;
+ struct endpoint_key_sockaddr sa;
+ struct endpoint_key_ifname ifname;
+ char bytes[1]; /* for easier casting */
+ };
+};
+
+static_assert(_Alignof(struct endpoint_key) <= 4, "endpoint_key must be aligned to <=4");
+static_assert(_Alignof(struct endpoint_key_sockaddr) <= 4, "endpoint_key must be aligned to <=4");
+static_assert(_Alignof(struct endpoint_key_ifname) <= 4, "endpoint_key must be aligned to <=4");
+
+void network_init(struct network *net, uv_loop_t *loop, int tcp_backlog)
+{
+ if (net != NULL) {
+ net->loop = loop;
+ net->endpoints = trie_create(NULL);
+ net->endpoint_kinds = trie_create(NULL);
+ net->proxy_all4 = false;
+ net->proxy_all6 = false;
+ net->proxy_addrs4 = trie_create(NULL);
+ net->proxy_addrs6 = trie_create(NULL);
+ net->tls_client_params = 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;
+ }
+}
+
+/** Notify the registered function about endpoint getting open.
+ * If log_port < 1, don't log it. */
+static int endpoint_open_lua_cb(struct network *net, struct endpoint *ep,
+ const char *log_addr)
+{
+ const bool ok = ep->flags.kind && !ep->handle && !ep->engaged && ep->fd != -1;
+ if (kr_fails_assert(ok))
+ return kr_error(EINVAL);
+ /* First find callback in the endpoint registry. */
+ lua_State *L = the_worker->engine->L;
+ void **pp = trie_get_try(net->endpoint_kinds, ep->flags.kind,
+ strlen(ep->flags.kind));
+ if (!pp && net->missing_kind_is_error) {
+ kr_log_error(NETWORK, "error: network socket kind '%s' not handled when opening '%s",
+ ep->flags.kind, log_addr);
+ if (ep->family != AF_UNIX)
+ kr_log_error(NETWORK, "#%d", ep->port);
+ kr_log_error(NETWORK, "'\n");
+ return kr_error(ENOENT);
+ }
+ if (!pp) return kr_ok();
+
+ /* Now execute the callback. */
+ const int fun_id = (char *)*pp - (char *)NULL;
+ lua_rawgeti(L, LUA_REGISTRYINDEX, fun_id);
+ lua_pushboolean(L, true /* open */);
+ lua_pushpointer(L, ep);
+ if (ep->family == AF_UNIX) {
+ lua_pushstring(L, log_addr);
+ } else {
+ lua_pushfstring(L, "%s#%d", log_addr, ep->port);
+ }
+ if (lua_pcall(L, 3, 0, 0)) {
+ kr_log_error(NETWORK, "error opening %s: %s\n", log_addr, lua_tostring(L, -1));
+ return kr_error(ENOSYS); /* TODO: better value? */
+ }
+ ep->engaged = true;
+ return kr_ok();
+}
+
+static int engage_endpoint_array(const char *b_key, uint32_t key_len, trie_val_t *val, void *net)
+{
+ const char *log_addr = network_endpoint_key_str((struct endpoint_key *) b_key);
+ if (!log_addr)
+ log_addr = "[unknown]";
+
+ endpoint_array_t *eps = *val;
+ for (int i = 0; i < eps->len; ++i) {
+ struct endpoint *ep = &eps->at[i];
+ const bool match = !ep->engaged && ep->flags.kind;
+ if (!match) continue;
+ int ret = endpoint_open_lua_cb(net, ep, log_addr);
+ if (ret) return ret;
+ }
+ return 0;
+}
+
+int network_engage_endpoints(struct network *net)
+{
+ if (net->missing_kind_is_error)
+ return kr_ok(); /* maybe weird, but let's make it idempotent */
+ net->missing_kind_is_error = true;
+ int ret = trie_apply_with_key(net->endpoints, engage_endpoint_array, net);
+ if (ret) {
+ net->missing_kind_is_error = false; /* avoid the same errors when closing */
+ return ret;
+ }
+ return kr_ok();
+}
+
+const char *network_endpoint_key_str(const struct endpoint_key *key)
+{
+ switch (key->type)
+ {
+ case ENDPOINT_KEY_SOCKADDR:;
+ const struct endpoint_key_sockaddr *sa_key =
+ (struct endpoint_key_sockaddr *) key;
+ struct sockaddr_storage sa_storage;
+ struct sockaddr *sa = kr_sockaddr_from_key(&sa_storage, (const char *) &sa_key->sa_key);
+ return kr_straddr(sa);
+ case ENDPOINT_KEY_IFNAME:;
+ const struct endpoint_key_ifname *if_key =
+ (struct endpoint_key_ifname *) key;
+ return if_key->ifname;
+ default:
+ kr_assert(false);
+ return NULL;
+ }
+}
+
+/** Notify the registered function about endpoint about to be closed. */
+static void endpoint_close_lua_cb(struct network *net, struct endpoint *ep)
+{
+ lua_State *L = the_worker->engine->L;
+ void **pp = trie_get_try(net->endpoint_kinds, ep->flags.kind,
+ strlen(ep->flags.kind));
+ if (!pp && net->missing_kind_is_error) {
+ kr_log_error(NETWORK, "internal error: missing kind '%s' in endpoint registry\n",
+ ep->flags.kind);
+ return;
+ }
+ if (!pp) return;
+
+ const int fun_id = (char *)*pp - (char *)NULL;
+ lua_rawgeti(L, LUA_REGISTRYINDEX, fun_id);
+ lua_pushboolean(L, false /* close */);
+ lua_pushpointer(L, ep);
+ lua_pushstring(L, "FIXME:endpoint-identifier");
+ if (lua_pcall(L, 3, 0, 0)) {
+ kr_log_error(NETWORK, "failed to close FIXME:endpoint-identifier: %s\n",
+ lua_tostring(L, -1));
+ }
+}
+
+static void endpoint_close(struct network *net, struct endpoint *ep, bool force)
+{
+ const bool is_control = ep->flags.kind && strcmp(ep->flags.kind, "control") == 0;
+ const bool is_xdp = ep->family == AF_XDP;
+
+ if (ep->family == AF_UNIX) { /* The FS name would be left behind. */
+ /* Extract local address for this socket. */
+ struct sockaddr_un sa;
+ sa.sun_path[0] = '\0'; /*< probably only for lint:scan-build */
+ socklen_t addr_len = sizeof(sa);
+ if (getsockname(ep->fd, (struct sockaddr *)&sa, &addr_len)
+ || unlink(sa.sun_path)) {
+ kr_log_error(NETWORK, "error (ignored) when closing unix socket (fd = %d): %s\n",
+ ep->fd, strerror(errno));
+ return;
+ }
+ }
+
+ if (ep->flags.kind && !is_control && !is_xdp) {
+ kr_assert(!ep->handle);
+ /* Special lua-handled endpoint. */
+ if (ep->engaged) {
+ endpoint_close_lua_cb(net, ep);
+ }
+ if (ep->fd > 0) {
+ close(ep->fd); /* nothing to do with errors */
+ }
+ free_const(ep->flags.kind);
+ return;
+ }
+
+ free_const(ep->flags.kind); /* needed if (is_control) */
+ kr_require(ep->handle);
+ if (force) { /* Force close if event loop isn't running. */
+ if (ep->fd >= 0) {
+ close(ep->fd);
+ }
+ if (ep->handle) {
+ ep->handle->loop = NULL;
+ io_free(ep->handle);
+ }
+ } else { /* Asynchronous close */
+ uv_close(ep->handle, io_free);
+ }
+}
+
+/** Endpoint visitor (see @file trie.h) */
+static int close_key(trie_val_t *val, void* net)
+{
+ endpoint_array_t *ep_array = *val;
+ for (int i = 0; i < ep_array->len; ++i) {
+ endpoint_close(net, &ep_array->at[i], true);
+ }
+ return 0;
+}
+
+static int free_key(trie_val_t *val, void* ext)
+{
+ endpoint_array_t *ep_array = *val;
+ array_clear(*ep_array);
+ free(ep_array);
+ return kr_ok();
+}
+
+int kind_unregister(trie_val_t *tv, void *L)
+{
+ int fun_id = (char *)*tv - (char *)NULL;
+ luaL_unref(L, LUA_REGISTRYINDEX, fun_id);
+ return 0;
+}
+
+void network_close_force(struct network *net)
+{
+ if (net != NULL) {
+ trie_apply(net->endpoints, close_key, net);
+ trie_apply(net->endpoints, free_key, NULL);
+ trie_clear(net->endpoints);
+ }
+}
+
+/** Frees all the `struct net_proxy_data` in the specified trie. */
+void network_proxy_free_addr_data(trie_t* trie)
+{
+ trie_it_t *it;
+ for (it = trie_it_begin(trie); !trie_it_finished(it); trie_it_next(it)) {
+ struct net_proxy_data *data = *trie_it_val(it);
+ free(data);
+ }
+ trie_it_free(it);
+}
+
+void network_deinit(struct network *net)
+{
+ if (net != NULL) {
+ network_close_force(net);
+ trie_apply(net->endpoint_kinds, kind_unregister, the_worker->engine->L);
+ trie_free(net->endpoint_kinds);
+ trie_free(net->endpoints);
+ network_proxy_free_addr_data(net->proxy_addrs4);
+ trie_free(net->proxy_addrs4);
+ network_proxy_free_addr_data(net->proxy_addrs6);
+ trie_free(net->proxy_addrs6);
+
+ tls_credentials_free(net->tls_credentials);
+ tls_client_params_free(net->tls_client_params);
+ tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx);
+ #ifndef NDEBUG
+ memset(net, 0, sizeof(*net));
+ #endif
+ }
+}
+
+/** Creates an endpoint key for use with a `trie_t` and stores it into `dst`.
+ * Returns the actual length of the generated key. */
+static ssize_t endpoint_key_create(struct endpoint_key_storage *dst,
+ const char *addr_str,
+ const struct sockaddr *sa)
+{
+ memset(dst, 0, sizeof(*dst));
+ if (sa) {
+ struct endpoint_key_sockaddr *key = &dst->sa;
+ key->type = ENDPOINT_KEY_SOCKADDR;
+ ssize_t keylen = kr_sockaddr_key(&key->sa_key, sa);
+ if (keylen < 0)
+ return keylen;
+ return sizeof(struct endpoint_key) + keylen;
+ } else {
+ struct endpoint_key_ifname *key = &dst->ifname;
+ key->type = ENDPOINT_KEY_IFNAME;
+
+ /* The subtractions and additions of 1 are here to account for
+ * null-terminators. */
+ strncpy(key->ifname, addr_str, sizeof(key->ifname) - 1);
+ return sizeof(struct endpoint_key) + strlen(key->ifname) + 1;
+ }
+}
+
+/** Fetch or create endpoint array and insert endpoint (shallow memcpy). */
+static int insert_endpoint(struct network *net, const char *addr_str,
+ const struct sockaddr *addr, struct endpoint *ep)
+{
+ /* Fetch or insert address into map */
+ struct endpoint_key_storage key;
+ ssize_t keylen = endpoint_key_create(&key, addr_str, addr);
+ if (keylen < 0)
+ return keylen;
+ trie_val_t *val = trie_get_ins(net->endpoints, key.bytes, keylen);
+ endpoint_array_t *ep_array;
+ if (*val) {
+ ep_array = *val;
+ } else {
+ ep_array = malloc(sizeof(*ep_array));
+ kr_require(ep_array);
+ array_init(*ep_array);
+ *val = ep_array;
+ }
+
+ if (array_reserve(*ep_array, ep_array->len + 1)) {
+ return kr_error(ENOMEM);
+ }
+ memcpy(&ep_array->at[ep_array->len++], ep, sizeof(*ep));
+ return kr_ok();
+}
+
+/** Open endpoint protocols. ep->flags were pre-set.
+ * \p addr_str is only used for logging or for XDP "address". */
+static int open_endpoint(struct network *net, const char *addr_str,
+ struct endpoint *ep, const struct sockaddr *sa)
+{
+ const bool is_control = ep->flags.kind && strcmp(ep->flags.kind, "control") == 0;
+ const bool is_xdp = ep->family == AF_XDP;
+ bool ok = (!is_xdp)
+ || (sa == NULL && ep->fd == -1 && ep->nic_queue >= 0
+ && ep->flags.sock_type == SOCK_DGRAM && !ep->flags.tls);
+ if (kr_fails_assert(ok))
+ return kr_error(EINVAL);
+ if (ep->handle) {
+ return kr_error(EEXIST);
+ }
+
+ if (sa && ep->fd == -1) {
+ if (sa->sa_family == AF_UNIX) {
+ struct sockaddr_un *sun = (struct sockaddr_un*)sa;
+ char *dirc = strdup(sun->sun_path);
+ char *dname = dirname(dirc);
+ (void)unlink(sun->sun_path); /** Attempt to unlink if socket path exists. */
+ (void)mkdir(dname, S_IRWXU|S_IRWXG); /** Attempt to create dir. */
+ free(dirc);
+ }
+ ep->fd = io_bind(sa, ep->flags.sock_type, &ep->flags);
+ if (ep->fd < 0) return ep->fd;
+ }
+ if (ep->flags.kind && !is_control && !is_xdp) {
+ /* This EP isn't to be managed internally after binding. */
+ return endpoint_open_lua_cb(net, ep, addr_str);
+ } else {
+ ep->engaged = true;
+ /* .engaged seems not really meaningful in this case, but... */
+ }
+
+ int ret;
+ if (is_control) {
+ uv_pipe_t *ep_handle = malloc(sizeof(uv_pipe_t));
+ ep->handle = (uv_handle_t *)ep_handle;
+ ret = !ep->handle ? ENOMEM
+ : io_listen_pipe(net->loop, ep_handle, ep->fd);
+ goto finish_ret;
+ }
+
+ if (ep->family == AF_UNIX) {
+ /* Some parts of connection handling would need more work,
+ * so let's support AF_UNIX only with .kind != NULL for now. */
+ kr_log_error(NETWORK, "AF_UNIX only supported with set { kind = '...' }\n");
+ ret = EAFNOSUPPORT;
+ goto finish_ret;
+ /*
+ uv_pipe_t *ep_handle = malloc(sizeof(uv_pipe_t));
+ */
+ }
+
+ if (is_xdp) {
+ #if ENABLE_XDP
+ uv_poll_t *ep_handle = malloc(sizeof(uv_poll_t));
+ ep->handle = (uv_handle_t *)ep_handle;
+ ret = !ep->handle ? ENOMEM
+ : io_listen_xdp(net->loop, ep, addr_str);
+ #else
+ ret = ESOCKTNOSUPPORT;
+ #endif
+ goto finish_ret;
+ } /* else */
+
+ if (ep->flags.sock_type == SOCK_DGRAM) {
+ if (kr_fails_assert(!ep->flags.tls))
+ return kr_error(EINVAL);
+ uv_udp_t *ep_handle = malloc(sizeof(uv_udp_t));
+ ep->handle = (uv_handle_t *)ep_handle;
+ ret = !ep->handle ? ENOMEM
+ : io_listen_udp(net->loop, ep_handle, ep->fd);
+ goto finish_ret;
+ } /* else */
+
+ if (ep->flags.sock_type == SOCK_STREAM) {
+ uv_tcp_t *ep_handle = malloc(sizeof(uv_tcp_t));
+ ep->handle = (uv_handle_t *)ep_handle;
+ ret = !ep->handle ? ENOMEM
+ : io_listen_tcp(net->loop, ep_handle, ep->fd,
+ net->tcp_backlog, ep->flags.tls, ep->flags.http);
+ goto finish_ret;
+ } /* else */
+
+ kr_assert(false);
+ return kr_error(EINVAL);
+finish_ret:
+ if (!ret) return ret;
+ free(ep->handle);
+ ep->handle = NULL;
+ return kr_error(ret);
+}
+
+/** @internal Fetch a pointer to endpoint of given parameters (or NULL).
+ * Beware that there might be multiple matches, though that's not common.
+ * The matching isn't really precise in the sense that it might not find
+ * and endpoint that would *collide* the passed one. */
+static struct endpoint * endpoint_get(struct network *net,
+ const char *addr_str,
+ const struct sockaddr *sa,
+ endpoint_flags_t flags)
+{
+ struct endpoint_key_storage key;
+ ssize_t keylen = endpoint_key_create(&key, addr_str, sa);
+ if (keylen < 0)
+ return NULL;
+ trie_val_t *val = trie_get_try(net->endpoints, key.bytes, keylen);
+ if (!val)
+ return NULL;
+ endpoint_array_t *ep_array = *val;
+
+ uint16_t port = kr_inaddr_port(sa);
+ for (int i = 0; i < ep_array->len; ++i) {
+ struct endpoint *ep = &ep_array->at[i];
+ if ((flags.xdp || ep->port == port) && endpoint_flags_eq(ep->flags, flags)) {
+ return ep;
+ }
+ }
+ return NULL;
+}
+
+/** \note pass (either sa != NULL xor ep.fd != -1) or XDP case (neither sa nor ep.fd)
+ * \note in XDP case addr_str is interface name
+ * \note ownership of ep.flags.* is taken on success. */
+static int create_endpoint(struct network *net, const char *addr_str,
+ struct endpoint *ep, const struct sockaddr *sa)
+{
+ int ret = open_endpoint(net, addr_str, ep, sa);
+ if (ret == 0) {
+ ret = insert_endpoint(net, addr_str, sa, ep);
+ }
+ if (ret != 0 && ep->handle) {
+ endpoint_close(net, ep, false);
+ }
+ return ret;
+}
+
+int network_listen_fd(struct network *net, int fd, endpoint_flags_t flags)
+{
+ if (kr_fails_assert(!flags.xdp))
+ return kr_error(EINVAL);
+ /* Extract fd's socket type. */
+ socklen_t len = sizeof(flags.sock_type);
+ int ret = getsockopt(fd, SOL_SOCKET, SO_TYPE, &flags.sock_type, &len);
+ if (ret != 0)
+ return kr_error(errno);
+ const bool is_dtls = flags.sock_type == SOCK_DGRAM && !flags.kind && flags.tls;
+ if (kr_fails_assert(!is_dtls))
+ return kr_error(EINVAL); /* Perhaps DTLS some day. */
+ if (flags.sock_type != SOCK_DGRAM && flags.sock_type != SOCK_STREAM)
+ 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(errno);
+
+ struct endpoint ep = {
+ .flags = flags,
+ .family = ss.ss_family,
+ .fd = fd,
+ };
+ /* Extract address string and port. */
+ char addr_buf[INET6_ADDRSTRLEN]; /* https://tools.ietf.org/html/rfc4291 */
+ const char *addr_str;
+ switch (ep.family) {
+ case AF_INET:
+ ret = uv_ip4_name((const struct sockaddr_in*)&ss, addr_buf, sizeof(addr_buf));
+ addr_str = addr_buf;
+ ep.port = ntohs(((struct sockaddr_in *)&ss)->sin_port);
+ break;
+ case AF_INET6:
+ ret = uv_ip6_name((const struct sockaddr_in6*)&ss, addr_buf, sizeof(addr_buf));
+ addr_str = addr_buf;
+ ep.port = ntohs(((struct sockaddr_in6 *)&ss)->sin6_port);
+ break;
+ case AF_UNIX:
+ /* No SOCK_DGRAM with AF_UNIX support, at least for now. */
+ ret = flags.sock_type == SOCK_STREAM ? kr_ok() : kr_error(EAFNOSUPPORT);
+ addr_str = ((struct sockaddr_un *)&ss)->sun_path;
+ break;
+ default:
+ ret = kr_error(EAFNOSUPPORT);
+ }
+ if (ret) return ret;
+
+ /* always create endpoint for supervisor supplied fd
+ * even if addr+port is not unique */
+ return create_endpoint(net, addr_str, &ep, (struct sockaddr *) &ss);
+}
+
+/** Try selecting XDP queue automatically. */
+static int16_t nic_queue_auto(void)
+{
+ const char *inst_str = getenv("SYSTEMD_INSTANCE");
+ if (!inst_str)
+ return 0; // should work OK for simple (single-kresd) deployments
+ char *endp;
+ errno = 0; // strtol() is special in this respect
+ long inst = strtol(inst_str, &endp, 10);
+ if (!errno && *endp == '\0' && inst > 0 && inst < UINT16_MAX)
+ return inst - 1; // 1-based vs. 0-based indexing conventions
+ return -1;
+}
+
+int network_listen(struct network *net, const char *addr, uint16_t port,
+ int16_t nic_queue, endpoint_flags_t flags)
+{
+ if (kr_fails_assert(net != NULL && addr != 0 && nic_queue >= -1))
+ return kr_error(EINVAL);
+
+ if (flags.xdp && nic_queue < 0) {
+ nic_queue = nic_queue_auto();
+ if (nic_queue < 0) {
+ return kr_error(EINVAL);
+ }
+ }
+
+ // Try parsing the address.
+ const struct sockaddr *sa = kr_straddr_socket(addr, port, NULL);
+ if (!sa && !flags.xdp) { // unusable address spec
+ return kr_error(EINVAL);
+ }
+ char ifname_buf[64] UNUSED;
+ if (sa && flags.xdp) { // auto-detection: address -> interface
+ #if ENABLE_XDP
+ int ret = knot_eth_name_from_addr((const struct sockaddr_storage *)sa,
+ ifname_buf, sizeof(ifname_buf));
+ // even on success we don't want to pass `sa` on
+ free_const(sa);
+ sa = NULL;
+ if (ret) {
+ return kr_error(ret);
+ }
+ addr = ifname_buf;
+ #else
+ return kr_error(ESOCKTNOSUPPORT);
+ #endif
+ }
+ // XDP: if addr failed to parse as address, we assume it's an interface name.
+
+ if (endpoint_get(net, addr, sa, flags)) {
+ return kr_error(EADDRINUSE); // Already listening
+ }
+
+ struct endpoint ep = { 0 };
+ ep.flags = flags;
+ ep.fd = -1;
+ ep.port = port;
+ ep.family = flags.xdp ? AF_XDP : sa->sa_family;
+ ep.nic_queue = nic_queue;
+
+ int ret = create_endpoint(net, addr, &ep, sa);
+
+ // Error reporting: more precision.
+ if (ret == KNOT_EINVAL && !sa && flags.xdp && ENABLE_XDP) {
+ if (!if_nametoindex(addr) && errno == ENODEV) {
+ ret = kr_error(ENODEV);
+ }
+ }
+
+ free_const(sa);
+ return ret;
+}
+
+int network_proxy_allow(struct network *net, const char* addr)
+{
+ if (kr_fails_assert(net != NULL && addr != NULL))
+ return kr_error(EINVAL);
+
+ int family = kr_straddr_family(addr);
+ if (family < 0) {
+ kr_log_error(NETWORK, "Wrong address format for proxy_allowed: %s\n",
+ addr);
+ return kr_error(EINVAL);
+ } else if (family == AF_UNIX) {
+ kr_log_error(NETWORK, "Unix sockets not supported for proxy_allowed: %s\n",
+ addr);
+ return kr_error(EINVAL);
+ }
+
+ union kr_in_addr ia;
+ int netmask = kr_straddr_subnet(&ia, addr);
+ if (netmask < 0) {
+ kr_log_error(NETWORK, "Wrong netmask format for proxy_allowed: %s\n", addr);
+ return kr_error(EINVAL);
+ } else if (netmask == 0) {
+ /* Netmask is zero: allow all addresses to use PROXYv2 */
+ switch (family) {
+ case AF_INET:
+ net->proxy_all4 = true;
+ break;
+ case AF_INET6:
+ net->proxy_all6 = true;
+ break;
+ default:
+ kr_assert(false);
+ return kr_error(EINVAL);
+ }
+
+ return kr_ok();
+ }
+
+ size_t addr_length;
+ trie_t *trie;
+ switch (family) {
+ case AF_INET:
+ addr_length = sizeof(ia.ip4);
+ trie = net->proxy_addrs4;
+ break;
+ case AF_INET6:
+ addr_length = sizeof(ia.ip6);
+ trie = net->proxy_addrs6;
+ break;
+ default:
+ kr_assert(false);
+ return kr_error(EINVAL);
+ }
+
+ kr_bitmask((unsigned char *) &ia, addr_length, netmask);
+ trie_val_t *val = trie_get_ins(trie, (char *) &ia, addr_length);
+ if (!val)
+ return kr_error(ENOMEM);
+
+ struct net_proxy_data *data = *val;
+ if (!data) {
+ /* Allocate data if the entry is new in the trie */
+ *val = malloc(sizeof(struct net_proxy_data));
+ data = *val;
+ data->netmask = 0;
+ }
+
+ if (data->netmask == 0) {
+ memcpy(&data->addr, &ia, addr_length);
+ data->netmask = netmask;
+ } else if (data->netmask > netmask) {
+ /* A more relaxed netmask configured - replace it */
+ data->netmask = netmask;
+ }
+
+ return kr_ok();
+}
+
+void network_proxy_reset(struct network *net)
+{
+ net->proxy_all4 = false;
+ network_proxy_free_addr_data(net->proxy_addrs4);
+ trie_clear(net->proxy_addrs4);
+ net->proxy_all6 = false;
+ network_proxy_free_addr_data(net->proxy_addrs6);
+ trie_clear(net->proxy_addrs6);
+}
+
+static int endpoints_close(struct network *net,
+ struct endpoint_key_storage *key, ssize_t keylen,
+ endpoint_array_t *ep_array, int port)
+{
+ size_t i = 0;
+ bool matched = false; /*< at least one match */
+ while (i < ep_array->len) {
+ struct endpoint *ep = &ep_array->at[i];
+ if (port < 0 || ep->port == port) {
+ endpoint_close(net, ep, false);
+ array_del(*ep_array, i);
+ matched = true;
+ /* do not advance i */
+ } else {
+ ++i;
+ }
+ }
+ if (!matched) {
+ return kr_error(ENOENT);
+ }
+
+ return kr_ok();
+}
+
+static bool endpoint_key_addr_matches(struct endpoint_key_storage *key_a,
+ struct endpoint_key_storage *key_b)
+{
+ if (key_a->type != key_b->type)
+ return false;
+
+ if (key_a->type == ENDPOINT_KEY_IFNAME)
+ return strncmp(key_a->ifname.ifname,
+ key_b->ifname.ifname,
+ sizeof(key_a->ifname.ifname)) == 0;
+
+ if (key_a->type == ENDPOINT_KEY_SOCKADDR) {
+ return kr_sockaddr_key_same_addr(
+ key_a->sa.sa_key.bytes, key_b->sa.sa_key.bytes);
+ }
+
+ kr_assert(false);
+ return kr_error(EINVAL);
+}
+
+struct endpoint_key_with_len {
+ struct endpoint_key_storage key;
+ size_t keylen;
+};
+typedef array_t(struct endpoint_key_with_len) endpoint_key_array_t;
+
+struct endpoint_close_wildcard_context {
+ struct network *net;
+ struct endpoint_key_storage *match_key;
+ endpoint_key_array_t del;
+ int ret;
+};
+
+static int endpoints_close_wildcard(const char *s_key, uint32_t keylen, trie_val_t *val, void *baton)
+{
+ struct endpoint_close_wildcard_context *ctx = baton;
+ struct endpoint_key_storage *key = (struct endpoint_key_storage *)s_key;
+
+ if (!endpoint_key_addr_matches(key, ctx->match_key))
+ return kr_ok();
+
+ endpoint_array_t *ep_array = *val;
+ int ret = endpoints_close(ctx->net, key, keylen, ep_array, -1);
+ if (ret)
+ ctx->ret = ret;
+
+ if (ep_array->len == 0) {
+ struct endpoint_key_with_len to_del = {
+ .key = *key,
+ .keylen = keylen
+ };
+ array_push(ctx->del, to_del);
+ }
+
+ return kr_ok();
+}
+
+int network_close(struct network *net, const char *addr_str, int port)
+{
+ auto_free struct sockaddr *addr = kr_straddr_socket(addr_str, port, NULL);
+ struct endpoint_key_storage key;
+ ssize_t keylen = endpoint_key_create(&key, addr_str, addr);
+ if (keylen < 0)
+ return keylen;
+
+ if (port < 0) {
+ struct endpoint_close_wildcard_context ctx = {
+ .net = net,
+ .match_key = &key
+ };
+ array_init(ctx.del);
+ trie_apply_with_key(net->endpoints, endpoints_close_wildcard, &ctx);
+ for (size_t i = 0; i < ctx.del.len; i++) {
+ trie_val_t val;
+ trie_del(net->endpoints,
+ ctx.del.at[i].key.bytes, ctx.del.at[i].keylen,
+ &val);
+ if (val) {
+ array_clear(*(endpoint_array_t *) val);
+ free(val);
+ }
+ }
+ return ctx.ret;
+ }
+
+ trie_val_t *val = trie_get_try(net->endpoints, key.bytes, keylen);
+ if (!val)
+ return kr_error(ENOENT);
+ endpoint_array_t *ep_array = *val;
+ int ret = endpoints_close(net, &key, keylen, ep_array, port);
+
+ /* Collapse key if it has no endpoint. */
+ if (ep_array->len == 0) {
+ array_clear(*ep_array);
+ free(ep_array);
+ trie_del(net->endpoints, key.bytes, keylen, NULL);
+ }
+
+ return ret;
+}
+
+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");
+ }
+ }
+}
+
+#ifdef SO_ATTACH_BPF
+static int set_bpf_cb(trie_val_t *val, void *ctx)
+{
+ endpoint_array_t *endpoints = *val;
+ int *bpffd = (int *)ctx;
+ if (kr_fails_assert(endpoints && bpffd))
+ return kr_error(EINVAL);
+
+ for (size_t i = 0; i < endpoints->len; i++) {
+ struct endpoint *endpoint = &endpoints->at[i];
+ uv_os_fd_t sockfd = -1;
+ if (endpoint->handle != NULL)
+ uv_fileno(endpoint->handle, &sockfd);
+ kr_require(sockfd != -1);
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_BPF, bpffd, sizeof(int)) != 0) {
+ return 1; /* return error (and stop iterating over net->endpoints) */
+ }
+ }
+ return 0; /* OK */
+}
+#endif
+
+int network_set_bpf(struct network *net, int bpf_fd)
+{
+#ifdef SO_ATTACH_BPF
+ if (trie_apply(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;
+}
+
+#ifdef SO_DETACH_BPF
+static int clear_bpf_cb(trie_val_t *val, void *ctx)
+{
+ endpoint_array_t *endpoints = *val;
+ if (kr_fails_assert(endpoints))
+ return kr_error(EINVAL);
+
+ for (size_t i = 0; i < endpoints->len; i++) {
+ struct endpoint *endpoint = &endpoints->at[i];
+ uv_os_fd_t sockfd = -1;
+ if (endpoint->handle != NULL)
+ uv_fileno(endpoint->handle, &sockfd);
+ kr_require(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. */
+ }
+ return 0;
+}
+#endif
+
+void network_clear_bpf(struct network *net)
+{
+#ifdef SO_DETACH_BPF
+ trie_apply(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..e21651f
--- /dev/null
+++ b/daemon/network.h
@@ -0,0 +1,162 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "daemon/tls.h"
+
+#include "lib/generic/array.h"
+#include "lib/generic/trie.h"
+
+#include <uv.h>
+#include <stdbool.h>
+
+#include <sys/socket.h>
+#ifndef AF_XDP
+#define AF_XDP 44
+#endif
+
+struct engine;
+struct session;
+
+/** Ways to listen on a socket (which may exist already). */
+typedef struct {
+ int sock_type; /**< SOCK_DGRAM or SOCK_STREAM */
+ bool tls; /**< only used together with .kind == NULL and SOCK_STREAM */
+ bool http; /**< DoH2, implies .tls (in current implementation) */
+ bool xdp; /**< XDP is special (not a normal socket, in particular) */
+ bool freebind; /**< used for binding to non-local address */
+ const char *kind; /**< tag for other types: "control" or module-handled kinds */
+} endpoint_flags_t;
+
+struct endpoint_key;
+
+static inline bool endpoint_flags_eq(endpoint_flags_t f1, endpoint_flags_t f2)
+{
+ if (f1.sock_type != f2.sock_type)
+ return false;
+ if (f1.kind && f2.kind)
+ return strcasecmp(f1.kind, f2.kind);
+ else
+ return f1.tls == f2.tls && f1.kind == f2.kind;
+}
+
+/** Wrapper for a single socket to listen on.
+ * There are two types: normal have handle, special have flags.kind (and never both).
+ *
+ * LATER: .family might be unexpected for IPv4-in-IPv6 addresses.
+ * ATM AF_UNIX is only supported with flags.kind != NULL
+ */
+struct endpoint {
+ /** uv_{udp,tcp,poll}_t (poll for XDP);
+ * NULL in case of endpoints that are to be handled by modules. */
+ uv_handle_t *handle;
+ int fd; /**< POSIX file-descriptor; always used. */
+ int family; /**< AF_INET or AF_INET6 or AF_UNIX or AF_XDP */
+ uint16_t port; /**< TCP/UDP port. Meaningless with AF_UNIX. */
+ int16_t nic_queue; /**< -1 or queue number of the interface for AF_XDP use. */
+ bool engaged; /**< to some module or internally */
+ endpoint_flags_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;
+};
+
+/** Information about an address that is allowed to use PROXYv2. */
+struct net_proxy_data {
+ union kr_in_addr addr;
+ uint8_t netmask; /**< Number of bits to be matched */
+};
+
+struct network {
+ uv_loop_t *loop;
+
+ /** Map: address string -> endpoint_array_t.
+ * \note even same address-port-flags tuples may appear. */
+ trie_t *endpoints;
+
+ /** Registry of callbacks for special endpoint kinds (for opening/closing).
+ * Map: kind (lowercased) -> lua function ID converted to void *
+ * The ID is the usual: raw int index in the LUA_REGISTRYINDEX table. */
+ trie_t *endpoint_kinds;
+ /** See network_engage_endpoints() */
+ bool missing_kind_is_error : 1;
+
+ /** True: All IPv4 addresses are allowed to use the PROXYv2 protocol */
+ bool proxy_all4 : 1;
+ /** True: All IPv6 addresses are allowed to use the PROXYv2 protocol */
+ bool proxy_all6 : 1;
+
+ /** IPv4 addresses and networks allowed to use the PROXYv2 protocol */
+ trie_t *proxy_addrs4;
+ /** IPv6 addresses and networks allowed to use the PROXYv2 protocol */
+ trie_t *proxy_addrs6;
+
+ struct tls_credentials *tls_credentials;
+ tls_client_params_t *tls_client_params; /**< Use tls_client_params_*() functions. */
+ 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);
+
+/** Start listening on addr#port with flags.
+ * \note if we did listen on that combination already,
+ * nothing is done and kr_error(EADDRINUSE) is returned.
+ * \note there's no short-hand to listen both on UDP and TCP.
+ * \note ownership of flags.* is taken on success. TODO: non-success?
+ * \param nic_queue == -1 for auto-selection or non-XDP.
+ * \note In XDP mode, addr may be also interface name, so kr_error(ENODEV)
+ * is returned if some nonsense is passed
+ */
+int network_listen(struct network *net, const char *addr, uint16_t port,
+ int16_t nic_queue, endpoint_flags_t flags);
+
+/** Allow the specified address to send the PROXYv2 header.
+ * \note the address may be specified with a netmask
+ */
+int network_proxy_allow(struct network *net, const char* addr);
+
+/** Reset all addresses allowed to send the PROXYv2 header. No addresses will
+ * be allowed to send PROXYv2 headers from the point of calling this function
+ * until re-allowed via network_proxy_allow again.
+ */
+void network_proxy_reset(struct network *net);
+
+/** Start listening on an open file-descriptor.
+ * \note flags.sock_type isn't meaningful here.
+ * \note ownership of flags.* is taken on success. TODO: non-success?
+ */
+int network_listen_fd(struct network *net, int fd, endpoint_flags_t flags);
+
+/** Stop listening on all endpoints with matching addr#port.
+ * port < 0 serves as a wild-card.
+ * \return kr_error(ENOENT) if nothing matched. */
+int network_close(struct network *net, const char *addr, int port);
+
+/** Close all endpoints immediately (no waiting for UV loop). */
+void network_close_force(struct network *net);
+
+/** Enforce that all endpoints are registered from now on.
+ * This only does anything with struct endpoint::flags.kind != NULL. */
+int network_engage_endpoints(struct network *net);
+
+/** Returns a string representation of the specified endpoint key.
+ *
+ * The result points into key or is on static storage like for kr_straddr() */
+const char *network_endpoint_key_str(const struct endpoint_key *key);
+
+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/proxyv2.c b/daemon/proxyv2.c
new file mode 100644
index 0000000..f977ccb
--- /dev/null
+++ b/daemon/proxyv2.c
@@ -0,0 +1,290 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "daemon/proxyv2.h"
+
+#include "lib/generic/trie.h"
+
+const char PROXY2_SIGNATURE[12] = {
+ 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A
+};
+
+#define PROXY2_IP6_ADDR_SIZE 16
+#define PROXY2_UNIX_ADDR_SIZE 108
+
+#define TLV_TYPE_SSL 0x20
+
+enum proxy2_family {
+ PROXY2_AF_UNSPEC = 0x0,
+ PROXY2_AF_INET = 0x1,
+ PROXY2_AF_INET6 = 0x2,
+ PROXY2_AF_UNIX = 0x3
+};
+
+enum proxy2_protocol {
+ PROXY2_PROTOCOL_UNSPEC = 0x0,
+ PROXY2_PROTOCOL_STREAM = 0x1,
+ PROXY2_PROTOCOL_DGRAM = 0x2
+};
+
+/** PROXYv2 protocol header section */
+struct proxy2_header {
+ uint8_t signature[sizeof(PROXY2_SIGNATURE)];
+ uint8_t version_command;
+ uint8_t family_protocol;
+ uint16_t length; /**< Length of the address section */
+};
+
+/** PROXYv2 additional information in Type-Length-Value (TLV) format. */
+struct proxy2_tlv {
+ uint8_t type;
+ uint8_t length_hi;
+ uint8_t length_lo;
+ uint8_t value[];
+};
+
+/** PROXYv2 protocol address section */
+union proxy2_address {
+ struct {
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ipv4_addr;
+ struct {
+ uint8_t src_addr[PROXY2_IP6_ADDR_SIZE];
+ uint8_t dst_addr[PROXY2_IP6_ADDR_SIZE];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ipv6_addr;
+ struct {
+ uint8_t src_addr[PROXY2_UNIX_ADDR_SIZE];
+ uint8_t dst_addr[PROXY2_UNIX_ADDR_SIZE];
+ } unix_addr;
+};
+
+
+/** Gets protocol version from the specified PROXYv2 header. */
+static inline unsigned char proxy2_header_version(const struct proxy2_header* h)
+{
+ return (h->version_command & 0xF0) >> 4;
+}
+
+/** Gets command from the specified PROXYv2 header. */
+static inline enum proxy2_command proxy2_header_command(const struct proxy2_header *h)
+{
+ return h->version_command & 0x0F;
+}
+
+/** Gets address family from the specified PROXYv2 header. */
+static inline enum proxy2_family proxy2_header_family(const struct proxy2_header *h)
+{
+ return (h->family_protocol & 0xF0) >> 4;
+}
+
+/** Gets transport protocol from the specified PROXYv2 header. */
+static inline enum proxy2_family proxy2_header_protocol(const struct proxy2_header *h)
+{
+ return h->family_protocol & 0x0F;
+}
+
+static inline union proxy2_address *proxy2_get_address(const struct proxy2_header *h)
+{
+ return (union proxy2_address *) ((uint8_t *) h + sizeof(struct proxy2_header));
+}
+
+static inline struct proxy2_tlv *get_tlvs(const struct proxy2_header *h, size_t addr_len)
+{
+ return (struct proxy2_tlv *) ((uint8_t *) proxy2_get_address(h) + addr_len);
+}
+
+/** Gets the length of the TLV's `value` attribute. */
+static inline uint16_t proxy2_tlv_length(const struct proxy2_tlv *tlv)
+{
+ return ((uint16_t) tlv->length_hi << 16) | tlv->length_lo;
+}
+
+static inline bool has_tlv(const struct proxy2_header *h,
+ const struct proxy2_tlv *tlv)
+{
+ uint64_t addr_length = ntohs(h->length);
+ ptrdiff_t hdr_len = sizeof(struct proxy2_header) + addr_length;
+
+ uint8_t *tlv_hdr_end = (uint8_t *) tlv + sizeof(struct proxy2_tlv);
+ ptrdiff_t distance = tlv_hdr_end - (uint8_t *) h;
+ if (hdr_len < distance)
+ return false;
+
+ uint8_t *tlv_end = tlv_hdr_end + proxy2_tlv_length(tlv);
+ distance = tlv_end - (uint8_t *) h;
+ return hdr_len >= distance;
+}
+
+static inline void next_tlv(struct proxy2_tlv **tlv)
+{
+ uint8_t *next = ((uint8_t *) *tlv + sizeof(struct proxy2_tlv) + proxy2_tlv_length(*tlv));
+ *tlv = (struct proxy2_tlv *) next;
+}
+
+
+bool proxy_allowed(const struct network *net, const struct sockaddr *saddr)
+{
+ union kr_in_addr addr;
+ trie_t *trie;
+ size_t addr_size;
+ switch (saddr->sa_family) {
+ case AF_INET:
+ if (net->proxy_all4)
+ return true;
+
+ trie = net->proxy_addrs4;
+ addr_size = sizeof(addr.ip4);
+ addr.ip4 = ((struct sockaddr_in *) saddr)->sin_addr;
+ break;
+ case AF_INET6:
+ if (net->proxy_all6)
+ return true;
+
+ trie = net->proxy_addrs6;
+ addr_size = sizeof(addr.ip6);
+ addr.ip6 = ((struct sockaddr_in6 *) saddr)->sin6_addr;
+ break;
+ default:
+ kr_assert(false); // Only IPv4 and IPv6 proxy addresses supported
+ return false;
+ }
+
+ trie_val_t *val;
+ int ret = trie_get_leq(trie, (char *) &addr, addr_size, &val);
+ if (ret != kr_ok() && ret != 1)
+ return false;
+
+ kr_assert(val);
+ const struct net_proxy_data *found = *val;
+ kr_assert(found);
+ return kr_bitcmp((char *) &addr, (char *) &found->addr, found->netmask) == 0;
+}
+
+ssize_t proxy_process_header(struct proxy_result *out, struct session *s,
+ const void *buf, const ssize_t nread)
+{
+ if (!buf)
+ return kr_error(EINVAL);
+
+ const struct proxy2_header *hdr = (struct proxy2_header *) buf;
+
+ uint64_t content_length = ntohs(hdr->length);
+ ssize_t hdr_len = sizeof(struct proxy2_header) + content_length;
+
+ /* PROXYv2 requires the header to be received all at once */
+ if (nread < hdr_len) {
+ return kr_error(KNOT_EMALF);
+ }
+
+ unsigned char version = proxy2_header_version(hdr);
+ if (version != 2) {
+ /* Version MUST be 2 for PROXYv2 protocol */
+ return kr_error(KNOT_EMALF);
+ }
+
+ enum proxy2_command command = proxy2_header_command(hdr);
+ if (command == PROXY2_CMD_LOCAL) {
+ /* Addresses for LOCAL are to be discarded */
+ *out = (struct proxy_result) { .command = PROXY2_CMD_LOCAL };
+ goto fill_wirebuf;
+ }
+
+ if (command != PROXY2_CMD_PROXY) {
+ /* PROXYv2 prohibits values other than LOCAL and PROXY */
+ return kr_error(KNOT_EMALF);
+ }
+
+ *out = (struct proxy_result) { .command = PROXY2_CMD_PROXY };
+
+ /* Parse flags */
+ enum proxy2_family family = proxy2_header_family(hdr);
+ switch(family) {
+ case PROXY2_AF_UNSPEC:
+ case PROXY2_AF_UNIX: /* UNIX is unsupported, fall back to UNSPEC */
+ out->family = AF_UNSPEC;
+ break;
+ case PROXY2_AF_INET:
+ out->family = AF_INET;
+ break;
+ case PROXY2_AF_INET6:
+ out->family = AF_INET6;
+ break;
+ default: /* PROXYv2 prohibits other values */
+ return kr_error(KNOT_EMALF);
+ }
+
+ enum proxy2_family protocol = proxy2_header_protocol(hdr);
+ switch (protocol) {
+ case PROXY2_PROTOCOL_DGRAM:
+ out->protocol = SOCK_DGRAM;
+ break;
+ case PROXY2_PROTOCOL_STREAM:
+ out->protocol = SOCK_STREAM;
+ break;
+ default: /* PROXYv2 prohibits other values */
+ return kr_error(KNOT_EMALF);
+ }
+
+ /* Parse addresses */
+ union proxy2_address* addr = proxy2_get_address(hdr);
+ size_t addr_length = 0;
+ switch(out->family) {
+ case AF_INET:
+ addr_length = sizeof(addr->ipv4_addr);
+ if (content_length < addr_length)
+ return kr_error(KNOT_EMALF);
+
+ out->src_addr.ip4 = (struct sockaddr_in) {
+ .sin_family = AF_INET,
+ .sin_addr = { .s_addr = addr->ipv4_addr.src_addr },
+ .sin_port = addr->ipv4_addr.src_port,
+ };
+ out->dst_addr.ip4 = (struct sockaddr_in) {
+ .sin_family = AF_INET,
+ .sin_addr = { .s_addr = addr->ipv4_addr.dst_addr },
+ .sin_port = addr->ipv4_addr.dst_port,
+ };
+ break;
+ case AF_INET6:
+ addr_length = sizeof(addr->ipv6_addr);
+ if (content_length < addr_length)
+ return kr_error(KNOT_EMALF);
+
+ out->src_addr.ip6 = (struct sockaddr_in6) {
+ .sin6_family = AF_INET6,
+ .sin6_port = addr->ipv6_addr.src_port
+ };
+ memcpy(
+ &out->src_addr.ip6.sin6_addr.s6_addr,
+ &addr->ipv6_addr.src_addr,
+ sizeof(out->src_addr.ip6.sin6_addr.s6_addr));
+ out->dst_addr.ip6 = (struct sockaddr_in6) {
+ .sin6_family = AF_INET6,
+ .sin6_port = addr->ipv6_addr.dst_port
+ };
+ memcpy(
+ &out->dst_addr.ip6.sin6_addr.s6_addr,
+ &addr->ipv6_addr.dst_addr,
+ sizeof(out->dst_addr.ip6.sin6_addr.s6_addr));
+ break;
+ }
+
+ /* Process additional information */
+ for (struct proxy2_tlv *tlv = get_tlvs(hdr, addr_length); has_tlv(hdr, tlv); next_tlv(&tlv)) {
+ switch (tlv->type) {
+ case TLV_TYPE_SSL:
+ out->has_tls = true;
+ break;
+ /* TODO: add more TLV types if needed */
+ }
+ }
+
+fill_wirebuf:
+ return session_wirebuf_trim(s, hdr_len);
+}
diff --git a/daemon/proxyv2.h b/daemon/proxyv2.h
new file mode 100644
index 0000000..2d57744
--- /dev/null
+++ b/daemon/proxyv2.h
@@ -0,0 +1,50 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include "daemon/session.h"
+#include "daemon/network.h"
+#include "lib/utils.h"
+
+extern const char PROXY2_SIGNATURE[12];
+
+#define PROXY2_MIN_SIZE 16
+
+enum proxy2_command {
+ PROXY2_CMD_LOCAL = 0x0,
+ PROXY2_CMD_PROXY = 0x1
+};
+
+/** Parsed result of the PROXY protocol */
+struct proxy_result {
+ enum proxy2_command command; /**< Proxy command - PROXY or LOCAL. */
+ int family; /**< Address family from netinet library (e.g. AF_INET6). */
+ int protocol; /**< Protocol type from socket library (e.g. SOCK_STREAM). */
+ union kr_sockaddr src_addr; /**< Parsed source address and port. */
+ union kr_sockaddr dst_addr; /**< Parsed destination address and port. */
+ bool has_tls : 1; /**< `true` = client has used TLS with the proxy.
+ If TLS padding is enabled, it will be used even if
+ the proxy did not use TLS with kresd. */
+};
+
+/** Checks for a PROXY protocol version 2 signature in the specified buffer. */
+static inline bool proxy_header_present(const void* buf, const ssize_t nread)
+{
+ return nread >= PROXY2_MIN_SIZE &&
+ memcmp(buf, PROXY2_SIGNATURE, sizeof(PROXY2_SIGNATURE)) == 0;
+}
+
+/** Checks whether the use of PROXYv2 protocol is allowed for the specified
+ * address. */
+bool proxy_allowed(const struct network *net, const struct sockaddr *saddr);
+
+/** Parses the PROXYv2 header from buf of size nread and writes the result into
+ * out. The rest of the buffer is moved to free bytes of the specified session's
+ * wire buffer. The function assumes that the PROXYv2 signature is present
+ * and has been already checked by the caller (like `udp_recv` or `tcp_recv`). */
+ssize_t proxy_process_header(struct proxy_result *out, struct session *s,
+ const void *buf, ssize_t nread);
diff --git a/daemon/proxyv2.test/deckard.yaml b/daemon/proxyv2.test/deckard.yaml
new file mode 100644
index 0000000..8eb2fa6
--- /dev/null
+++ b/daemon/proxyv2.test/deckard.yaml
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+programs:
+ - name: dnsdist
+ binary: dnsdist
+ additional:
+ - --verbose
+ - --supervised
+ - --config
+ - dnsdist.conf
+ ignore_exit_code: True
+ templates:
+ - daemon/proxyv2.test/dnsdist_config.j2
+ configs:
+ - dnsdist.conf
+ - name: kresd
+ binary: kresd
+ additional:
+ - --noninteractive
+ templates:
+ - daemon/proxyv2.test/kresd_config.j2
+ - tests/integration/hints_zone.j2
+ configs:
+ - config
+ - hints
diff --git a/daemon/proxyv2.test/dnsdist_config.j2 b/daemon/proxyv2.test/dnsdist_config.j2
new file mode 100644
index 0000000..0bd4a55
--- /dev/null
+++ b/daemon/proxyv2.test/dnsdist_config.j2
@@ -0,0 +1,11 @@
+-- vim:syntax=lua
+setLocal('{{SELF_ADDR}}')
+setVerboseHealthChecks(true)
+setServerPolicy(firstAvailable)
+
+local server = newServer({
+ address="{{PROGRAMS['kresd']['address']}}",
+ useProxyProtocol=true,
+ checkName="example.cz."
+})
+server:setUp()
diff --git a/daemon/proxyv2.test/kresd_config.j2 b/daemon/proxyv2.test/kresd_config.j2
new file mode 100644
index 0000000..e7cbf63
--- /dev/null
+++ b/daemon/proxyv2.test/kresd_config.j2
@@ -0,0 +1,63 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+{% raw %}
+modules.load('view < policy')
+view:addr("127.127.0.0", policy.suffix(policy.DENY_MSG("addr 127.127.0.0 matched com"),{"\3com\0"}))
+-- policy.add(policy.all(policy.FORWARD('1.2.3.4')))
+
+-- make sure DNSSEC is turned off for tests
+trust_anchors.remove('.')
+
+-- Disable RFC5011 TA update
+if ta_update then
+ modules.unload('ta_update')
+end
+
+-- Disable RFC8145 signaling, scenario doesn't provide expected answers
+if ta_signal_query then
+ modules.unload('ta_signal_query')
+end
+
+-- Disable RFC8109 priming, scenario doesn't provide expected answers
+if priming then
+ modules.unload('priming')
+end
+
+-- Disable this module because it make one priming query
+if detect_time_skew then
+ modules.unload('detect_time_skew')
+end
+
+_hint_root_file('hints')
+cache.size = 2*MB
+log_level('debug')
+{% endraw %}
+
+-- Allow PROXYv2 from dnsdist's address
+--net.proxy_allowed("{{PROGRAMS['dnsdist']['address']}}")
+net.proxy_allowed("127.127.0.0/16")
+
+net = { '{{SELF_ADDR}}' }
+
+{% if QMIN == "false" %}
+option('NO_MINIMIZE', true)
+{% else %}
+option('NO_MINIMIZE', false)
+{% endif %}
+
+
+-- Self-checks on globals
+assert(help() ~= nil)
+assert(worker.id ~= nil)
+-- Self-checks on facilities
+assert(cache.count() == 0)
+assert(cache.stats() ~= nil)
+assert(cache.backends() ~= nil)
+assert(worker.stats() ~= nil)
+assert(net.interfaces() ~= nil)
+-- Self-checks on loaded stuff
+assert(net.list()[1].transport.ip == '{{SELF_ADDR}}')
+assert(#modules.list() > 0)
+-- Self-check timers
+ev = event.recurrent(1 * sec, function (ev) return 1 end)
+event.cancel(ev)
+ev = event.after(0, function (ev) return 1 end)
diff --git a/daemon/proxyv2.test/proxyv2_valid.rpl b/daemon/proxyv2.test/proxyv2_valid.rpl
new file mode 100644
index 0000000..ada8a37
--- /dev/null
+++ b/daemon/proxyv2.test/proxyv2_valid.rpl
@@ -0,0 +1,72 @@
+; SPDX-License-Identifier: GPL-3.0-or-later
+; config options
+ stub-addr: 1.2.3.4
+ query-minimization: off
+CONFIG_END
+
+SCENARIO_BEGIN proxyv2:valid test
+
+RANGE_BEGIN 0 110
+ ADDRESS 1.2.3.4
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.cz. IN A
+SECTION ANSWER
+example.cz. IN A 5.6.7.8
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+k.root-servers.net. IN AAAA
+SECTION ANSWER
+k.root-servers.net. IN AAAA ::1
+ENTRY_END
+
+RANGE_END
+
+; query with PROXYv2 header - not blocked
+STEP 10 QUERY
+ENTRY_BEGIN
+ADJUST raw_id
+REPLY RD
+SECTION QUESTION
+example.cz. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH flags rcode question answer
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+example.cz. IN A
+SECTION ANSWER
+example.cz. IN A 5.6.7.8
+ENTRY_END
+
+; query with PROXYv2 header - blocked by view:addr
+; NXDOMAIN expected
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+example.com. IN A
+ENTRY_END
+
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH opcode question rcode additional
+REPLY QR RD RA AA NXDOMAIN
+SECTION QUESTION
+example.com. IN A
+SECTION ADDITIONAL
+explanation.invalid. 10800 IN TXT "addr 127.127.0.0 matched com"
+ENTRY_END
+
+SCENARIO_END
diff --git a/daemon/scripting.rst b/daemon/scripting.rst
new file mode 100644
index 0000000..1950a2b
--- /dev/null
+++ b/daemon/scripting.rst
@@ -0,0 +1,398 @@
+.. SPDX-License-Identifier: GPL-3.0-or-later
+
+.. _runtime-cfg:
+
+Run-time reconfiguration
+========================
+
+Knot Resolver offers several ways to modify its configuration at run-time:
+
+ - Using control socket driven by an external system
+ - Using Lua program embedded in Resolver's configuration file
+
+Both ways can also be combined: For example the configuration file can contain
+a little Lua function which gathers statistics and returns them in JSON string.
+This can be used by an external system which uses control socket to call this
+user-defined function and to retrieve its results.
+
+
+.. _control-sockets:
+
+Control sockets
+---------------
+Control socket acts like "an interactive configuration file" so all actions
+available in configuration file can be executed interactively using the control
+socket. One possible use-case is reconfiguring the resolver instances from
+another program, e.g. a maintenance script.
+
+.. note:: Each instance of Knot Resolver exposes its own control socket. Take
+ that into account when scripting deployments with
+ :ref:`systemd-multiple-instances`.
+
+When Knot Resolver is started using Systemd (see section
+:ref:`quickstart-startup`) it creates a control socket in path
+``/run/knot-resolver/control/$ID``. Connection to the socket can
+be made from command line using e.g. ``socat``:
+
+.. code-block:: bash
+
+ $ socat - UNIX-CONNECT:/run/knot-resolver/control/1
+
+When successfully connected to a socket, the command line should change to
+something like ``>``. Then you can interact with kresd to see configuration or
+set a new one. There are some basic commands to start with.
+
+.. code-block:: lua
+
+ > help() -- shows help
+ > net.interfaces() -- lists available interfaces
+ > net.list() -- lists running network services
+
+
+The *direct output* of commands sent over socket is captured and sent back,
+which gives you an immediate response on the outcome of your command.
+The commands and their output are also logged in ``contrl`` group,
+on ``debug`` level if successful or ``warning`` level if failed
+(see around :func:`log_level`).
+
+Control sockets are also a way to enumerate and test running instances, the
+list of sockets corresponds to the list of processes, and you can test the
+process for liveliness by connecting to the UNIX socket.
+
+.. function:: map(lua_snippet)
+
+ Executes the provided string as lua code on every running resolver instance
+ and returns the results as a table.
+
+ Key ``n`` is always present in the returned table and specifies the total
+ number of instances the command was executed on. The table also contains
+ results from each instance accessible through keys ``1`` to ``n``
+ (inclusive). If any instance returns ``nil``, it is not explicitly part of
+ the table, but you can detect it by iterating through ``1`` to ``n``.
+
+ .. code-block:: lua
+
+ > map('worker.id') -- return an ID of every active instance
+ {
+ '2',
+ '1',
+ ['n'] = 2,
+ }
+ > map('worker.id == "1" or nil') -- example of `nil` return value
+ {
+ [2] = true,
+ ['n'] = 2,
+ }
+
+ The order of instances isn't guaranteed or stable. When you need to identify
+ the instances, you may use ``kluautil.kr_table_pack()`` function to return multiple
+ values as a table. It uses similar semantics with ``n`` as described above
+ to allow ``nil`` values.
+
+ .. code-block:: lua
+
+ > map('require("kluautil").kr_table_pack(worker.id, stats.get("answer.total"))')
+ {
+ {
+ '2',
+ 42,
+ ['n'] = 2,
+ },
+ {
+ '1',
+ 69,
+ ['n'] = 2,
+ },
+ ['n'] = 2,
+ }
+
+ If the command fails on any instance, an error is returned and the execution
+ is in an undefined state (the command might not have been executed on all
+ instances). When using the ``map()`` function to execute any code that might
+ fail, your code should be wrapped in `pcall()
+ <https://www.lua.org/manual/5.1/manual.html#pdf-pcall>`_ to avoid this
+ issue.
+
+ .. code-block:: lua
+
+ > map('require("kluautil").kr_table_pack(pcall(net.tls, "cert.pem", "key.pem"))')
+ {
+ {
+ true, -- function succeeded
+ true, -- function return value(s)
+ ['n'] = 2,
+ },
+ {
+ false, -- function failed
+ 'error occurred...', -- the returned error message
+ ['n'] = 2,
+ },
+ ['n'] = 2,
+ }
+
+
+Lua scripts
+-----------
+
+As it was mentioned in section :ref:`config-syntax`, Resolver's configuration
+file contains program in Lua programming language. This allows you to write
+dynamic rules and helps you to avoid repetitive templating that is unavoidable
+with static configuration. For example parts of configuration can depend on
+:func:`hostname` of the machine:
+
+.. code-block:: lua
+
+ if hostname() == 'hidden' then
+ net.listen(net.eth0, 5353)
+ else
+ net.listen('127.0.0.1')
+ net.listen(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 Lua libraries (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
+
+Helper functions
+^^^^^^^^^^^^^^^^
+Following built-in functions are useful for scripting:
+
+.. envvar:: env (table)
+
+ Retrieve environment variables.
+
+ Example:
+
+ .. code-block:: lua
+
+ env.USER -- equivalent to $USER in shell
+
+.. function:: fromjson(JSONstring)
+
+ :return: Lua representation of data in JSON string.
+
+ Example:
+
+ .. code-block:: lua
+
+ > fromjson('{"key1": "value1", "key2": {"subkey1": 1, "subkey2": 2}}')
+ [key1] => value1
+ [key2] => {
+ [subkey1] => 1
+ [subkey2] => 2
+ }
+
+
+.. 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 also affects ephemeral (self-signed) certificates generated by kresd
+ for DNS over TLS.
+
+.. function:: package_version()
+
+ :return: Current package version as string.
+
+ Example:
+
+ .. code-block:: lua
+
+ > package_version()
+ 2.1.1
+
+.. 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, ``true`` if resolution was started
+
+ 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 (pkt, req)
+ -- Check answer RCODE
+ 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:: tojson(object)
+
+ :return: JSON text representation of `object`.
+
+ Example:
+
+ .. code-block:: lua
+
+ > testtable = { key1 = "value1", "key2" = { subkey1 = 1, subkey2 = 2 } }
+ > tojson(testtable)
+ {"key1":"value1","key2":{"subkey1":1,"subkey2":2}}
+
+
+.. _async-events:
+
+Asynchronous events
+-------------------
+
+Lua language used in configuration file allows you to script actions upon
+various events, for example publish statistics each minute. Following example
+uses built-in function :func:`event.recurrent()` which calls user-supplied
+anonymous function:
+
+.. code-block:: lua
+
+ local ffi = require('ffi')
+ modules.load('stats')
+
+ -- log statistics every second
+ local stat_id = event.recurrent(1 * second, function(evid)
+ log_info(ffi.C.LOG_GRP_STATISTICS, table_print(stats.list()))
+ end)
+
+ -- stop printing statistics after first minute
+ event.after(1 * minute, function(evid)
+ event.cancel(stat_id)
+ end)
+
+
+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.
+
+To persist state between two invocations of a function Lua uses concept called
+closures_. In the following example function ``speed_monitor()`` is a closure
+function, which provides persistent variable called ``previous``.
+
+.. code-block:: lua
+
+ local ffi = require('ffi')
+ 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_warn(ffi.C.LOG_GRP_STATISTICS, '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 monitoring probes that cooperate well with the daemon internal operations.
+See :func:`event.socket()`.
+
+Filesystem watchers are possible with :func:`worker.coroutine()` and cqueues_,
+see the cqueues documentation for more information. Here is an simple example:
+
+.. 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 information about the modified file
+ print(name, notify[flag])
+ end
+ end
+ end)
+
+.. include:: ../daemon/bindings/event.rst
+
+.. include:: ../modules/etcd/README.rst
+
+.. _closures: https://www.lua.org/pil/6.1.html
+.. _cqueues: https://25thandclement.com/~william/projects/cqueues.html
+.. _LuaRocks: https://luarocks.org/
diff --git a/daemon/session.c b/daemon/session.c
new file mode 100644
index 0000000..ed0ff68
--- /dev/null
+++ b/daemon/session.c
@@ -0,0 +1,850 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <libknot/packet/pkt.h>
+
+#include "lib/defines.h"
+#include "daemon/session.h"
+#include "daemon/tls.h"
+#include "daemon/http.h"
+#include "daemon/worker.h"
+#include "daemon/io.h"
+#include "daemon/proxyv2.h"
+#include "lib/generic/queue.h"
+
+#define TLS_CHUNK_SIZE (16 * 1024)
+
+/* Initial max frame size: https://tools.ietf.org/html/rfc7540#section-6.5.2 */
+#define HTTP_MAX_FRAME_SIZE 16384
+
+/* Per-socket (TCP or UDP) persistent structure.
+ *
+ * In particular note that for UDP clients it's just one session (per socket)
+ * shared for all clients. For TCP/TLS it's also for the connection-specific socket,
+ * i.e one session per connection.
+ *
+ * LATER(optim.): the memory here is used a bit wastefully.
+ */
+struct session {
+ struct session_flags sflags; /**< miscellaneous flags. */
+ union kr_sockaddr peer; /**< address of peer; not for UDP clients (downstream) */
+ union kr_sockaddr sockname; /**< our local address; for UDP it may be a wildcard */
+ uv_handle_t *handle; /**< libuv handle for IO operations. */
+ uv_timer_t timeout; /**< libuv handle for timer. */
+
+ struct tls_ctx *tls_ctx; /**< server side tls-related data. */
+ struct tls_client_ctx *tls_client_ctx; /**< client side tls-related data. */
+
+ struct proxy_result *proxy; /**< PROXYv2 data for TCP. May be `NULL` if not proxied. */
+
+#if ENABLE_DOH2
+ struct http_ctx *http_ctx; /**< server side http-related data. */
+#endif
+
+ trie_t *tasks; /**< list of tasks associated 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, except for XDP. */
+ 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. */
+ bool was_useful; /**< I.e. produced a DNS message at some point. */
+};
+
+static void on_session_close(uv_handle_t *handle)
+{
+ struct session *session = handle->data;
+ kr_require(session->handle == handle);
+ io_free(handle);
+}
+
+static void on_session_timer_close(uv_handle_t *timer)
+{
+ struct session *session = timer->data;
+ uv_handle_t *handle = session->handle;
+ kr_require(handle && handle->data == session);
+ kr_require(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) {
+ session_clear(session);
+ free(session);
+ }
+}
+
+void session_clear(struct session *session)
+{
+ kr_require(session_is_empty(session));
+ if (session->handle && session->handle->type == UV_TCP) {
+ free(session->wire_buf);
+ }
+ free(session->proxy);
+#if ENABLE_DOH2
+ http_free(session->http_ctx);
+#endif
+ 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)
+{
+ kr_require(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_client_close(session->tls_client_ctx);
+ }
+ 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 (kr_fails_assert(v))
+ return kr_error(ENOMEM);
+ if (*v == NULL) {
+ *v = task;
+ worker_task_ref(task);
+ } else if (kr_fails_assert(*v == task)) {
+ 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 == KNOT_EOK) {
+ kr_require(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 != KNOT_EOK) {
+ 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)
+{
+ if (kr_fails_assert(session->sflags.outgoing))
+ return NULL;
+ trie_t *t = session->tasks;
+ 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 == KNOT_EOK) {
+ 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)
+{
+ if (kr_fails_assert(session->sflags.outgoing))
+ return NULL;
+ trie_t *t = session->tasks;
+ 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 sockaddr *session_get_sockname(struct session *session)
+{
+ return &session->sockname.ip;
+}
+
+struct tls_ctx *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 *ctx)
+{
+ session->tls_ctx = ctx;
+}
+
+struct tls_client_ctx *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 *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;
+}
+
+#if ENABLE_DOH2
+struct http_ctx *session_http_get_server_ctx(const struct session *session)
+{
+ return session->http_ctx;
+}
+
+void session_http_set_server_ctx(struct session *session, struct http_ctx *ctx)
+{
+ session->http_ctx = ctx;
+}
+#endif
+
+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, bool has_http)
+{
+ 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;
+ }
+#if ENABLE_DOH2
+ if (has_http) {
+ /* When decoding large packets,
+ * HTTP/2 frames can be up to 16 KB by default. */
+ wire_buffer_size += HTTP_MAX_FRAME_SIZE;
+ session->sflags.has_http = true;
+ }
+#endif
+ 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... */
+ kr_require(the_worker);
+ session->wire_buf = the_worker->wire_buf;
+ session->wire_buf_size = sizeof(the_worker->wire_buf);
+ } else {
+ kr_assert(handle->type == UV_POLL/*XDP*/);
+ /* - wire_buf* are left zeroed, as they make no sense
+ * - timer is unused but OK for simplicity (server-side sessions are few)
+ */
+ }
+
+ 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);
+ }
+}
+
+struct proxy_result *session_proxy_create(struct session *session)
+{
+ if (!kr_fails_assert(!session->proxy)) {
+ session->proxy = calloc(1, sizeof(struct proxy_result));
+ kr_require(session->proxy);
+ }
+
+ return session->proxy;
+}
+
+struct proxy_result *session_proxy_get(struct session *session)
+{
+ return session->proxy;
+}
+
+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);
+ kr_require(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) {
+ struct kr_request *req = worker_task_request(task);
+ if (!kr_fails_assert(req))
+ kr_query_inform_timeout(req, req->current_query);
+ 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;
+ // Session might be closing and get here e.g. through a late on_send callback.
+ const bool is_closing = uv_is_closing((uv_handle_t *)timer);
+ if (is_closing || kr_fails_assert(is_closing == session->sflags.closing))
+ return kr_error(EINVAL);
+
+ if (kr_fails_assert(timer->data == session))
+ return kr_error(EINVAL);
+ int ret = uv_timer_start(timer, cb, timeout, repeat);
+ if (ret != 0) {
+ uv_timer_stop(timer);
+ return kr_error(ret);
+ }
+ return kr_ok();
+}
+
+int session_timer_restart(struct session *session)
+{
+ kr_require(!uv_is_closing((uv_handle_t *)&session->timeout));
+ 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 (kr_fails_assert(data == &session->wire_buf[session->wire_buf_end_idx]))
+ return kr_error(EINVAL);
+ if (kr_fails_assert(len >= 0))
+ return kr_error(EINVAL);
+ if (kr_fails_assert(session->wire_buf_end_idx + len <= session->wire_buf_size))
+ return kr_error(EINVAL);
+
+ session->wire_buf_end_idx += len;
+ return len;
+}
+
+ssize_t session_wirebuf_trim(struct session *session, ssize_t len)
+{
+ if (kr_fails_assert(len >= 0))
+ return kr_error(EINVAL);
+ if (kr_fails_assert(session->wire_buf_start_idx + len <= session->wire_buf_size))
+ return kr_error(EINVAL);
+
+ session->wire_buf_start_idx += len;
+ if (session->wire_buf_start_idx > session->wire_buf_end_idx)
+ session->wire_buf_end_idx = session->wire_buf_start_idx;
+ return len;
+}
+
+void session_tcp_penalize(struct session *s)
+{
+ if (s->was_useful || !s->sflags.outgoing)
+ return;
+ /* We want to penalize the IP address, if a task is asking a query.
+ * It might not be the right task, but that doesn't matter so much
+ * for attributing the useless session to the IP address. */
+ struct qr_task *t = session_tasklist_get_first(s);
+ struct kr_query *qry = NULL;
+ if (t) {
+ struct kr_request *req = worker_task_request(t);
+ qry = array_tail(req->rplan.pending);
+ }
+ if (qry) /* We reuse the error for connection, as it's quite similar. */
+ qry->server_selection.error(qry, worker_task_get_transport(t),
+ KR_SELECTION_TCP_CONNECT_FAILED);
+}
+
+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;
+ session_tcp_penalize(session);
+ return NULL;
+ }
+ if (msg_size + 2 > wirebuf_msg_data_size) {
+ return NULL;
+ }
+ if (msg_size == 0) {
+ session->sflags.wirebuf_error = true;
+ session_tcp_penalize(session);
+ 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;
+ session_tcp_penalize(session);
+ return NULL;
+ }
+
+ session->was_useful = true;
+ 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 (kr_fails_assert(wirebuf_data_size >= 2)) {
+ /* TCP message length field isn't in buffer, must not happen. */
+ 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 (kr_fails_assert(wirebuf_msg_size + 2 <= wirebuf_data_size)) {
+ /* TCP message length field is greater then
+ * number of bytes in buffer, must not happen. */
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return kr_error(EINVAL);
+ }
+ }
+
+ if (kr_fails_assert(wirebuf_msg_start == pkt_msg_start)) {
+ /* packet wirebuf must be located at the beginning
+ * of the session wirebuf, must not happen. */
+ session->wire_buf_start_idx = 0;
+ session->wire_buf_end_idx = 0;
+ return kr_error(EINVAL);
+ }
+
+ if (kr_fails_assert(wirebuf_msg_size >= pkt_msg_size)) {
+ /* Message length field is lesser then packet size,
+ * must not happen. */
+ 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, struct io_comm_data *comm)
+{
+ int ret = 0;
+ if (session->wire_buf_start_idx == session->wire_buf_end_idx)
+ return ret;
+
+ 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 *pkt = NULL;
+
+ while (((pkt = session_produce_packet(session, &the_worker->pkt_pool)) != NULL) &&
+ (ret < max_iterations)) {
+ if (kr_fails_assert(!session_wirebuf_error(session)))
+ return -1;
+ int res = worker_submit(session, comm, NULL, NULL, pkt);
+ /* Errors from worker_submit() are intentionally *not* handled in order to
+ * ensure the entire wire buffer is processed. */
+ if (res == kr_ok())
+ ret += 1;
+ if (session_discard_packet(session, pkt) < 0) {
+ /* Packet data isn't stored in memory as expected.
+ * something went wrong, normally should not happen. */
+ break;
+ }
+ }
+
+ /* worker_submit() may cause the session to close (e.g. due to IO
+ * write error when the packet triggers an immediate answer). This is
+ * an error state, as well as any wirebuf error. */
+ if (session->sflags.closing || session_wirebuf_error(session))
+ ret = -1;
+
+ return ret;
+}
+
+void session_kill_ioreq(struct session *session, struct qr_task *task)
+{
+ if (!session || session->sflags.closing)
+ return;
+ if (kr_fails_assert(session->sflags.outgoing && session->handle))
+ return;
+ session_tasklist_del(session, task);
+ if (session->handle->type == UV_UDP) {
+ session_close(session);
+ return;
+ }
+}
+
+/** Update timestamp */
+void session_touch(struct session *session)
+{
+ session->last_activity = kr_now();
+}
+
+uint64_t session_last_activity(struct session *session)
+{
+ return session->last_activity;
+}
diff --git a/daemon/session.h b/daemon/session.h
new file mode 100644
index 0000000..1f95ac5
--- /dev/null
+++ b/daemon/session.h
@@ -0,0 +1,166 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/packet/pkt.h>
+
+#include <stdbool.h>
+#include <uv.h>
+#include "lib/defines.h"
+
+struct qr_task;
+struct worker_ctx;
+struct session;
+struct io_comm_data;
+struct proxy_result;
+
+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 has_http : 1; /**< True: given session uses HTTP. */
+ bool connected : 1; /**< True: TCP connection is established. */
+ bool no_proxy : 1; /**< True: TCP has gotten some data - PROXYv2 header
+ * disallowed. Proxy headers are only expected at
+ * the very start of a stream. */
+ 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->type isn't UV_TCP, has_* parameters will be ignored. */
+struct session *session_new(uv_handle_t *handle, bool has_tls, bool has_http);
+/** 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);
+
+/** PROXYv2 data. */
+/** Creates zero-initialized PROXYv2 data for the session. Should only be called
+ * once per session. */
+struct proxy_result *session_proxy_create(struct session *session);
+/** Gets the session's PROXYv2 data, if it exists. If it does not, returns `NULL`. */
+struct proxy_result *session_proxy_get(struct session *session);
+
+/** 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);
+/** Penalize this server if the session hasn't been useful (and is outgoing). */
+void session_tcp_penalize(struct session *session);
+
+/** Get pointer to session flags */
+struct session_flags *session_flags(struct session *session);
+/** Get pointer to peer address. */
+struct sockaddr *session_get_peer(struct session *session);
+/** Get pointer to sockname (address of our end, not meaningful for UDP downstream). */
+struct sockaddr *session_get_sockname(struct session *session);
+/** Get pointer to server-side tls-related data. */
+struct tls_ctx *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 *ctx);
+/** Get pointer to client-side tls-related data. */
+struct tls_client_ctx *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 *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);
+
+#if ENABLE_DOH2
+/** Get pointer to server-side http-related data. */
+struct http_ctx *session_http_get_server_ctx(const struct session *session);
+/** Set pointer to server-side http-related data. */
+void session_http_set_server_ctx(struct session *session, struct http_ctx *ctx);
+#endif
+
+/** Get pointer to underlying libuv handle for IO operations. */
+KR_EXPORT 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, struct io_comm_data *comm);
+ssize_t session_wirebuf_consume(struct session *session,
+ const uint8_t *data, ssize_t len);
+/** Trims `len` bytes from the start of the session's wire buffer.
+ * If this operation makes the buffer's end appear before the start, it gets
+ * nudged to the same position as the start. */
+ssize_t session_wirebuf_trim(struct session *session, 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 *session, struct qr_task *task);
+/** Update timestamp */
+void session_touch(struct session *session);
+/** 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 *session);
diff --git a/daemon/tls.c b/daemon/tls.c
new file mode 100644
index 0000000..2e1631b
--- /dev/null
+++ b/daemon/tls.c
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (C) 2016 American Civil Liberties Union (ACLU)
+ * Copyright (C) CZ.NIC, z.s.p.o
+ *
+ * Initial Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ * Ondřej Surý <ondrej@sury.org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <gnutls/abstract.h>
+#include <gnutls/crypto.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <uv.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "contrib/ucw/lib.h"
+#include "contrib/base64.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
+
+#define VERBOSE_MSG(cl_side, ...)\
+ if (cl_side) \
+ kr_log_debug(TLSCLIENT, __VA_ARGS__); \
+ else \
+ kr_log_debug(TLS, __VA_ARGS__);
+
+/** @internal Debugging facility. */
+#ifdef DEBUG
+#define DEBUG_MSG(...) kr_log_debug(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 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;
+ if (kr_fails_assert(t)) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ 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)
+{
+ if (kr_fails_assert(req->data))
+ return;
+ struct async_write_ctx *async_ctx = (struct async_write_ctx *)req->data;
+ struct tls_common_ctx *t = async_ctx->t;
+ if (t->write_queue_size)
+ t->write_queue_size -= 1;
+ else
+ kr_assert(false);
+ 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 (kr_fails_assert(t)) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (iovcnt == 0) {
+ return 0;
+ }
+
+ if (kr_fails_assert(t->session)) {
+ errno = EFAULT;
+ return -1;
+ }
+ uv_stream_t *handle = (uv_stream_t *)session_get_handle(t->session);
+ if (kr_fails_assert(handle && handle->type == UV_TCP)) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ /*
+ * 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. */
+ VERBOSE_MSG(t->client_side, "uv_try_write error: %s\n",
+ uv_strerror(ret));
+ 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);
+ VERBOSE_MSG(t->client_side, "uv_write error: %s\n",
+ uv_strerror(ret));
+ 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;
+
+ 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);
+ VERBOSE_MSG(ctx->client_side, "TLS handshake with %s has completed\n",
+ 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 */
+ VERBOSE_MSG(ctx->client_side, "gnutls_handshake failed: %s (%d)\n",
+ gnutls_strerror_name(err), err);
+ /* Notify the peer about handshake failure via an alert. */
+ gnutls_alert_send_appropriate(ctx->tls_session, 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);
+ VERBOSE_MSG(ctx->client_side, "TLS alert from %s received: %s\n",
+ kr_straddr(peer), alert_name);
+ }
+ }
+ return kr_ok();
+}
+
+
+struct tls_ctx *tls_new(struct worker_ctx *worker)
+{
+ if (kr_fails_assert(worker && worker->engine))
+ return 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 *tls = calloc(1, sizeof(struct tls_ctx));
+ if (tls == NULL) {
+ kr_log_error(TLS, "failed to allocate TLS context\n");
+ return NULL;
+ }
+
+ int flags = GNUTLS_SERVER | GNUTLS_NONBLOCK;
+#if GNUTLS_VERSION_NUMBER >= 0x030705
+ if (gnutls_check_version("3.7.5"))
+ flags |= GNUTLS_NO_TICKETS_TLS12;
+#endif
+ int err = gnutls_init(&tls->c.tls_session, flags);
+ 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 || kr_fails_assert(ctx->session))
+ return;
+
+ if (ctx->handshake_state == TLS_HS_DONE) {
+ const struct sockaddr *peer = session_get_peer(ctx->session);
+ VERBOSE_MSG(ctx->client_side, "closing tls connection to `%s`\n",
+ kr_straddr(peer));
+ ctx->handshake_state = TLS_HS_CLOSING;
+ gnutls_bye(ctx->tls_session, GNUTLS_SHUT_RDWR);
+ }
+}
+
+void tls_client_close(struct tls_client_ctx *ctx)
+{
+ /* Store the current session data for potential resumption of this session */
+ if (ctx->params) {
+ gnutls_free(ctx->params->session_data.data);
+ ctx->params->session_data.data = NULL;
+ ctx->params->session_data.size = 0;
+ gnutls_session_get_data2(ctx->c.tls_session, &ctx->params->session_data);
+ }
+
+ tls_close(&ctx->c);
+}
+
+void tls_free(struct tls_ctx *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);
+
+ if (kr_fails_assert(tls_ctx && session_flags(s)->outgoing == tls_ctx->client_side))
+ return kr_error(EINVAL);
+
+ const uint16_t pkt_size = htons(pkt->size);
+ 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)) {
+ VERBOSE_MSG(tls_ctx->client_side, "gnutls_record_send failed: %s (%zd)\n",
+ 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 {
+ VERBOSE_MSG(tls_ctx->client_side, "gnutls_record_uncork failed: %s (%d)\n",
+ gnutls_strerror_name(ret), ret);
+ return kr_error(EIO);
+ }
+ }
+
+ if (ret != submitted) {
+ kr_log_error(TLS, "gnutls_record_uncork didn't send all data (%d of %zd)\n", 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);
+ }
+
+ if (kr_fails_assert(tls_p->session == s))
+ return kr_error(EINVAL);
+ const bool ok = tls_p->recv_buf == buf && nread <= sizeof(tls_p->recv_buf);
+ if (kr_fails_assert(ok)) /* don't risk overflowing the buffer if we have a mistake somewhere */
+ return kr_error(EINVAL);
+
+ 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) {
+ if (tls_p->consumed == tls_p->nread) {
+ /* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */
+ break; /* No more data available in this libuv buffer */
+ }
+ continue;
+ } 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);
+ VERBOSE_MSG(tls_p->client_side, "TLS rehandshake with %s has started\n",
+ 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) {
+ VERBOSE_MSG(tls_p->client_side, "gnutls_record_recv failed: %s (%zd)\n",
+ gnutls_strerror_name(count), count);
+ return kr_error(EIO);
+ } else if (count == 0) {
+ break;
+ }
+ DEBUG_MSG("[%s] received %zd data\n", tls_p->client_side ? "tls_client" : "tls", 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 TLS_CAN_USE_PINS
+/*
+ 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)
+
+/* Compute pin_sha256 for the certificate.
+ * It may be in raw format - just TLS_SHA256_RAW_LEN bytes without termination,
+ * or it may be a base64 0-terminated string requiring up to
+ * TLS_SHA256_BASE64_BUFLEN bytes.
+ * \return error code */
+static int get_oob_key_pin(gnutls_x509_crt_t crt, char *outchar, ssize_t outchar_len, bool raw)
+{
+ /* TODO: simplify this function by using gnutls_x509_crt_get_key_id() */
+ if (kr_fails_assert(!raw || outchar_len >= TLS_SHA256_RAW_LEN)) {
+ return kr_error(ENOSPC);
+ /* With !raw we have check inside kr_base64_encode. */
+ }
+ gnutls_pubkey_t key;
+ int err = gnutls_pubkey_init(&key);
+ if (err != GNUTLS_E_SUCCESS) return err;
+
+ gnutls_datum_t datum = { .data = NULL, .size = 0 };
+ err = gnutls_pubkey_import_x509(key, crt, 0);
+ if (err != GNUTLS_E_SUCCESS) goto leave;
+
+ err = gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &datum);
+ if (err != GNUTLS_E_SUCCESS) goto leave;
+
+ char raw_pin[TLS_SHA256_RAW_LEN]; /* TMP buffer if raw == false */
+ err = gnutls_hash_fast(GNUTLS_DIG_SHA256, datum.data, datum.size,
+ (raw ? outchar : raw_pin));
+ if (err != GNUTLS_E_SUCCESS || raw/*success*/)
+ goto leave;
+ /* Convert to non-raw. */
+ err = kr_base64_encode((uint8_t *)raw_pin, sizeof(raw_pin),
+ (uint8_t *)outchar, outchar_len);
+ if (err >= 0 && err < outchar_len) {
+ err = GNUTLS_E_SUCCESS;
+ outchar[err] = '\0'; /* kr_base64_encode() doesn't do it */
+ } else if (kr_fails_assert(err < 0)) {
+ err = kr_error(ENOSPC); /* base64 fits but '\0' doesn't */
+ outchar[outchar_len - 1] = '\0';
+ }
+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++) {
+ gnutls_x509_crt_t *certs = NULL;
+ unsigned int cert_count = 0;
+ int err = gnutls_certificate_get_x509_crt(tls_credentials->credentials,
+ index, &certs, &cert_count);
+ if (err != 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[TLS_SHA256_BASE64_BUFLEN] = { 0 };
+ err = get_oob_key_pin(certs[i], pin, sizeof(pin), false);
+ if (err != 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_debug(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_warning(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);
+}
+
+void tls_client_param_unref(tls_client_param_t *entry)
+{
+ if (!entry || kr_fails_assert(entry->refs)) return;
+ --(entry->refs);
+ if (entry->refs) return;
+
+ DEBUG_MSG("freeing TLS parameters %p\n", (void *)entry);
+
+ for (int i = 0; i < entry->ca_files.len; ++i) {
+ free_const(entry->ca_files.at[i]);
+ }
+ array_clear(entry->ca_files);
+
+ free_const(entry->hostname);
+
+ for (int i = 0; i < entry->pins.len; ++i) {
+ free_const(entry->pins.at[i]);
+ }
+ 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);
+}
+static int param_free(void **param, void *null)
+{
+ if (kr_fails_assert(param && *param))
+ return -1;
+ tls_client_param_unref(*param);
+ return 0;
+}
+void tls_client_params_free(tls_client_params_t *params)
+{
+ if (!params) return;
+ trie_apply(params, param_free, NULL);
+ trie_free(params);
+}
+
+tls_client_param_t * tls_client_param_new(void)
+{
+ tls_client_param_t *e = calloc(1, sizeof(*e));
+ if (kr_fails_assert(e))
+ return NULL;
+ /* Note: those array_t don't need further initialization. */
+ e->refs = 1;
+ int ret = gnutls_certificate_allocate_credentials(&e->credentials);
+ if (ret != GNUTLS_E_SUCCESS) {
+ kr_log_error(TLSCLIENT, "error: gnutls_certificate_allocate_credentials() fails (%s)\n",
+ gnutls_strerror_name(ret));
+ free(e);
+ return NULL;
+ }
+ gnutls_certificate_set_verify_function(e->credentials, client_verify_certificate);
+ return e;
+}
+
+/**
+ * Convert an IP address and port number to binary key.
+ *
+ * \precond buffer \param key must have sufficient size
+ * \param addr[in]
+ * \param len[out] output length
+ * \param key[out] output buffer
+ */
+static bool construct_key(const union kr_sockaddr *addr, uint32_t *len, char *key)
+{
+ switch (addr->ip.sa_family) {
+ case AF_INET:
+ memcpy(key, &addr->ip4.sin_port, sizeof(addr->ip4.sin_port));
+ memcpy(key + sizeof(addr->ip4.sin_port), &addr->ip4.sin_addr,
+ sizeof(addr->ip4.sin_addr));
+ *len = sizeof(addr->ip4.sin_port) + sizeof(addr->ip4.sin_addr);
+ return true;
+ case AF_INET6:
+ memcpy(key, &addr->ip6.sin6_port, sizeof(addr->ip6.sin6_port));
+ memcpy(key + sizeof(addr->ip6.sin6_port), &addr->ip6.sin6_addr,
+ sizeof(addr->ip6.sin6_addr));
+ *len = sizeof(addr->ip6.sin6_port) + sizeof(addr->ip6.sin6_addr);
+ return true;
+ default:
+ kr_assert(!EINVAL);
+ return false;
+ }
+}
+tls_client_param_t ** tls_client_param_getptr(tls_client_params_t **params,
+ const struct sockaddr *addr, bool do_insert)
+{
+ if (kr_fails_assert(params && addr))
+ return NULL;
+ /* We accept NULL for empty map; ensure the map exists if needed. */
+ if (!*params) {
+ if (!do_insert) return NULL;
+ *params = trie_create(NULL);
+ if (kr_fails_assert(*params))
+ return NULL;
+ }
+ /* Construct the key. */
+ const union kr_sockaddr *ia = (const union kr_sockaddr *)addr;
+ char key[sizeof(ia->ip6.sin6_port) + sizeof(ia->ip6.sin6_addr)];
+ uint32_t len;
+ if (!construct_key(ia, &len, key))
+ return NULL;
+ /* Get the entry. */
+ return (tls_client_param_t **)
+ (do_insert ? trie_get_ins : trie_get_try)(*params, key, len);
+}
+
+int tls_client_param_remove(tls_client_params_t *params, const struct sockaddr *addr)
+{
+ const union kr_sockaddr *ia = (const union kr_sockaddr *)addr;
+ char key[sizeof(ia->ip6.sin6_port) + sizeof(ia->ip6.sin6_addr)];
+ uint32_t len;
+ if (!construct_key(ia, &len, key))
+ return kr_error(EINVAL);
+ trie_val_t param_ptr;
+ int ret = trie_del(params, key, len, &param_ptr);
+ if (ret != KNOT_EOK)
+ return kr_error(ret);
+ tls_client_param_unref(param_ptr);
+ return kr_ok();
+}
+
+/**
+ * Verify that at least one certificate in the certificate chain matches
+ * at least one certificate pin in the non-empty params->pins array.
+ * \returns GNUTLS_E_SUCCESS if pin matches, any other value is an error
+ */
+static int client_verify_pin(const unsigned int cert_list_size,
+ const gnutls_datum_t *cert_list,
+ tls_client_param_t *params)
+{
+ if (kr_fails_assert(params->pins.len > 0))
+ return GNUTLS_E_CERTIFICATE_ERROR;
+#if TLS_CAN_USE_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;
+ }
+
+ #ifdef DEBUG
+ if (kr_log_is_debug(TLS, NULL)) {
+ char pin_base64[TLS_SHA256_BASE64_BUFLEN];
+ /* DEBUG: additionally compute and print the base64 pin.
+ * Not very efficient, but that's OK for DEBUG. */
+ ret = get_oob_key_pin(cert, pin_base64, sizeof(pin_base64), false);
+ if (ret == GNUTLS_E_SUCCESS) {
+ DEBUG_MSG("[tls_client] received pin: %s\n", pin_base64);
+ } else {
+ DEBUG_MSG("[tls_client] failed to convert received pin\n");
+ /* Now we hope that `ret` below can't differ. */
+ }
+ }
+ #endif
+ char cert_pin[TLS_SHA256_RAW_LEN];
+ /* Get raw pin and compare. */
+ ret = get_oob_key_pin(cert, cert_pin, sizeof(cert_pin), true);
+ gnutls_x509_crt_deinit(cert);
+ if (ret != GNUTLS_E_SUCCESS) {
+ return ret;
+ }
+ for (size_t j = 0; j < params->pins.len; ++j) {
+ const uint8_t *pin = params->pins.at[j];
+ if (memcmp(cert_pin, pin, TLS_SHA256_RAW_LEN) != 0)
+ continue; /* mismatch */
+ DEBUG_MSG("[tls_client] matched a configured pin no. %zd\n", j);
+ return GNUTLS_E_SUCCESS;
+ }
+ DEBUG_MSG("[tls_client] none of %zd configured pin(s) matched\n",
+ params->pins.len);
+ }
+
+ kr_log_error(TLSCLIENT, "no pin matched: %zu pins * %d certificates\n",
+ params->pins.len, cert_list_size);
+ return GNUTLS_E_CERTIFICATE_ERROR;
+
+#else /* TLS_CAN_USE_PINS */
+ kr_log_error(TLSCLIENT, "internal inconsistency: TLS_CAN_USE_PINS\n");
+ kr_assert(false);
+ return GNUTLS_E_CERTIFICATE_ERROR;
+#endif
+}
+
+/**
+ * Verify that \param tls_session contains a valid X.509 certificate chain
+ * with given hostname.
+ *
+ * \returns GNUTLS_E_SUCCESS if certificate chain is valid, any other value is an error
+ */
+static int client_verify_certchain(gnutls_session_t tls_session, const char *hostname)
+{
+ if (kr_fails_assert(hostname)) {
+ kr_log_error(TLSCLIENT, "internal config inconsistency: no hostname set\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ unsigned int status;
+ int ret = gnutls_certificate_verify_peers3(tls_session, hostname, &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(tls_session), &msg, 0);
+ if (ret == GNUTLS_E_SUCCESS) {
+ kr_log_error(TLSCLIENT, "failed to verify peer certificate: "
+ "%s\n", msg.data);
+ gnutls_free(msg.data);
+ } else {
+ kr_log_error(TLSCLIENT, "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(TLSCLIENT, "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;
+}
+
+/**
+ * Verify that actual TLS security parameters of \param tls_session
+ * match requirements provided by user in tls_session->params.
+ * \returns GNUTLS_E_SUCCESS if requirements were met, any other value is an error
+ */
+static int client_verify_certificate(gnutls_session_t tls_session)
+{
+ struct tls_client_ctx *ctx = gnutls_session_get_ptr(tls_session);
+ if (kr_fails_assert(ctx->params))
+ return GNUTLS_E_CERTIFICATE_ERROR;
+
+ if (ctx->params->insecure) {
+ 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(TLSCLIENT, "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(TLSCLIENT, "empty certificate list\n");
+ return GNUTLS_E_CERTIFICATE_ERROR;
+ }
+
+ if (ctx->params->pins.len > 0)
+ /* check hash of the certificate but ignore everything else */
+ return client_verify_pin(cert_list_size, cert_list, ctx->params);
+ else
+ return client_verify_certchain(ctx->c.tls_session, ctx->params->hostname);
+}
+
+struct tls_client_ctx *tls_client_ctx_new(tls_client_param_t *entry,
+ struct worker_ctx *worker)
+{
+ struct tls_client_ctx *ctx = calloc(1, sizeof (struct tls_client_ctx));
+ if (!ctx) {
+ return NULL;
+ }
+ unsigned int flags = GNUTLS_CLIENT | GNUTLS_NONBLOCK
+#ifdef GNUTLS_ENABLE_FALSE_START
+ | GNUTLS_ENABLE_FALSE_START
+#endif
+ ;
+#if GNUTLS_VERSION_NUMBER >= 0x030705
+ if (gnutls_check_version("3.7.5"))
+ flags |= GNUTLS_NO_TICKETS_TLS12;
+#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. */
+ ++(entry->refs);
+ ctx->params = entry;
+
+ ret = gnutls_credentials_set(ctx->c.tls_session, GNUTLS_CRD_CERTIFICATE,
+ entry->credentials);
+ if (ret == GNUTLS_E_SUCCESS && entry->hostname) {
+ ret = gnutls_server_name_set(ctx->c.tls_session, GNUTLS_NAME_DNS,
+ entry->hostname, strlen(entry->hostname));
+ kr_log_debug(TLSCLIENT, "set hostname, ret = %d\n", ret);
+ } else if (!entry->hostname) {
+ kr_log_debug(TLSCLIENT, "no hostname\n");
+ }
+ 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 *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 */
+ tls_client_param_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;
+ if (kr_fails_assert(t)) {
+ errno = EFAULT;
+ return -1;
+ }
+ 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 *client_ctx,
+ struct session *session,
+ tls_handshake_cb handshake_cb)
+{
+ if (session == NULL || client_ctx == NULL)
+ return kr_error(EINVAL);
+
+ if (kr_fails_assert(session_flags(session)->outgoing && session_get_handle(session)->type == UV_TCP))
+ return kr_error(EINVAL);
+
+ 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;
+
+ tls_client_param_t *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 *ctx, struct session *session)
+{
+ if (!ctx) {
+ return kr_error(EINVAL);
+ }
+ ctx->c.session = session;
+ return kr_ok();
+}
+
+#undef DEBUG_MSG
+#undef VERBOSE_MSG
diff --git a/daemon/tls.h b/daemon/tls.h
new file mode 100644
index 0000000..af1f5c9
--- /dev/null
+++ b/daemon/tls.h
@@ -0,0 +1,235 @@
+/* Copyright (C) 2016 American Civil Liberties Union (ACLU)
+ * Copyright (C) CZ.NIC, z.s.p.o
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#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/trie.h"
+#include "lib/utils.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;
+struct tls_client_ctx;
+struct tls_credentials {
+ int count;
+ char *tls_cert;
+ char *tls_key;
+ gnutls_certificate_credentials_t credentials;
+ time_t valid_until;
+ char *ephemeral_servicename;
+};
+
+
+#define TLS_SHA256_RAW_LEN 32 /* gnutls_hash_get_len(GNUTLS_DIG_SHA256) */
+/** Required buffer length for pin_sha256, including the zero terminator. */
+#define TLS_SHA256_BASE64_BUFLEN (((TLS_SHA256_RAW_LEN * 8 + 4) / 6) + 3 + 1)
+
+#if GNUTLS_VERSION_NUMBER >= 0x030400
+ #define TLS_CAN_USE_PINS 1
+#else
+ #define TLS_CAN_USE_PINS 0
+#endif
+
+
+/** TLS authentication parameters for a single address-port pair. */
+typedef struct {
+ uint32_t refs; /**< Reference count; consider TLS sessions in progress. */
+ bool insecure; /**< Use no authentication. */
+ const char *hostname; /**< Server name for SNI and certificate check, lowercased. */
+ array_t(const char *) ca_files; /**< Paths to certificate files; not really used. */
+ array_t(const uint8_t *) pins; /**< Certificate pins as raw unterminated strings.*/
+ gnutls_certificate_credentials_t credentials; /**< CA creds. in gnutls format. */
+ gnutls_datum_t session_data; /**< Session-resumption data gets stored here. */
+} tls_client_param_t;
+/** Holds configuration for TLS authentication for all potential servers.
+ * Special case: NULL pointer also means empty. */
+typedef trie_t tls_client_params_t;
+
+/** Get a pointer-to-pointer to TLS auth params.
+ * If it didn't exist, it returns NULL (if !do_insert) or pointer to NULL. */
+tls_client_param_t ** tls_client_param_getptr(tls_client_params_t **params,
+ const struct sockaddr *addr, bool do_insert);
+
+/** Get a pointer to TLS auth params or NULL. */
+static inline tls_client_param_t *
+ tls_client_param_get(tls_client_params_t *params, const struct sockaddr *addr)
+{
+ tls_client_param_t **pe = tls_client_param_getptr(&params, addr, false);
+ return pe ? *pe : NULL;
+}
+
+/** Allocate and initialize the structure (with ->ref = 1). */
+tls_client_param_t * tls_client_param_new(void);
+/** Reference-counted free(); any inside data is freed alongside. */
+void tls_client_param_unref(tls_client_param_t *entry);
+
+int tls_client_param_remove(tls_client_params_t *params, const struct sockaddr *addr);
+/** Free TLS authentication parameters. */
+void tls_client_params_free(tls_client_params_t *params);
+
+
+struct worker_ctx;
+struct qr_task;
+struct network;
+struct engine;
+
+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);
+
+
+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 {
+ /*
+ * Since pointer to tls_ctx 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 {
+ /*
+ * Since pointer to tls_client_ctx needs to be casted
+ * to tls_ctx_common in some functions,
+ * this field must be always at first position
+ */
+ struct tls_common_ctx c;
+ tls_client_param_t *params; /**< It's reference-counted. */
+};
+
+/*! Create an empty TLS context in query context */
+struct tls_ctx* tls_new(struct worker_ctx *worker);
+
+/*! Close a TLS context (call gnutls_bye()) */
+void tls_close(struct tls_common_ctx *ctx);
+
+/*! Close a TLS client context (call gnutls_bye()), storing its session data
+ * for potential resumption. */
+void tls_client_close(struct tls_client_ctx *ctx);
+
+/*! Release a TLS context */
+void tls_free(struct tls_ctx* 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);
+
+
+/*! Allocate new client TLS context */
+struct tls_client_ctx *tls_client_ctx_new(tls_client_param_t *entry,
+ struct worker_ctx *worker);
+
+/*! Free client TLS context */
+void tls_client_ctx_free(struct tls_client_ctx *ctx);
+
+int tls_client_connect_start(struct tls_client_ctx *client_ctx,
+ struct session *session,
+ tls_handshake_cb handshake_cb);
+
+int tls_client_ctx_set_session(struct tls_client_ctx *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..ff4682f
--- /dev/null
+++ b/daemon/tls_ephemeral_credentials.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 American Civil Liberties Union (ACLU)
+ * Copyright (C) CZ.NIC, z.s.p.o.
+ *
+ * Initial Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <sys/file.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gnutls/crypto.h>
+
+#include "daemon/engine.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 (void)
+{
+ 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] &= 0x7f;
+ 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..b198903
--- /dev/null
+++ b/daemon/tls_session_ticket-srv.c
@@ -0,0 +1,245 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#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 */
+#define TLS_SESSION_RESUMPTION_SYNC (GNUTLS_VERSION_NUMBER >= 0x030603)
+#if TLS_SESSION_RESUMPTION_SYNC
+ #define TST_HASH GNUTLS_DIG_SHA3_512
+#else
+ #define TST_HASH abort()
+#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
+
+/** 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 (kr_fails_assert(!secret_len || (secret && hash_len >= secret_len && hash_len <= UINT16_MAX))) {
+ return NULL;
+ /* reasonable secret_len is best enforced in config API */
+ }
+ if (kr_fails_assert(tst_key_invariants()))
+ 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 (kr_fails_assert(key_tmp.size == SESSION_KEY_SIZE))
+ 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 (kr_fails_assert(ctx && ctx->hash_len >= sizeof(epoch)))
+ return kr_error(EINVAL);
+ /* documented limitation: time_t and endianness 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
+ kr_assert(!ENOTSUP);
+ 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)
+{
+ if (kr_fails_assert(timer))
+ return;
+ tst_ctx_t *ctx = timer->data;
+ if (kr_fails_assert(ctx))
+ return;
+ 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) {
+ kr_log_error(TLS, "session ticket: failed rotation, %s\n",
+ kr_strerror(err));
+ if (kr_fails_assert(err != kr_error(EINVAL)))
+ return;
+ }
+ /* 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! */
+ if (kr_fails_assert(remain_ms < (TST_KEY_LIFETIME + 1 /*rounding tolerance*/) * 1000))
+ return;
+ kr_log_debug(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 (kr_fails_assert(err == 0)) {
+ kr_log_error(TLS, "session ticket: failed to schedule, %s\n",
+ uv_strerror(err));
+ return;
+ }
+}
+
+/* Implementation for prototypes from ./tls.h */
+
+void tls_session_ticket_enable(struct tls_session_ticket_ctx *ctx, gnutls_session_t session)
+{
+ if (kr_fails_assert(ctx && session))
+ return;
+ 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)
+{
+ if (kr_fails_assert(loop && (!secret_len || secret)))
+ return NULL;
+ #if GNUTLS_VERSION_NUMBER < 0x030500
+ /* We would need different SESSION_KEY_SIZE; avoid an error. */
+ 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/udp_queue.c b/daemon/udp_queue.c
new file mode 100644
index 0000000..1f8ff39
--- /dev/null
+++ b/daemon/udp_queue.c
@@ -0,0 +1,146 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "kresconfig.h"
+#include "daemon/udp_queue.h"
+
+#include "daemon/worker.h"
+#include "lib/generic/array.h"
+#include "lib/utils.h"
+
+struct qr_task;
+
+#include <sys/socket.h>
+
+
+#if !ENABLE_SENDMMSG
+int udp_queue_init_global(uv_loop_t *loop)
+{
+ return 0;
+}
+/* Appease the linker in case this unused call isn't optimized out. */
+void udp_queue_push(int fd, struct kr_request *req, struct qr_task *task)
+{
+ abort();
+}
+#else
+
+/* LATER: it might be useful to have this configurable during runtime,
+ * but the structures below would have to change a little (broken up). */
+#define UDP_QUEUE_LEN 64
+
+/** A queue of up to UDP_QUEUE_LEN messages, meant for the same socket. */
+typedef struct {
+ int len; /**< The number of messages in the queue: 0..UDP_QUEUE_LEN */
+ struct mmsghdr msgvec[UDP_QUEUE_LEN]; /**< Parameter for sendmmsg() */
+ struct {
+ struct qr_task *task; /**< Links for completion callbacks. */
+ struct iovec msg_iov[1]; /**< storage for .msgvec[i].msg_iov */
+ } items[UDP_QUEUE_LEN];
+} udp_queue_t;
+
+static udp_queue_t * udp_queue_create(void)
+{
+ udp_queue_t *q = calloc(1, sizeof(*q));
+ kr_require(q != NULL);
+
+ for (int i = 0; i < UDP_QUEUE_LEN; ++i) {
+ struct msghdr *mhi = &q->msgvec[i].msg_hdr;
+ /* These shall remain always the same. */
+ mhi->msg_iov = q->items[i].msg_iov;
+ mhi->msg_iovlen = 1;
+ /* msg_name and msg_namelen will be per-call,
+ * and the rest is OK to remain zeroed all the time. */
+ }
+ return q;
+}
+
+/** Global state for udp_queue_*. Note: we never free the pointed-to memory. */
+struct state {
+ /** Singleton map: fd -> udp_queue_t, as a simple array of pointers. */
+ udp_queue_t **udp_queues;
+ int udp_queues_len;
+
+ /** List of FD numbers that might have a non-empty queue. */
+ array_t(int) waiting_fds;
+
+ uv_check_t check_handle;
+};
+static struct state state = {0};
+
+/** Empty the given queue. The queue is assumed to exist (but may be empty). */
+static void udp_queue_send(int fd)
+{
+ udp_queue_t *const q = state.udp_queues[fd];
+ if (!q->len) return;
+ int sent_len = sendmmsg(fd, q->msgvec, q->len, 0);
+ /* ATM we don't really do anything about failures. */
+ int err = sent_len < 0 ? errno : EAGAIN /* unknown error, really */;
+ for (int i = 0; i < q->len; ++i) {
+ qr_task_on_send(q->items[i].task, NULL, i < sent_len ? 0 : err);
+ worker_task_unref(q->items[i].task);
+ }
+ q->len = 0;
+}
+
+/** Periodical callback to send all queued packets. */
+static void udp_queue_check(uv_check_t *handle)
+{
+ for (int i = 0; i < state.waiting_fds.len; ++i) {
+ udp_queue_send(state.waiting_fds.at[i]);
+ }
+ state.waiting_fds.len = 0;
+}
+
+int udp_queue_init_global(uv_loop_t *loop)
+{
+ int ret = uv_check_init(loop, &state.check_handle);
+ if (!ret) ret = uv_check_start(&state.check_handle, udp_queue_check);
+ return ret;
+}
+
+void udp_queue_push(int fd, struct kr_request *req, struct qr_task *task)
+{
+ if (fd < 0) {
+ kr_log_error(SYSTEM, "ERROR: called udp_queue_push(fd = %d, ...)\n", fd);
+ abort();
+ }
+ worker_task_ref(task);
+ /* Get a valid correct queue. */
+ if (fd >= state.udp_queues_len) {
+ const int new_len = fd + 1;
+ state.udp_queues = realloc(state.udp_queues,
+ sizeof(state.udp_queues[0]) * new_len);
+ if (!state.udp_queues) abort();
+ memset(state.udp_queues + state.udp_queues_len, 0,
+ sizeof(state.udp_queues[0]) * (new_len - state.udp_queues_len));
+ state.udp_queues_len = new_len;
+ }
+ if (unlikely(state.udp_queues[fd] == NULL))
+ state.udp_queues[fd] = udp_queue_create();
+ udp_queue_t *const q = state.udp_queues[fd];
+
+ /* Append to the queue */
+ struct sockaddr *sa = (struct sockaddr *)/*const-cast*/req->qsource.comm_addr;
+ q->msgvec[q->len].msg_hdr.msg_name = sa;
+ q->msgvec[q->len].msg_hdr.msg_namelen = kr_sockaddr_len(sa);
+ q->items[q->len].task = task;
+ q->items[q->len].msg_iov[0] = (struct iovec){
+ .iov_base = req->answer->wire,
+ .iov_len = req->answer->size,
+ };
+ if (q->len == 0)
+ array_push(state.waiting_fds, fd);
+ ++(q->len);
+
+ if (q->len >= UDP_QUEUE_LEN) {
+ kr_assert(q->len == UDP_QUEUE_LEN);
+ udp_queue_send(fd);
+ /* We don't need to search state.waiting_fds;
+ * anyway, it's more efficient to let the hook do that. */
+ }
+}
+
+#endif
+
diff --git a/daemon/udp_queue.h b/daemon/udp_queue.h
new file mode 100644
index 0000000..f4a1ae1
--- /dev/null
+++ b/daemon/udp_queue.h
@@ -0,0 +1,16 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <uv.h>
+struct kr_request;
+struct qr_task;
+
+/** Initialize the global state for udp_queue. */
+int udp_queue_init_global(uv_loop_t *loop);
+
+/** Send req->answer via UDP, possibly not immediately. */
+void udp_queue_push(int fd, struct kr_request *req, struct qr_task *task);
+
diff --git a/daemon/worker.c b/daemon/worker.c
new file mode 100644
index 0000000..8b6b49e
--- /dev/null
+++ b/daemon/worker.c
@@ -0,0 +1,2252 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "kresconfig.h"
+#include "daemon/worker.h"
+
+#include <uv.h>
+#include <lua.h>
+#include <lauxlib.h>
+#include <libknot/packet/pkt.h>
+#include <libknot/descriptor.h>
+#include <contrib/cleanup.h>
+#include <contrib/ucw/lib.h>
+#include <contrib/ucw/mempool.h>
+#if defined(__GLIBC__) && defined(_GNU_SOURCE)
+#include <malloc.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+
+#if ENABLE_XDP
+ #include <libknot/xdp/xdp.h>
+#endif
+
+#include "daemon/bindings/api.h"
+#include "daemon/engine.h"
+#include "daemon/io.h"
+#include "daemon/proxyv2.h"
+#include "daemon/session.h"
+#include "daemon/tls.h"
+#include "daemon/http.h"
+#include "daemon/udp_queue.h"
+#include "lib/layer.h"
+#include "lib/utils.h"
+
+
+/* Magic defaults for the worker. */
+#ifndef MAX_PIPELINED
+#define MAX_PIPELINED 100
+#endif
+
+#define VERBOSE_MSG(qry, ...) kr_log_q(qry, WORKER, __VA_ARGS__)
+
+/** Client request state. */
+struct request_ctx
+{
+ struct kr_request req;
+
+ struct worker_ctx *worker;
+ struct qr_task *task;
+ struct {
+ /** NULL if the request didn't come over network. */
+ struct session *session;
+ /** Requestor's address; separate because of UDP session "sharing". */
+ union kr_sockaddr addr;
+ /** Request communication address; if not from a proxy, same as addr. */
+ union kr_sockaddr comm_addr;
+ /** Local address. For AF_XDP we couldn't use session's,
+ * as the address might be different every time. */
+ union kr_sockaddr dst_addr;
+ /** MAC addresses - ours [0] and router's [1], in case of AF_XDP socket. */
+ uint8_t eth_addrs[2][6];
+ } source;
+};
+
+/** 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 timeouts;
+ uint16_t iter_count;
+ uint32_t refs;
+ bool finished : 1;
+ bool leading : 1;
+ uint64_t creation_time;
+ uint64_t send_time;
+ uint64_t recv_time;
+ struct kr_transport *transport;
+};
+
+
+/* Convenience macros */
+#define qr_task_ref(task) \
+ do { ++(task)->refs; } while(0)
+#define qr_task_unref(task) \
+ do { \
+ if (task) \
+ kr_require((task)->refs > 0); \
+ if ((task) && --(task)->refs == 0) \
+ qr_task_free((task)); \
+ } while (0)
+
+/* 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,
+ const 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);
+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);
+struct session* worker_find_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr *addr);
+static void on_tcp_connect_timeout(uv_timer_t *timer);
+static void on_udp_timeout(uv_timer_t *timer);
+static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *pkt);
+
+
+struct worker_ctx the_worker_value; /**< Static allocation is suitable for the singleton. */
+struct worker_ctx *the_worker = NULL;
+
+/*! @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 has_http)
+{
+ bool precond = (socktype == SOCK_DGRAM || socktype == SOCK_STREAM)
+ && (family == AF_INET || family == AF_INET6);
+ if (kr_fails_assert(precond)) {
+ kr_log_debug(WORKER, "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, has_http);
+ 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 kr_sockaddr *addr;
+ if (family == AF_INET) {
+ addr = (union kr_sockaddr *)&worker->out_addr4;
+ } else {
+ addr = (union kr_sockaddr *)&worker->out_addr6;
+ }
+ if (addr->ip.sa_family != AF_UNSPEC) {
+ if (kr_fails_assert(addr->ip.sa_family == family)) {
+ io_free(handle);
+ return NULL;
+ }
+ 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_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;
+}
+
+/** Get a mempool. */
+static inline struct mempool *pool_borrow(struct worker_ctx *worker)
+{
+ /* The implementation used to have extra caching layer,
+ * but it didn't work well. Now it's very simple. */
+ return mp_new(16 * 1024);
+}
+/** Return a mempool. */
+static inline void pool_release(struct worker_ctx *worker, struct mempool *mp)
+{
+ 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)
+{
+ kr_require(pkt);
+ return kr_rrkey(dst, knot_pkt_qclass(pkt), knot_pkt_qname(pkt),
+ knot_pkt_qtype(pkt), knot_pkt_qtype(pkt));
+}
+
+#if ENABLE_XDP
+static uint8_t *alloc_wire_cb(struct kr_request *req, uint16_t *maxlen)
+{
+ if (kr_fails_assert(maxlen))
+ return NULL;
+ struct request_ctx *ctx = (struct request_ctx *)req;
+ /* We know it's an AF_XDP socket; otherwise this CB isn't assigned. */
+ uv_handle_t *handle = session_get_handle(ctx->source.session);
+ if (kr_fails_assert(handle->type == UV_POLL))
+ return NULL;
+ xdp_handle_data_t *xhd = handle->data;
+ knot_xdp_msg_t out;
+ bool ipv6 = ctx->source.comm_addr.ip.sa_family == AF_INET6;
+ int ret = knot_xdp_send_alloc(xhd->socket,
+ #if KNOT_VERSION_HEX >= 0x030100
+ ipv6 ? KNOT_XDP_MSG_IPV6 : 0, &out);
+ #else
+ ipv6, &out, NULL);
+ #endif
+ if (ret != KNOT_EOK) {
+ kr_assert(ret == KNOT_ENOMEM);
+ *maxlen = 0;
+ return NULL;
+ }
+ *maxlen = MIN(*maxlen, out.payload.iov_len);
+#if KNOT_VERSION_HEX < 0x030100
+ /* It's most convenient to fill the MAC addresses at this point. */
+ memcpy(out.eth_from, &ctx->source.eth_addrs[0], 6);
+ memcpy(out.eth_to, &ctx->source.eth_addrs[1], 6);
+#endif
+ return out.payload.iov_base;
+}
+static void free_wire(const struct request_ctx *ctx)
+{
+ if (kr_fails_assert(ctx->req.alloc_wire_cb == alloc_wire_cb))
+ return;
+ knot_pkt_t *ans = ctx->req.answer;
+ if (unlikely(ans == NULL)) /* dropped */
+ return;
+ if (likely(ans->wire == NULL)) /* sent most likely */
+ return;
+ /* We know it's an AF_XDP socket; otherwise alloc_wire_cb isn't assigned. */
+ uv_handle_t *handle = session_get_handle(ctx->source.session);
+ if (kr_fails_assert(handle->type == UV_POLL))
+ return;
+ xdp_handle_data_t *xhd = handle->data;
+ /* Freeing is done by sending an empty packet (the API won't really send it). */
+ knot_xdp_msg_t out;
+ out.payload.iov_base = ans->wire;
+ out.payload.iov_len = 0;
+ uint32_t sent = 0;
+#if KNOT_VERSION_HEX >= 0x030100
+ int ret = 0;
+ knot_xdp_send_free(xhd->socket, &out, 1);
+#else
+ int ret = knot_xdp_send(xhd->socket, &out, 1, &sent);
+#endif
+ kr_assert(ret == KNOT_EOK && sent == 0);
+ kr_log_debug(XDP, "freed unsent buffer, ret = %d\n", ret);
+}
+#endif
+/* Helper functions for transport selection */
+static inline bool is_tls_capable(struct sockaddr *address) {
+ tls_client_param_t *tls_entry = tls_client_param_get(the_worker->engine->net.tls_client_params, address);
+ return tls_entry;
+}
+
+static inline bool is_tcp_connected(struct sockaddr *address) {
+ return worker_find_tcp_connected(the_worker, address);
+}
+
+static inline bool is_tcp_waiting(struct sockaddr *address) {
+ return worker_find_tcp_waiting(the_worker, address);
+}
+
+/** Create and initialize a request_ctx (on a fresh mempool).
+ *
+ * session 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,
+ struct session *session,
+ struct io_comm_data *comm,
+ const uint8_t *eth_from,
+ const uint8_t *eth_to,
+ 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_calloc(&pool, 1, sizeof(*ctx));
+ if (!ctx) {
+ pool_release(worker, pool.ctx);
+ return NULL;
+ }
+
+ /* TODO Relocate pool to struct request */
+ ctx->worker = worker;
+ if (session && kr_fails_assert(session_flags(session)->outgoing == false)) {
+ pool_release(worker, pool.ctx);
+ return NULL;
+ }
+ ctx->source.session = session;
+ if (kr_fails_assert(!!eth_to == !!eth_from)) {
+ pool_release(worker, pool.ctx);
+ return NULL;
+ }
+ const bool is_xdp = eth_to != NULL;
+ if (is_xdp) {
+ #if ENABLE_XDP
+ if (kr_fails_assert(session)) {
+ pool_release(worker, pool.ctx);
+ return NULL;
+ }
+ memcpy(&ctx->source.eth_addrs[0], eth_to, sizeof(ctx->source.eth_addrs[0]));
+ memcpy(&ctx->source.eth_addrs[1], eth_from, sizeof(ctx->source.eth_addrs[1]));
+ ctx->req.alloc_wire_cb = alloc_wire_cb;
+ #else
+ kr_assert(!EINVAL);
+ pool_release(worker, pool.ctx);
+ return NULL;
+ #endif
+ }
+
+ struct kr_request *req = &ctx->req;
+ req->pool = pool;
+ req->vars_ref = LUA_NOREF;
+ req->uid = uid;
+ req->qsource.comm_flags.xdp = is_xdp;
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_NONE, NULL);
+ array_init(req->qsource.headers);
+ if (session) {
+ kr_require(comm);
+
+ const struct sockaddr *src_addr = comm->src_addr;
+ const struct sockaddr *comm_addr = comm->comm_addr;
+ const struct sockaddr *dst_addr = comm->dst_addr;
+ const struct proxy_result *proxy = comm->proxy;
+
+ req->qsource.comm_flags.tcp = session_get_handle(session)->type == UV_TCP;
+ req->qsource.comm_flags.tls = session_flags(session)->has_tls;
+ req->qsource.comm_flags.http = session_flags(session)->has_http;
+
+ req->qsource.flags = req->qsource.comm_flags;
+ if (proxy) {
+ req->qsource.flags.tcp = proxy->protocol == SOCK_STREAM;
+ req->qsource.flags.tls = proxy->has_tls;
+ }
+
+ req->qsource.stream_id = -1;
+#if ENABLE_DOH2
+ if (req->qsource.comm_flags.http) {
+ struct http_ctx *http_ctx = session_http_get_server_ctx(session);
+ struct http_stream stream = queue_head(http_ctx->streams);
+ req->qsource.stream_id = stream.id;
+ if (stream.headers) {
+ req->qsource.headers = *stream.headers;
+ free(stream.headers);
+ stream.headers = NULL;
+ }
+ }
+#endif
+ /* We need to store a copy of peer address. */
+ memcpy(&ctx->source.addr.ip, src_addr, kr_sockaddr_len(src_addr));
+ req->qsource.addr = &ctx->source.addr.ip;
+
+ if (!comm_addr)
+ comm_addr = src_addr;
+ memcpy(&ctx->source.comm_addr.ip, comm_addr, kr_sockaddr_len(comm_addr));
+ req->qsource.comm_addr = &ctx->source.comm_addr.ip;
+
+ if (!dst_addr) /* We wouldn't have to copy in this case, but for consistency. */
+ dst_addr = session_get_sockname(session);
+ memcpy(&ctx->source.dst_addr.ip, dst_addr, kr_sockaddr_len(dst_addr));
+ req->qsource.dst_addr = &ctx->source.dst_addr.ip;
+ }
+
+ req->selection_context.is_tls_capable = is_tls_capable;
+ req->selection_context.is_tcp_connected = is_tcp_connected;
+ req->selection_context.is_tcp_waiting = is_tcp_waiting;
+ array_init(req->selection_context.forwarding_targets);
+ array_reserve_mm(req->selection_context.forwarding_targets, 1, kr_memreserve, &req->pool);
+
+ worker->stats.rconcurrent += 1;
+
+ return ctx;
+}
+
+/** More initialization, related to the particular incoming query/packet. */
+static int request_start(struct request_ctx *ctx, knot_pkt_t *query)
+{
+ if (kr_fails_assert(query && ctx))
+ return kr_error(EINVAL);
+
+ struct kr_request *req = &ctx->req;
+ req->qsource.size = query->size;
+ if (knot_pkt_has_tsig(query)) {
+ req->qsource.size += query->tsig_wire.len;
+ }
+
+ 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);
+ worker->stats.queries += 1;
+ 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;
+ }
+ /* Free HTTP/2 headers for DoH requests. */
+ for(int i = 0; i < ctx->req.qsource.headers.len; i++) {
+ free(ctx->req.qsource.headers.at[i].name);
+ free(ctx->req.qsource.headers.at[i].value);
+ }
+ array_clear(ctx->req.qsource.headers);
+
+ /* Make sure to free XDP buffer in case it wasn't sent. */
+ if (ctx->req.alloc_wire_cb) {
+ #if ENABLE_XDP
+ free_wire(ctx);
+ #else
+ kr_assert(!EINVAL);
+ #endif
+ }
+ /* 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. */
+ worker->stats.rconcurrent -= 1;
+}
+
+static struct qr_task *qr_task_create(struct request_ctx *ctx)
+{
+ /* Choose (initial) pktbuf size. As it is now, pktbuf can be used
+ * for UDP answers from upstream *and* from cache
+ * and for sending queries upstream */
+ uint16_t pktbuf_max = KR_EDNS_PAYLOAD;
+ const knot_rrset_t *opt_our = ctx->worker->engine->resolver.upstream_opt_rr;
+ if (opt_our) {
+ pktbuf_max = MAX(pktbuf_max, knot_edns_get_payload(opt_our));
+ }
+
+ /* Create resolution task */
+ struct qr_task *task = mm_calloc(&ctx->req.pool, 1, sizeof(*task));
+ if (!task) {
+ return NULL;
+ }
+
+ /* 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;
+ kr_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;
+
+ if (kr_fails_assert(ctx))
+ return;
+
+ 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)
+{
+ if (kr_fails_assert(!session_flags(session)->outgoing && session_get_handle(session)->type == UV_TCP))
+ return kr_error(EINVAL);
+
+ session_tasklist_add(session, task);
+
+ struct request_ctx *ctx = task->ctx;
+ if (kr_fails_assert(ctx && (ctx->source.session == NULL || ctx->source.session == session)))
+ return kr_error(EINVAL);
+ 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);
+ kr_require(task->waiting.len == 0);
+ kr_require(task->leading == false);
+
+ struct session *s = ctx->source.session;
+ if (s) {
+ kr_require(!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 */
+int qr_task_on_send(struct qr_task *task, const uv_handle_t *handle, int status)
+{
+ if (task->finished) {
+ kr_require(task->leading == false);
+ qr_task_complete(task);
+ }
+
+ if (!handle || kr_fails_assert(handle->data))
+ return status;
+ struct session* s = handle->data;
+
+ if (handle->type == UV_UDP && session_flags(s)->outgoing) {
+ // This should ensure that we are only dealing with our question to upstream
+ if (kr_fails_assert(!knot_wire_get_qr(task->pktbuf->wire)))
+ return status;
+ // start the timer
+ struct kr_query *qry = array_tail(task->ctx->req.rplan.pending);
+ if (kr_fails_assert(qry && task->transport))
+ return status;
+ size_t timeout = task->transport->timeout;
+ int ret = session_timer_start(s, on_udp_timeout, timeout, 0);
+ /* Start next step with timeout, fatal if can't start a timer. */
+ if (ret != 0) {
+ subreq_finalize(task, &task->transport->address.ip, task->pktbuf);
+ qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ }
+
+ if (handle->type == UV_TCP) {
+ if (status != 0) { // session probably not usable anymore; typically: ECONNRESET
+ const struct kr_request *req = &task->ctx->req;
+ if (kr_log_is_debug(WORKER, req)) {
+ const char *peer_str = NULL;
+ if (!session_flags(s)->outgoing) {
+ peer_str = "hidden"; // avoid logging downstream IPs
+ } else if (task->transport) {
+ peer_str = kr_straddr(&task->transport->address.ip);
+ }
+ if (!peer_str)
+ peer_str = "unknown"; // probably shouldn't happen
+ kr_log_req(req, 0, 0, WORKER,
+ "=> disconnected from '%s': %s\n",
+ peer_str, uv_strerror(status));
+ }
+ worker_end_tcp(s);
+ return status;
+ }
+
+ 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,
+ const 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);
+ if (kr_fails_assert(handle && handle->data == session))
+ return qr_task_on_send(task, NULL, kr_error(EINVAL));
+ const bool is_stream = handle->type == UV_TCP;
+ kr_require(is_stream || handle->type == UV_UDP);
+
+ 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);
+ }
+
+ struct worker_ctx *worker = ctx->worker;
+ /* Note time for upstream RTT */
+ task->send_time = kr_now();
+ task->recv_time = 0; // task structure is being reused so we have to zero this out here
+ /* Send using given protocol */
+ if (kr_fails_assert(!session_flags(session)->closing))
+ return qr_task_on_send(task, NULL, kr_error(EIO));
+
+ 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);
+
+ if (session_flags(session)->has_http) {
+#if ENABLE_DOH2
+ uv_write_t *write_req = (uv_write_t *)ioreq;
+ write_req->data = task;
+ ret = http_write(write_req, handle, pkt, ctx->req.qsource.stream_id, &on_write);
+#else
+ ret = kr_error(ENOPROTOOPT);
+#endif
+ } else 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;
+ /* We need to write message length in native byte order,
+ * but we don't have a convenient place to store those bytes.
+ * The problem is that all memory referenced from buf[] MUST retain
+ * its contents at least until on_write() is called, and I currently
+ * can't see any convenient place outside the `pkt` structure.
+ * So we use directly the *individual* bytes in pkt->size.
+ * The call to htonl() and the condition will probably be inlinable. */
+ int lsbi, slsbi; /* (second) least significant byte index */
+ if (htonl(1) == 1) { /* big endian */
+ lsbi = sizeof(pkt->size) - 1;
+ slsbi = sizeof(pkt->size) - 2;
+ } else {
+ lsbi = 0;
+ slsbi = 1;
+ }
+ uv_buf_t buf[3] = {
+ { (char *)&pkt->size + slsbi, 1 },
+ { (char *)&pkt->size + lsbi, 1 },
+ { (char *)pkt->wire, pkt->size },
+ };
+ write_req->data = task;
+ ret = uv_write(write_req, (uv_stream_t *)handle, buf, 3, &on_write);
+ } else {
+ kr_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);
+ }
+
+ if (session_flags(session)->has_http)
+ worker->stats.err_http += 1;
+ else if (session_flags(session)->has_tls)
+ worker->stats.err_tls += 1;
+ else if (handle->type == UV_UDP)
+ worker->stats.err_udp += 1;
+ else
+ worker->stats.err_tcp += 1;
+ }
+
+ /* Update outgoing query 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)
+{
+ if (kr_fails_assert(session_flags(session)->outgoing))
+ return kr_error(EINVAL);
+ struct sockaddr *peer = session_get_peer(session);
+ int deletion_res = worker_del_tcp_waiting(the_worker, peer);
+ int ret = kr_ok();
+
+ if (status) {
+ struct qr_task *task = session_waitinglist_get(session);
+ if (task) {
+ // TLS handshake failed, report it to server selection
+ struct kr_query *qry = array_tail(task->ctx->req.rplan.pending);
+ qry->server_selection.error(qry, task->transport, KR_SELECTION_TLS_HANDSHAKE_FAILED);
+ }
+#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. */
+ kr_require(deletion_res != 0);
+ struct kr_sockaddr_key_storage key;
+ ssize_t keylen = kr_sockaddr_key(&key, peer);
+ if (keylen < 0)
+ return keylen;
+ trie_val_t *val;
+ kr_require((val = trie_get_try(the_worker->tcp_connected, key.bytes, keylen)) && *val);
+ }
+#endif
+ return ret;
+ }
+
+ /* handshake was completed successfully */
+ struct tls_client_ctx *tls_client_ctx = session_tls_get_client_ctx(session);
+ tls_client_param_t *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_debug(TLSCLIENT, "TLS session has resumed\n");
+ } else {
+ kr_log_debug(TLSCLIENT, "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(the_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(the_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 timed out
+ * 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(the_worker, peer);
+ session_waitinglist_finalize(session, KR_STATE_FAIL);
+ session_tasklist_finalize(session, KR_STATE_FAIL);
+ 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 = the_worker;
+ kr_require(worker);
+ uv_stream_t *handle = req->handle;
+ struct session *session = handle->data;
+ struct sockaddr *peer = session_get_peer(session);
+ free(req);
+
+ if (kr_fails_assert(session_flags(session)->outgoing))
+ return;
+
+ if (session_flags(session)->closing) {
+ worker_del_tcp_waiting(worker, peer);
+ kr_assert(session_is_empty(session));
+ return;
+ }
+
+ const bool log_debug = kr_log_is_debug(WORKER, NULL);
+
+ /* Check if the connection is in the waiting list.
+ * If no, most likely this is timed out 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 timed out session. */
+ if (log_debug) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_debug(WORKER, "=> connected to '%s', but session "
+ "is already timed out, close\n",
+ peer_str ? peer_str : "");
+ }
+ kr_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 (log_debug) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_debug(WORKER, "=> connected to '%s', but peer "
+ "is already connected, close\n",
+ peer_str ? peer_str : "");
+ }
+ kr_assert(session_tasklist_is_empty(session));
+ session_waitinglist_retry(session, false);
+ session_close(session);
+ return;
+ }
+
+ if (status != 0) {
+ if (log_debug) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_debug(WORKER, "=> 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_query *qry = array_tail(task->ctx->req.rplan.pending);
+ qry->server_selection.error(qry, task->transport, KR_SELECTION_TCP_CONNECT_FAILED);
+ }
+ kr_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);
+ kr_assert(session_tasklist_is_empty(session));
+ session_close(session);
+ return;
+ }
+ }
+
+ if (log_debug) {
+ const char *peer_str = kr_straddr(peer);
+ kr_log_debug(WORKER, "=> 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 *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 = the_worker;
+ kr_require(worker);
+
+ kr_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);
+ if (kr_log_is_debug_qry(WORKER, qry)) {
+ const char *peer_str = kr_straddr(peer);
+ VERBOSE_MSG(qry, "=> connection to '%s' failed (internal timeout)\n",
+ peer_str ? peer_str : "");
+ }
+
+ qry->server_selection.error(qry, task->transport, KR_SELECTION_TCP_CONNECT_TIMEOUT);
+
+ worker->stats.timeout += session_waitinglist_get_len(session);
+ session_waitinglist_retry(session, true);
+ kr_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 timed out 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;
+ kr_assert(session_get_handle(session)->data == session);
+ kr_assert(session_tasklist_get_len(session) == 1);
+ kr_assert(session_waitinglist_is_empty(session));
+
+ uv_timer_stop(timer);
+
+ struct qr_task *task = session_tasklist_get_first(session);
+ if (!task)
+ return;
+ 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);
+ qry->server_selection.error(qry, task->transport, KR_SELECTION_QUERY_TIMEOUT);
+ }
+
+ task->timeouts += 1;
+ worker->stats.timeout += 1;
+ qr_task_step(task, NULL, NULL);
+}
+
+static uv_handle_t *transmit(struct qr_task *task)
+{
+ uv_handle_t *ret = NULL;
+
+ if (task) {
+ struct kr_transport* transport = task->transport;
+
+ struct sockaddr_in6 *choice = (struct sockaddr_in6 *)&transport->address;
+
+ 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, transport, task->pktbuf) != 0) {
+ return ret;
+ }
+ ret = ioreq_spawn(ctx->worker, SOCK_DGRAM, choice->sin6_family, false, false);
+ if (!ret) {
+ return ret;
+ }
+ struct sockaddr *addr = (struct sockaddr *)choice;
+ struct session *session = ret->data;
+ struct sockaddr *peer = session_get_peer(session);
+ kr_assert(peer->sa_family == AF_UNSPEC && session_flags(session)->outgoing);
+ kr_require(addr->sa_family == AF_INET || addr->sa_family == AF_INET6);
+ 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;
+ session_start_read(session); /* Start reading answer */
+ }
+ }
+ return ret;
+}
+
+
+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);
+ kr_assert(ret == KNOT_EOK && val_deleted == task);
+ }
+ /* 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;
+
+ // Note that this transport may not be present in `leader_qry`'s server selection
+ follower->transport = task->transport;
+ if(follower->transport) {
+ follower->transport->deduplicated = true;
+ }
+ 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)
+{
+ if (kr_fails_assert(task))
+ return;
+ 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 (kr_fails_assert(*tvp == NULL))
+ return;
+ *tvp = task;
+ task->leading = true;
+}
+
+static bool subreq_enqueue(struct qr_task *task)
+{
+ if (kr_fails_assert(task))
+ return false;
+ 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;
+}
+
+#if ENABLE_XDP
+static void xdp_tx_waker(uv_idle_t *handle)
+{
+ int ret = knot_xdp_send_finish(handle->data);
+ if (ret != KNOT_EAGAIN && ret != KNOT_EOK)
+ kr_log_error(XDP, "check: ret = %d, %s\n", ret, knot_strerror(ret));
+ /* Apparently some drivers need many explicit wake-up calls
+ * even if we push no additional packets (in case they accumulated a lot) */
+ if (ret != KNOT_EAGAIN)
+ uv_idle_stop(handle);
+ knot_xdp_send_prepare(handle->data);
+ /* LATER(opt.): it _might_ be better for performance to do these two steps
+ * at different points in time */
+}
+#endif
+/** Send an answer packet over XDP. */
+static int xdp_push(struct qr_task *task, const uv_handle_t *src_handle)
+{
+#if ENABLE_XDP
+ struct request_ctx *ctx = task->ctx;
+ xdp_handle_data_t *xhd = src_handle->data;
+ if (kr_fails_assert(xhd && xhd->socket && xhd->session == ctx->source.session))
+ return qr_task_on_send(task, src_handle, kr_error(EINVAL));
+
+ knot_xdp_msg_t msg;
+#if KNOT_VERSION_HEX >= 0x030100
+ /* We don't have a nice way of preserving the _msg_t from frame allocation,
+ * so we manually redo all other parts of knot_xdp_send_alloc() */
+ memset(&msg, 0, sizeof(msg));
+ bool ipv6 = ctx->source.addr.ip.sa_family == AF_INET6;
+ msg.flags = ipv6 ? KNOT_XDP_MSG_IPV6 : 0;
+ memcpy(msg.eth_from, &ctx->source.eth_addrs[0], 6);
+ memcpy(msg.eth_to, &ctx->source.eth_addrs[1], 6);
+#endif
+ const struct sockaddr *ip_from = &ctx->source.dst_addr.ip;
+ const struct sockaddr *ip_to = &ctx->source.comm_addr.ip;
+ memcpy(&msg.ip_from, ip_from, kr_sockaddr_len(ip_from));
+ memcpy(&msg.ip_to, ip_to, kr_sockaddr_len(ip_to));
+ msg.payload.iov_base = ctx->req.answer->wire;
+ msg.payload.iov_len = ctx->req.answer->size;
+
+ uint32_t sent;
+ int ret = knot_xdp_send(xhd->socket, &msg, 1, &sent);
+ ctx->req.answer->wire = NULL; /* it's been freed */
+
+ uv_idle_start(&xhd->tx_waker, xdp_tx_waker);
+ kr_log_debug(XDP, "pushed a packet, ret = %d\n", ret);
+
+ return qr_task_on_send(task, src_handle, ret);
+#else
+ kr_assert(!EINVAL);
+ return kr_error(EINVAL);
+#endif
+}
+
+static int qr_task_finalize(struct qr_task *task, int state)
+{
+ kr_require(task && task->leading == false);
+ if (task->finished) {
+ return kr_ok();
+ }
+ 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 ? kr_ok() : kr_error(EIO);
+ }
+
+ /* meant to be dropped */
+ if (unlikely(ctx->req.answer == NULL || ctx->req.options.NO_ANSWER)) {
+ /* For NO_ANSWER, a well-behaved layer should set the state to FAIL */
+ kr_assert(!ctx->req.options.NO_ANSWER || (ctx->req.state & KR_STATE_FAIL));
+
+ (void) qr_task_on_send(task, NULL, kr_ok());
+ return kr_ok();
+ }
+
+ if (session_flags(source_session)->closing ||
+ ctx->source.addr.ip.sa_family == AF_UNSPEC)
+ return kr_error(EINVAL);
+
+ /* Reference task as the callback handler can close it */
+ qr_task_ref(task);
+
+ /* Send back answer */
+ int ret;
+ const uv_handle_t *src_handle = session_get_handle(source_session);
+ if (kr_fails_assert(src_handle->type == UV_UDP || src_handle->type == UV_TCP
+ || src_handle->type == UV_POLL)) {
+ ret = kr_error(EINVAL);
+ } else if (src_handle->type == UV_POLL) {
+ ret = xdp_push(task, src_handle);
+ } else if (src_handle->type == UV_UDP && ENABLE_SENDMMSG) {
+ int fd;
+ ret = uv_fileno(src_handle, &fd);
+ if (ret == 0)
+ udp_queue_push(fd, &ctx->req, task);
+ else
+ kr_assert(false);
+ } else {
+ ret = qr_task_send(task, source_session, &ctx->source.comm_addr.ip, ctx->req.answer);
+ }
+
+ if (ret != 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;
+ kr_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);
+
+ if (ret != kr_ok() || state != KR_STATE_DONE)
+ return kr_error(EIO);
+ return kr_ok();
+}
+
+static int udp_task_step(struct qr_task *task,
+ const struct sockaddr *packet_source, knot_pkt_t *packet)
+{
+ /* 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 = transmit(task);
+ if (handle == NULL) {
+ subreq_finalize(task, packet_source, packet);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+
+ /* Announce and start subrequest.
+ * @note Only UDP can lead I/O as it doesn't touch 'task->pktbuf' for reassembly.
+ */
+ subreq_lead(task);
+
+ return kr_ok();
+}
+
+static int tcp_task_waiting_connection(struct session *session, struct qr_task *task)
+{
+ if (kr_fails_assert(session_flags(session)->outgoing && !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)
+{
+ if (kr_fails_assert(session_flags(session)->outgoing && !session_flags(session)->closing))
+ return kr_error(EINVAL);
+ struct request_ctx *ctx = task->ctx;
+ struct worker_ctx *worker = ctx->worker;
+
+ /* 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 SERVFAIL, */
+ 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 tls_client_ctx *tls_ctx = NULL;
+ struct network *net = &worker->engine->net;
+ tls_client_param_t *entry = tls_client_param_get(net->tls_client_params, addr);
+ 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_http = false;
+ bool has_tls = (tls_ctx != NULL);
+ uv_handle_t *client = ioreq_spawn(worker, SOCK_STREAM, addr->sa_family, has_tls, has_http);
+ if (!client) {
+ tls_client_ctx_free(tls_ctx);
+ free(conn);
+ return kr_error(EINVAL);
+ }
+ struct session *session = client->data;
+ if (kr_fails_assert(session_flags(session)->has_tls == has_tls)) {
+ tls_client_ctx_free(tls_ctx);
+ free(conn);
+ return kr_error(EINVAL);
+ }
+ 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(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(worker, addr);
+ free(conn);
+ session_close(session);
+ return kr_error(EINVAL);
+ }
+
+ struct kr_query *qry = task_get_last_pending_query(task);
+ if (kr_log_is_debug_qry(WORKER, 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(worker, addr);
+ free(conn);
+ session_close(session);
+ qry->server_selection.error(qry, task->transport, KR_SELECTION_TCP_CONNECT_FAILED);
+ 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(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)
+{
+ if (kr_fails_assert(task->pending_count == 0)) {
+ subreq_finalize(task, packet_source, packet);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+
+ /* target */
+ const struct sockaddr *addr = &task->transport->address.ip;
+ 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, task->transport, 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) {
+ struct kr_request *req = worker_task_request(task);
+ if (!kr_fails_assert(req))
+ kr_query_inform_timeout(req, req->current_query);
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+
+ /* Consume input and produce next query */
+ struct request_ctx *ctx = task->ctx;
+ if (kr_fails_assert(ctx))
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ struct kr_request *req = &ctx->req;
+ struct worker_ctx *worker = ctx->worker;
+
+ 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, &task->transport, packet);
+ }
+ return qr_task_finalize(task, KR_STATE_FAIL);
+ }
+ }
+
+ // Report network RTT back to server selection
+ if (packet && task->send_time && task->recv_time) {
+ struct kr_query *qry = array_tail(req->rplan.pending);
+ qry->server_selection.update_rtt(qry, task->transport, task->recv_time - task->send_time);
+ }
+
+ int state = kr_resolve_consume(req, &task->transport, packet);
+
+ task->transport = NULL;
+ while (state == KR_STATE_PRODUCE) {
+ state = kr_resolve_produce(req, &task->transport, task->pktbuf);
+ if (unlikely(++task->iter_count > KR_ITER_LIMIT ||
+ task->timeouts >= KR_TIMEOUT_LIMIT)) {
+
+ struct kr_rplan *rplan = &req->rplan;
+ struct kr_query *last = kr_rplan_last(rplan);
+ if (task->iter_count > KR_ITER_LIMIT) {
+ char *msg = "cancelling query due to exceeded iteration count limit";
+ VERBOSE_MSG(last, "%s of %d\n", msg, KR_ITER_LIMIT);
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER,
+ "OGHD: exceeded iteration count limit");
+ }
+ if (task->timeouts >= KR_TIMEOUT_LIMIT) {
+ char *msg = "cancelling query due to exceeded timeout retries limit";
+ VERBOSE_MSG(last, "%s of %d\n", msg, KR_TIMEOUT_LIMIT);
+ kr_request_set_extended_error(req, KNOT_EDNS_EDE_NREACH_AUTH, "QLPL");
+ }
+
+ 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->transport || !task->transport->protocol) {
+ return qr_task_step(task, NULL, NULL);
+ }
+
+ switch (task->transport->protocol)
+ {
+ case KR_TRANSPORT_UDP:
+ return udp_task_step(task, packet_source, packet);
+ case KR_TRANSPORT_TCP: // fall through
+ case KR_TRANSPORT_TLS:
+ return tcp_task_step(task, packet_source, packet);
+ default:
+ kr_assert(!EINVAL);
+ return kr_error(EINVAL);
+ }
+}
+
+int worker_submit(struct session *session, struct io_comm_data *comm,
+ const uint8_t *eth_from, const uint8_t *eth_to, knot_pkt_t *pkt)
+{
+ if (!session || !pkt)
+ return kr_error(EINVAL);
+
+ uv_handle_t *handle = session_get_handle(session);
+ if (!handle || !handle->loop->data)
+ return kr_error(EINVAL);
+
+ const bool is_query = pkt->size > KNOT_WIRE_OFFSET_FLAGS1
+ && knot_wire_get_qr(pkt->wire) == 0;
+ const bool is_outgoing = session_flags(session)->outgoing;
+
+ int ret = 0;
+ if (is_query == is_outgoing)
+ ret = KNOT_ENOENT;
+
+ // For responses from upstream, try to find associated task and query.
+ // In case of errors, at least try to guess.
+ struct qr_task *task = NULL;
+ bool task_matched_id = false;
+ if (is_outgoing && pkt->size >= 2) {
+ const uint16_t id = knot_wire_get_id(pkt->wire);
+ task = session_tasklist_del_msgid(session, id);
+ task_matched_id = task != NULL;
+ if (task_matched_id) // Note receive time for RTT calculation
+ task->recv_time = kr_now();
+ if (!task_matched_id) {
+ ret = KNOT_ENOENT;
+ VERBOSE_MSG(NULL, "=> DNS message with mismatching ID %d\n",
+ (int)id);
+ }
+ }
+ if (!task && is_outgoing && handle->type == UV_TCP) {
+ // Source address of the reply got somewhat validated,
+ // so we try to at least guess which query, for error reporting.
+ task = session_tasklist_get_first(session);
+ }
+ struct kr_query *qry = NULL;
+ if (task)
+ qry = array_tail(task->ctx->req.rplan.pending);
+
+ // Parse the packet, unless it's useless anyway.
+ if (ret == 0) {
+ ret = knot_pkt_parse(pkt, 0);
+ if (ret == KNOT_ETRAIL && is_outgoing
+ && !kr_fails_assert(pkt->parsed < pkt->size)) {
+ // We deal with this later, so that RCODE takes priority.
+ ret = 0;
+ }
+ if (ret && kr_log_is_debug_qry(WORKER, qry)) {
+ VERBOSE_MSG(qry, "=> DNS message failed to parse, %s\n",
+ knot_strerror(ret));
+ }
+ }
+
+ struct http_ctx *http_ctx = NULL;
+#if ENABLE_DOH2
+ http_ctx = session_http_get_server_ctx(session);
+
+ /* Badly formed query when using DoH leads to a Bad Request */
+ if (http_ctx && !is_outgoing && ret) {
+ http_send_status(session, HTTP_STATUS_BAD_REQUEST);
+ return kr_error(ret);
+ }
+#endif
+
+ if (!is_outgoing && http_ctx && queue_len(http_ctx->streams) <= 0)
+ return kr_error(ENOENT);
+
+ const struct sockaddr *addr = comm ? comm->src_addr : NULL;
+
+ /* Ignore badly formed queries. */
+ if (ret) {
+ if (is_outgoing && qry) // unusuable response from somewhat validated IP
+ qry->server_selection.error(qry, task->transport, KR_SELECTION_MALFORMED);
+ if (!is_outgoing)
+ the_worker->stats.dropped += 1;
+ if (task_matched_id) // notify task that answer won't be coming anymore
+ qr_task_step(task, addr, NULL);
+ return kr_error(EILSEQ);
+ }
+
+ /* Start new task on listening sockets,
+ * or resume if this is subrequest */
+ if (!is_outgoing) { /* request from a client */
+ struct request_ctx *ctx =
+ request_create(the_worker, session, comm, eth_from,
+ eth_to, knot_wire_get_id(pkt->wire));
+ if (http_ctx)
+ queue_pop(http_ctx->streams);
+ if (!ctx)
+ return kr_error(ENOMEM);
+
+ ret = request_start(ctx, pkt);
+ 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 { /* response from upstream */
+ if (task == NULL) {
+ return kr_error(ENOENT);
+ }
+ if (kr_fails_assert(!session_flags(session)->closing))
+ return kr_error(EINVAL);
+ }
+ if (kr_fails_assert(!uv_is_closing(session_get_handle(session))))
+ return kr_error(EINVAL);
+
+ /* Packet was successfully parsed.
+ * Task was created (found). */
+ session_touch(session);
+
+ /* Consume input and produce next message */
+ return qr_task_step(task, addr, pkt);
+}
+
+static int trie_add_tcp_session(trie_t *trie, const struct sockaddr *addr,
+ struct session *session)
+{
+ if (kr_fails_assert(trie && addr))
+ return kr_error(EINVAL);
+ struct kr_sockaddr_key_storage key;
+ ssize_t keylen = kr_sockaddr_key(&key, addr);
+ if (keylen < 0)
+ return keylen;
+ trie_val_t *val = trie_get_ins(trie, key.bytes, keylen);
+ if (kr_fails_assert(*val == NULL))
+ return kr_error(EINVAL);
+ *val = session;
+ return kr_ok();
+}
+
+static int trie_del_tcp_session(trie_t *trie, const struct sockaddr *addr)
+{
+ if (kr_fails_assert(trie && addr))
+ return kr_error(EINVAL);
+ struct kr_sockaddr_key_storage key;
+ ssize_t keylen = kr_sockaddr_key(&key, addr);
+ if (keylen < 0)
+ return keylen;
+ int ret = trie_del(trie, key.bytes, keylen, NULL);
+ return ret ? kr_error(ENOENT) : kr_ok();
+}
+
+static struct session *trie_find_tcp_session(trie_t *trie,
+ const struct sockaddr *addr)
+{
+ if (kr_fails_assert(trie && addr))
+ return NULL;
+ struct kr_sockaddr_key_storage key;
+ ssize_t keylen = kr_sockaddr_key(&key, addr);
+ if (keylen < 0)
+ return NULL;
+ trie_val_t *val = trie_get_try(trie, key.bytes, keylen);
+ return val ? *val : NULL;
+}
+
+int worker_add_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr* addr,
+ struct session *session)
+{
+ return trie_add_tcp_session(worker->tcp_connected, addr, session);
+}
+
+int worker_del_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ return trie_del_tcp_session(worker->tcp_connected, addr);
+}
+
+struct session* worker_find_tcp_connected(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ return trie_find_tcp_session(worker->tcp_connected, addr);
+}
+
+static int worker_add_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr,
+ struct session *session)
+{
+ return trie_add_tcp_session(worker->tcp_waiting, addr, session);
+}
+
+int worker_del_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ return trie_del_tcp_session(worker->tcp_waiting, addr);
+}
+
+struct session* worker_find_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr)
+{
+ return trie_find_tcp_session(worker->tcp_waiting, addr);
+}
+
+int worker_end_tcp(struct session *session)
+{
+ if (!session)
+ return kr_error(EINVAL);
+
+ session_timer_stop(session);
+
+ struct sockaddr *peer = session_get_peer(session);
+
+ worker_del_tcp_waiting(the_worker, peer);
+ worker_del_tcp_connected(the_worker, peer);
+ session_flags(session)->connected = false;
+
+ struct tls_client_ctx *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 *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);
+ kr_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 {
+ kr_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 {
+ kr_assert(task->ctx->source.session == session);
+ task->ctx->source.session = NULL;
+ }
+ worker_task_unref(task);
+ }
+ session_close(session);
+ return kr_ok();
+}
+
+knot_pkt_t *worker_resolve_mk_pkt_dname(knot_dname_t *qname, uint16_t qtype, uint16_t qclass,
+ const struct kr_qflags *options)
+{
+ knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_EDNS_MAX_UDP_PAYLOAD, NULL);
+ if (!pkt)
+ return NULL;
+ knot_pkt_put_question(pkt, qname, qclass, qtype);
+ knot_wire_set_rd(pkt->wire);
+ knot_wire_set_ad(pkt->wire);
+
+ /* Add OPT RR, including wire format so modules can see both representations.
+ * knot_pkt_put() copies the outside; we need to duplicate the inside manually. */
+ knot_rrset_t *opt = knot_rrset_copy(the_worker->engine->resolver.downstream_opt_rr, NULL);
+ if (!opt) {
+ knot_pkt_free(pkt);
+ return NULL;
+ }
+ if (options->DNSSEC_WANT) {
+ knot_edns_set_do(opt);
+ }
+ knot_pkt_begin(pkt, KNOT_ADDITIONAL);
+ int ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, opt, KNOT_PF_FREE);
+ if (ret == KNOT_EOK) {
+ free(opt); /* inside is owned by pkt now */
+ } else {
+ knot_rrset_free(opt, NULL);
+ knot_pkt_free(pkt);
+ return NULL;
+ }
+
+ if (options->DNSSEC_CD) {
+ knot_wire_set_cd(pkt->wire);
+ }
+
+ return pkt;
+}
+
+knot_pkt_t *worker_resolve_mk_pkt(const char *qname_str, uint16_t qtype, uint16_t qclass,
+ const struct kr_qflags *options)
+{
+ uint8_t qname[KNOT_DNAME_MAXLEN];
+ if (!knot_dname_from_str(qname, qname_str, sizeof(qname)))
+ return NULL;
+ return worker_resolve_mk_pkt_dname(qname, qtype, qclass, options);
+}
+
+struct qr_task *worker_resolve_start(knot_pkt_t *query, struct kr_qflags options)
+{
+ struct worker_ctx *worker = the_worker;
+ if (kr_fails_assert(worker && query))
+ return NULL;
+
+
+ struct request_ctx *ctx = request_create(worker, NULL, NULL, 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 kr_transport *worker_task_get_transport(struct qr_task *task)
+{
+ return task->transport;
+}
+
+struct session *worker_request_get_source_session(const struct kr_request *req)
+{
+ static_assert(offsetof(struct request_ctx, req) == 0,
+ "Bad struct request_ctx definition.");
+ return ((struct request_ctx *)req)->source.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. We assume worker's been zeroed. */
+static int worker_reserve(struct worker_ctx *worker)
+{
+ worker->tcp_connected = trie_create(NULL);
+ worker->tcp_waiting = trie_create(NULL);
+ worker->subreq_out = trie_create(NULL);
+
+ mm_ctx_mempool(&worker->pkt_pool, 4 * sizeof(knot_pkt_t));
+
+ return kr_ok();
+}
+
+void worker_deinit(void)
+{
+ struct worker_ctx *worker = the_worker;
+ if (kr_fails_assert(worker))
+ return;
+ trie_free(worker->tcp_connected);
+ trie_free(worker->tcp_waiting);
+ trie_free(worker->subreq_out);
+ worker->subreq_out = NULL;
+
+ for (int i = 0; i < worker->doh_qry_headers.len; i++)
+ free((void *)worker->doh_qry_headers.at[i]);
+ array_clear(worker->doh_qry_headers);
+
+ mp_delete(worker->pkt_pool.ctx);
+ worker->pkt_pool.ctx = NULL;
+
+ the_worker = NULL;
+}
+
+int worker_init(struct engine *engine, int worker_count)
+{
+ if (kr_fails_assert(engine && engine->L && the_worker == NULL))
+ return kr_error(EINVAL);
+ kr_bindings_register(engine->L);
+
+ /* Create main worker. */
+ struct worker_ctx *worker = &the_worker_value;
+ memset(worker, 0, sizeof(*worker));
+ worker->engine = engine;
+
+ uv_loop_t *loop = uv_default_loop();
+ worker->loop = loop;
+
+ worker->count = worker_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);
+
+ worker->tcp_pipeline_max = MAX_PIPELINED;
+ worker->out_addr4.sin_family = AF_UNSPEC;
+ worker->out_addr6.sin6_family = AF_UNSPEC;
+
+ array_init(worker->doh_qry_headers);
+
+ int ret = worker_reserve(worker);
+ if (ret) return ret;
+ worker->next_request_uid = UINT16_MAX + 1;
+
+ /* Set some worker.* fields in Lua */
+ lua_getglobal(engine->L, "worker");
+ pid_t pid = getpid();
+
+ auto_free char *pid_str = NULL;
+ const char *inst_name = getenv("SYSTEMD_INSTANCE");
+ if (inst_name) {
+ lua_pushstring(engine->L, inst_name);
+ } else {
+ ret = asprintf(&pid_str, "%ld", (long)pid);
+ kr_assert(ret > 0);
+ lua_pushstring(engine->L, pid_str);
+ }
+ lua_setfield(engine->L, -2, "id");
+
+ lua_pushnumber(engine->L, pid);
+ lua_setfield(engine->L, -2, "pid");
+ lua_pushnumber(engine->L, worker_count);
+ lua_setfield(engine->L, -2, "count");
+
+ char cwd[PATH_MAX];
+ get_workdir(cwd, sizeof(cwd));
+ lua_pushstring(engine->L, cwd);
+ lua_setfield(engine->L, -2, "cwd");
+
+ the_worker = worker;
+ loop->data = the_worker;
+ /* ^^^^ Now this shouldn't be used anymore, but it's hard to be 100% sure. */
+ return kr_ok();
+}
+
+#undef VERBOSE_MSG
diff --git a/daemon/worker.h b/daemon/worker.h
new file mode 100644
index 0000000..fd9b1f3
--- /dev/null
+++ b/daemon/worker.h
@@ -0,0 +1,195 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "daemon/engine.h"
+#include "lib/generic/array.h"
+#include "lib/generic/trie.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;
+/** Data about the communication (defined in io.h). */
+struct io_comm_data;
+
+/** Pointer to the singleton worker. NULL if not initialized. */
+KR_EXPORT extern struct worker_ctx *the_worker;
+
+/** Create and initialize the worker.
+ * \return error code (ENOMEM) */
+int worker_init(struct engine *engine, int worker_count);
+
+/** Destroy the worker (free memory). */
+void worker_deinit(void);
+
+/**
+ * Process an incoming packet (query from a client or answer from upstream).
+ *
+ * @param session session the packet came from, or NULL (not from network)
+ * @param comm IO communication data (see `struct io_comm_data` docs)
+ * @param eth_* MAC addresses or NULL (they're useful for XDP)
+ * @param pkt the packet, or NULL (an error from the transport layer)
+ * @return 0 or an error code
+ */
+int worker_submit(struct session *session, struct io_comm_data *comm,
+ const uint8_t *eth_from, const uint8_t *eth_to, knot_pkt_t *pkt);
+
+/**
+ * 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);
+
+KR_EXPORT knot_pkt_t *worker_resolve_mk_pkt_dname(knot_dname_t *qname, uint16_t qtype, uint16_t qclass,
+ const struct kr_qflags *options);
+
+/**
+ * Create a packet suitable for worker_resolve_start(). All in malloc() memory.
+ */
+KR_EXPORT knot_pkt_t *
+worker_resolve_mk_pkt(const char *qname_str, uint16_t qtype, uint16_t qclass,
+ const struct kr_qflags *options);
+
+/**
+ * Start query resolution with given query.
+ *
+ * @return task or NULL
+ */
+KR_EXPORT struct qr_task *
+worker_resolve_start(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
+ */
+KR_EXPORT 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);
+
+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);
+struct session* worker_find_tcp_waiting(struct worker_ctx *worker,
+ const struct sockaddr* addr);
+struct session* worker_find_tcp_connected(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 kr_transport *worker_task_get_transport(struct qr_task *task);
+
+/** Note: source session is NULL in case the request hasn't come over network. */
+KR_EXPORT struct session *worker_request_get_source_session(const struct kr_request *req);
+
+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);
+
+/** To be called after sending a DNS message. It mainly deals with cleanups. */
+int qr_task_on_send(struct qr_task *task, const uv_handle_t *handle, int status);
+
+/** Various worker statistics. Sync with wrk_stats() */
+struct worker_stats {
+ size_t queries; /**< Total number of requests (from clients and internal ones). */
+ size_t concurrent; /**< The number of requests currently in processing. */
+ size_t rconcurrent; /*< TODO: remove? I see no meaningful difference from .concurrent. */
+ size_t dropped; /**< The number of requests dropped due to being badly formed. See #471. */
+
+ size_t timeout; /**< Number of outbound queries that timed out. */
+ size_t udp; /**< Number of outbound queries over UDP. */
+ size_t tcp; /**< Number of outbound queries over TCP (excluding TLS). */
+ size_t tls; /**< Number of outbound queries over TLS. */
+ size_t ipv4; /**< Number of outbound queries over IPv4.*/
+ size_t ipv6; /**< Number of outbound queries over IPv6. */
+
+ size_t err_udp; /**< Total number of write errors for UDP transport. */
+ size_t err_tcp; /**< Total number of write errors for TCP transport. */
+ size_t err_tls; /**< Total number of write errors for TLS transport. */
+ size_t err_http; /**< Total number of write errors for HTTP(S) transport. */
+};
+
+/** @cond internal */
+
+/** Number of request within timeout window. */
+#define MAX_PENDING 4
+
+/** 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
+
+/** List of query resolution tasks. */
+typedef array_t(struct qr_task *) qr_tasklist_t;
+
+/** List of HTTP header names. */
+typedef array_t(const char *) doh_headerlist_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 count; /** unreliable, does not count systemd instance, do not use */
+ 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 worker_stats stats;
+
+ bool too_many_open;
+ size_t rconcurrent_highwatermark;
+ /** List of active outbound TCP sessions */
+ trie_t *tcp_connected;
+ /** List of outbound TCP sessions waiting to be accepted */
+ trie_t *tcp_waiting;
+ /** Subrequest leaders (struct qr_task*), indexed by qname+qtype+qclass. */
+ trie_t *subreq_out;
+ knot_mm_t pkt_pool;
+ unsigned int next_request_uid;
+
+ /* HTTP Headers for DoH. */
+ doh_headerlist_t doh_qry_headers;
+};
+
+/** @endcond */
+
diff --git a/daemon/zimport.c b/daemon/zimport.c
new file mode 100644
index 0000000..af21a15
--- /dev/null
+++ b/daemon/zimport.c
@@ -0,0 +1,741 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/* 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 and (optionally) ZONEMD verification.
+ * 2) DNSSEC validation and storage in cache.
+ *
+ * These stages are implemented as two separate functions
+ * (zi_zone_import and zi_zone_process) which run sequentially with a
+ * 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.
+ */
+
+#include "daemon/zimport.h"
+
+#include <inttypes.h> /* PRIu64 */
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <uv.h>
+
+#include <libknot/rrset.h>
+#include <libzscanner/scanner.h>
+
+#include <libknot/version.h>
+#define ENABLE_ZONEMD (KNOT_VERSION_HEX >= 0x030100)
+#if ENABLE_ZONEMD
+ #include <libdnssec/digest.h>
+
+ #if KNOT_VERSION_HEX < 0x030200
+ #define KNOT_ZONEMD_ALGORITHM_SHA384 KNOT_ZONEMD_ALORITHM_SHA384
+ #define KNOT_ZONEMD_ALGORITHM_SHA512 KNOT_ZONEMD_ALORITHM_SHA512
+ #endif
+#endif
+
+#include "daemon/worker.h"
+#include "lib/dnssec/ta.h"
+#include "lib/dnssec.h"
+#include "lib/generic/trie.h"
+#include "lib/utils.h"
+
+/* Pause between parse and import stages, milliseconds. */
+#define ZONE_IMPORT_PAUSE 100
+
+// NAN normally comes from <math.h> but it's not guaranteed.
+#ifndef NAN
+ #define NAN nan("")
+#endif
+
+struct zone_import_ctx {
+ knot_mm_t *pool; /// memory pool for all allocations (including struct itself)
+ knot_dname_t *origin;
+ uv_timer_t timer;
+
+ // from zi_config_t
+ zi_callback cb;
+ void *cb_param;
+
+ trie_t *rrsets; /// map: key_get() -> knot_rrset_t*, in ZONEMD order
+ uint32_t timestamp_rr; /// stamp of when RR data arrived (seconds since epoch)
+
+ struct kr_svldr_ctx *svldr; /// DNSSEC validator; NULL iff we don't validate
+ const knot_dname_t *last_cut; /// internal to zi_rrset_import()
+
+#if ENABLE_ZONEMD
+ uint8_t *digest_buf; /// temporary buffer for digest computation (on pool)
+ #define DIGEST_BUF_SIZE (64*1024 - 1)
+ #define DIGEST_ALG_COUNT 2
+ struct {
+ bool active; /// whether we want it computed
+ dnssec_digest_ctx_t *ctx;
+ const uint8_t *expected; /// expected digest (inside zonemd on pool)
+ } digests[DIGEST_ALG_COUNT]; /// we use indices 0 and 1 for SHA 384 and 512
+#endif
+};
+
+typedef struct zone_import_ctx zone_import_ctx_t;
+
+
+#define KEY_LEN (KNOT_DNAME_MAXLEN + 1 + 2 + 2)
+/** Construct key for name, type and signed type (if type == RRSIG).
+ *
+ * Return negative error code in asserted cases.
+ */
+static int key_get(char buf[KEY_LEN], const knot_dname_t *name,
+ uint16_t type, uint16_t type_maysig, char **key_p)
+{
+ char *lf = (char *)knot_dname_lf(name, (uint8_t *)buf);
+ if (kr_fails_assert(lf && key_p))
+ return kr_error(EINVAL);
+ int len = lf[0];
+ lf++; // point to start of data
+ *key_p = lf;
+ // Check that LF is right-aligned to KNOT_DNAME_MAXLEN in buf.
+ if (kr_fails_assert(lf + len == buf + KNOT_DNAME_MAXLEN))
+ return kr_error(EINVAL);
+ buf[KNOT_DNAME_MAXLEN] = 0; // this ensures correct ZONEMD order
+ memcpy(buf + KNOT_DNAME_MAXLEN + 1, &type, sizeof(type));
+ len += 1 + sizeof(type);
+ if (type == KNOT_RRTYPE_RRSIG) {
+ memcpy(buf + KNOT_DNAME_MAXLEN + 1 + sizeof(type),
+ &type_maysig, sizeof(type_maysig));
+ len += sizeof(type_maysig);
+ }
+ return len;
+}
+
+/** Simple helper to retreive from zone_import_ctx_t::rrsets */
+static knot_rrset_t * rrset_get(trie_t *rrsets, const knot_dname_t *name,
+ uint16_t type, uint16_t type_maysig)
+{
+ char key_buf[KEY_LEN], *key;
+ const int len = key_get(key_buf, name, type, type_maysig, &key);
+ if (len < 0)
+ return NULL;
+ const trie_val_t *rrsig_p = trie_get_try(rrsets, key, len);
+ if (!rrsig_p)
+ return NULL;
+ kr_assert(*rrsig_p);
+ return *rrsig_p;
+}
+
+#if ENABLE_ZONEMD
+static int digest_rrset(trie_val_t *rr_p, void *z_import_v)
+{
+ zone_import_ctx_t *z_import = z_import_v;
+ const knot_rrset_t *rr = *rr_p;
+
+ // ignore apex ZONEMD or its RRSIG, and also out of bailiwick records
+ const int origin_bailiwick = knot_dname_in_bailiwick(rr->owner, z_import->origin);
+ const bool is_apex = origin_bailiwick == 0;
+ if (is_apex && kr_rrset_type_maysig(rr) == KNOT_RRTYPE_ZONEMD)
+ return KNOT_EOK;
+ if (unlikely(origin_bailiwick < 0))
+ return KNOT_EOK;
+
+ const int len = knot_rrset_to_wire_extra(rr, z_import->digest_buf, DIGEST_BUF_SIZE,
+ 0, NULL, KNOT_PF_ORIGTTL);
+ if (len < 0)
+ return kr_error(len);
+
+ // digest serialized RRSet
+ for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
+ if (!z_import->digests[i].active)
+ continue;
+ dnssec_binary_t bufbin = { len, z_import->digest_buf };
+ int ret = dnssec_digest(z_import->digests[i].ctx, &bufbin);
+ if (ret != KNOT_EOK)
+ return kr_error(ret);
+ }
+ return KNOT_EOK;
+}
+
+/** Verify ZONEMD in the stored zone, and return error code.
+ *
+ * ZONEMD signature is verified iff z_import->svldr != NULL
+ https://www.rfc-editor.org/rfc/rfc8976.html#name-verifying-zone-digest
+ */
+static int zonemd_verify(zone_import_ctx_t *z_import)
+{
+ bool zonemd_is_valid = false;
+ // Find ZONEMD RR + RRSIG
+ knot_rrset_t * const rr_zonemd
+ = rrset_get(z_import->rrsets, z_import->origin, KNOT_RRTYPE_ZONEMD, 0);
+ if (!rr_zonemd) {
+ // no zonemd; let's compute the shorter digest and print info later
+ z_import->digests[KNOT_ZONEMD_ALGORITHM_SHA384 - 1].active = true;
+ goto do_digest;
+ }
+ // Validate ZONEMD RRSIG, if desired
+ if (z_import->svldr) {
+ const knot_rrset_t *rrsig_zonemd
+ = rrset_get(z_import->rrsets, z_import->origin,
+ KNOT_RRTYPE_RRSIG, KNOT_RRTYPE_ZONEMD);
+ int ret = rrsig_zonemd
+ ? kr_svldr_rrset(rr_zonemd, &rrsig_zonemd->rrs, z_import->svldr)
+ : kr_error(ENOENT);
+ zonemd_is_valid = (ret == kr_ok());
+
+ if (!rrsig_zonemd) {
+ kr_log_error(PREFILL, "ZONEMD signature missing\n");
+ } else if (!zonemd_is_valid) {
+ kr_log_error(PREFILL, "ZONEMD signature failed to validate\n");
+ }
+ }
+
+ // Get SOA serial
+ const knot_rrset_t *soa = rrset_get(z_import->rrsets, z_import->origin,
+ KNOT_RRTYPE_SOA, 0);
+ if (!soa) {
+ kr_log_error(PREFILL, "SOA record not found\n");
+ return kr_error(ENOENT);
+ }
+ if (soa->rrs.count != 1) {
+ kr_log_error(PREFILL, "the SOA RR set is weird\n");
+ return kr_error(EINVAL);
+ } // length is checked by parser already
+ const uint32_t soa_serial = knot_soa_serial(soa->rrs.rdata);
+
+ // Figure out SOA+ZONEMD RR contents.
+ bool some_active = false;
+ knot_rdata_t *rd = rr_zonemd->rrs.rdata;
+ for (int i = 0; i < rr_zonemd->rrs.count; ++i, rd = knot_rdataset_next(rd)) {
+ if (rd->len < 6 || knot_zonemd_scheme(rd) != KNOT_ZONEMD_SCHEME_SIMPLE
+ || knot_zonemd_soa_serial(rd) != soa_serial)
+ continue;
+ const int algo = knot_zonemd_algorithm(rd);
+ if (algo != KNOT_ZONEMD_ALGORITHM_SHA384 && algo != KNOT_ZONEMD_ALGORITHM_SHA512)
+ continue;
+ if (rd->len != 6 + knot_zonemd_digest_size(rd)) {
+ kr_log_error(PREFILL, "ZONEMD record has incorrect digest length\n");
+ return kr_error(EINVAL);
+ }
+ if (z_import->digests[algo - 1].active) {
+ kr_log_error(PREFILL, "multiple clashing ZONEMD records found\n");
+ return kr_error(EINVAL);
+ }
+ some_active = true;
+ z_import->digests[algo - 1].active = true;
+ z_import->digests[algo - 1].expected = knot_zonemd_digest(rd);
+ }
+ if (!some_active) {
+ kr_log_error(PREFILL, "ZONEMD record(s) found but none were usable\n");
+ return kr_error(ENOENT);
+ }
+do_digest:
+ // Init memory, etc.
+ if (!z_import->digest_buf) {
+ z_import->digest_buf = mm_alloc(z_import->pool, DIGEST_BUF_SIZE);
+ if (!z_import->digest_buf)
+ return kr_error(ENOMEM);
+ }
+ for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
+ const int algo = i + 1;
+ if (!z_import->digests[i].active)
+ continue;
+ int ret = dnssec_digest_init(algo, &z_import->digests[i].ctx);
+ if (ret != KNOT_EOK) {
+ // free previous successful _ctx, if applicable
+ dnssec_binary_t digest = { 0 };
+ while (--i >= 0) {
+ if (z_import->digests[i].active)
+ dnssec_digest_finish(z_import->digests[i].ctx,
+ &digest);
+ }
+ return kr_error(ENOMEM);
+ }
+ }
+ // Actually compute the digest(s).
+ int ret = trie_apply(z_import->rrsets, digest_rrset, z_import);
+ dnssec_binary_t digs[DIGEST_ALG_COUNT] = { { 0 } };
+ for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
+ if (!z_import->digests[i].active)
+ continue;
+ int ret2 = dnssec_digest_finish(z_import->digests[i].ctx, &digs[i]);
+ if (ret == DNSSEC_EOK)
+ ret = ret2;
+ // we need to keep going to free all digests[*].ctx
+ }
+ if (ret != DNSSEC_EOK) {
+ for (int i = 0; i < DIGEST_ALG_COUNT; ++i)
+ free(digs[i].data);
+ kr_log_error(PREFILL, "error when computing digest: %s\n",
+ kr_strerror(ret));
+ return kr_error(ret);
+ }
+ // Now only check that one of the hashes match.
+ bool has_match = false;
+ for (int i = 0; i < DIGEST_ALG_COUNT; ++i) {
+ if (!z_import->digests[i].active)
+ continue;
+ // hexdump the hash for logging
+ char hash_str[digs[i].size * 2 + 1];
+ for (ssize_t j = 0; j < digs[i].size; ++j)
+ sprintf(hash_str + 2*j, "%02x", digs[i].data[j]);
+
+ if (!z_import->digests[i].expected) {
+ kr_log_error(PREFILL, "no ZONEMD found; computed hash: %s\n",
+ hash_str);
+ } else if (memcmp(z_import->digests[i].expected, digs[i].data,
+ digs[i].size) != 0) {
+ kr_log_error(PREFILL, "ZONEMD hash mismatch; computed hash: %s\n",
+ hash_str);
+ } else {
+ kr_log_debug(PREFILL, "ZONEMD hash matches\n");
+ has_match = true;
+ continue;
+ }
+ }
+
+ for (int i = 0; i < DIGEST_ALG_COUNT; ++i)
+ free(digs[i].data);
+ bool ok = has_match && (zonemd_is_valid || !z_import->svldr);
+ return ok ? kr_ok() : kr_error(ENOENT);
+}
+#endif
+
+
+/**
+ * @internal Import given rrset to cache.
+ *
+ * @return error code; we could've chosen to keep importing even if some RRset fails,
+ * but it would be harder to ensure that we don't generate too many logs
+ * and that we pass an error to the finishing callback.
+ */
+static int zi_rrset_import(trie_val_t *rr_p, void *z_import_v)
+{
+ zone_import_ctx_t *z_import = z_import_v;
+ knot_rrset_t *rr = *rr_p;
+
+ if (rr->type == KNOT_RRTYPE_RRSIG)
+ return 0; // we do RRSIGs at once with their types
+
+ const int origin_bailiwick = knot_dname_in_bailiwick(rr->owner, z_import->origin);
+ if (unlikely(origin_bailiwick < 0)) {
+ KR_DNAME_GET_STR(owner_str, rr->owner);
+ kr_log_warning(PREFILL, "ignoring out of bailiwick record(s) on %s\n",
+ owner_str);
+ return 0; // well, let's continue without error
+ }
+
+ // Determine if this RRset is authoritative.
+ // We utilize that iteration happens in canonical order.
+ bool is_auth;
+ const int kdib = knot_dname_in_bailiwick(rr->owner, z_import->last_cut);
+ if (kdib == 0 && (rr->type == KNOT_RRTYPE_DS || rr->type == KNOT_RRTYPE_NSEC
+ || rr->type == KNOT_RRTYPE_NSEC3)) {
+ // parent side of the zone cut (well, presumably in case of NSEC*)
+ is_auth = true;
+ } else if (kdib >= 0) {
+ // inside non-auth subtree
+ is_auth = false;
+ } else if (rr->type == KNOT_RRTYPE_NS && origin_bailiwick > 0) {
+ // entering non-auth subtree
+ z_import->last_cut = rr->owner;
+ is_auth = false;
+ } else {
+ // outside non-auth subtree
+ is_auth = true;
+ z_import->last_cut = NULL; // so that the next _in_bailiwick() is faster
+ }
+ // Rare case: `A` exactly on zone cut would be misdetected and fail validation;
+ // it's the only type ordered before NS.
+ if (unlikely(is_auth && rr->type < KNOT_RRTYPE_NS)) {
+ if (rrset_get(z_import->rrsets, rr->owner, KNOT_RRTYPE_NS, 0))
+ is_auth = false;
+ }
+
+ // Get and validate the corresponding RRSIGs, if authoritative.
+ const knot_rrset_t *rrsig = NULL;
+ if (is_auth) {
+ rrsig = rrset_get(z_import->rrsets, rr->owner, KNOT_RRTYPE_RRSIG, rr->type);
+ if (unlikely(!rrsig && z_import->svldr)) {
+ KR_DNAME_GET_STR(owner_str, rr->owner);
+ KR_RRTYPE_GET_STR(type_str, rr->type);
+ kr_log_error(PREFILL, "no records found for %s RRSIG %s\n",
+ owner_str, type_str);
+ return kr_error(ENOENT);
+ }
+ }
+ if (is_auth && z_import->svldr) {
+ int ret = kr_svldr_rrset(rr, &rrsig->rrs, z_import->svldr);
+ if (unlikely(ret)) {
+ KR_DNAME_GET_STR(owner_str, rr->owner);
+ KR_RRTYPE_GET_STR(type_str, rr->type);
+ kr_log_error(PREFILL, "validation failed for %s %s: %s\n",
+ owner_str, type_str, kr_strerror(ret));
+ return kr_error(ret);
+ }
+ }
+
+ uint8_t rank;
+ if (!is_auth) {
+ rank = KR_RANK_OMIT;
+ } else if (z_import->svldr) {
+ rank = KR_RANK_AUTH|KR_RANK_SECURE;
+ } else {
+ rank = KR_RANK_AUTH|KR_RANK_INSECURE;
+ }
+
+ int ret = kr_cache_insert_rr(&the_worker->engine->resolver.cache, rr, rrsig,
+ rank, z_import->timestamp_rr,
+ // Optim.: only stash NSEC* params at the apex.
+ origin_bailiwick == 0);
+ if (ret) {
+ kr_log_error(PREFILL, "caching an RRset failed: %s\n",
+ kr_strerror(ret));
+ return kr_error(ret);
+ }
+ return 0; // success
+}
+
+static void ctx_delete(zone_import_ctx_t *z_import)
+{
+ if (kr_fails_assert(z_import)) return;
+ kr_svldr_free_ctx(z_import->svldr);
+
+ /* Free `z_import`'s pool, including `z_import` itself, because it is
+ * allocated inside said pool. */
+ mm_ctx_delete(z_import->pool);
+}
+static void timer_close(uv_handle_t *handle)
+{
+ ctx_delete(handle->data);
+}
+
+/** @internal Iterate over parsed rrsets and try to import each of them. */
+static void zi_zone_process(uv_timer_t *timer)
+{
+ zone_import_ctx_t *z_import = timer->data;
+
+ kr_timer_t stopwatch;
+ kr_timer_start(&stopwatch);
+
+ int ret = trie_apply(z_import->rrsets, zi_rrset_import, z_import);
+ (void)kr_cache_commit(&the_worker->engine->resolver.cache); // RW transaction open
+ if (ret == 0) {
+ kr_log_info(PREFILL, "performance: validating and caching took %.3lf s\n",
+ kr_timer_elapsed(&stopwatch));
+ }
+
+ if (z_import->cb)
+ z_import->cb(kr_error(ret), z_import->cb_param);
+ uv_close((uv_handle_t *)timer, timer_close);
+}
+
+/** @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(PREFILL, "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(PREFILL, "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(PREFILL, "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(PREFILL, "line %"PRIu64": error adding rdata to rrset\n",
+ s->line_counter);
+ return -1;
+ }
+ /* zscanner itself does not canonize - neither owner nor insides */
+ res = knot_rrset_rr_to_canonical(new_rr);
+ if (res != KNOT_EOK) {
+ kr_log_error(PREFILL, "line %"PRIu64": error when canonizing: %s\n",
+ s->line_counter, knot_strerror(res));
+ 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_buf[KEY_LEN], *key;
+ const int len = key_get(key_buf, new_rr->owner, new_rr->type,
+ kr_rrset_type_maysig(new_rr), &key);
+ if (len < 0) {
+ kr_log_error(PREFILL, "line %"PRIu64": error constructing rrkey\n",
+ s->line_counter);
+ return -1;
+ }
+ trie_val_t *rr_p = trie_get_ins(z_import->rrsets, key, len);
+ if (!rr_p)
+ return -1; // ENOMEM
+ if (*rr_p) {
+ knot_rrset_t *rr = *rr_p;
+ res = knot_rdataset_merge(&rr->rrs, &new_rr->rrs, z_import->pool);
+ } else {
+ *rr_p = new_rr;
+ }
+ if (res != 0) {
+ kr_log_error(PREFILL, "line %"PRIu64": error saving parsed rrset\n",
+ s->line_counter);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int zi_state_parsing(zs_scanner_t *s)
+{
+ bool empty = true;
+ 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;
+ empty = false;
+ if (s->r_type == KNOT_RRTYPE_SOA) {
+ z_import->origin = knot_dname_copy(s->r_owner,
+ z_import->pool);
+ }
+ break;
+ case ZS_STATE_ERROR:
+ kr_log_error(PREFILL, "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(PREFILL, "line: %"PRIu64
+ ": INCLUDE is not supported\n",
+ s->line_counter);
+ return -1;
+ case ZS_STATE_EOF:
+ case ZS_STATE_STOP:
+ if (empty) {
+ kr_log_error(PREFILL, "empty zone file\n");
+ return -1;
+ }
+ if (!((zone_import_ctx_t *) s->process.data)->origin) {
+ kr_log_error(PREFILL, "zone file doesn't contain SOA record\n");
+ return -1;
+ }
+ return (s->error.counter == 0) ? 0 : -1;
+ default:
+ kr_log_error(PREFILL, "line: %"PRIu64
+ ": unexpected parse state: %i\n",
+ s->line_counter, s->state);
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+int zi_zone_import(const zi_config_t config)
+{
+ const zi_config_t *c = &config;
+ if (kr_fails_assert(c && c->zone_file))
+ return kr_error(EINVAL);
+
+ knot_mm_t *pool = mm_ctx_mempool2(1024 * 1024);
+ zone_import_ctx_t *z_import = mm_calloc(pool, 1, sizeof(*z_import));
+ if (!z_import) return kr_error(ENOMEM);
+ z_import->pool = pool;
+
+ z_import->cb = c->cb;
+ z_import->cb_param = c->cb_param;
+ z_import->rrsets = trie_create(z_import->pool);
+
+ kr_timer_t stopwatch;
+ kr_timer_start(&stopwatch);
+
+ //// Parse the whole zone file into z_import->rrsets.
+ zs_scanner_t s_storage, *s = &s_storage;
+ /* 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 ret = zs_init(s, c->origin, KNOT_CLASS_IN, c->ttl);
+ if (ret != 0) {
+ kr_log_error(PREFILL, "error initializing zone scanner instance, error: %i (%s)\n",
+ s->error.code, zs_strerror(s->error.code));
+ goto fail;
+ }
+
+ ret = zs_set_input_file(s, c->zone_file);
+ if (ret != 0) {
+ kr_log_error(PREFILL, "error opening zone file `%s`, error: %i (%s)\n",
+ c->zone_file, s->error.code, zs_strerror(s->error.code));
+ zs_deinit(s);
+ goto fail;
+ }
+
+ /* 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. */
+ ret = zs_set_processing(s, NULL, NULL, (void *)z_import);
+ if (ret != 0) {
+ kr_log_error(PREFILL, "zs_set_processing() failed for zone file `%s`, "
+ "error: %i (%s)\n",
+ c->zone_file, s->error.code, zs_strerror(s->error.code));
+ zs_deinit(s);
+ goto fail;
+ }
+
+ ret = zi_state_parsing(s);
+ zs_deinit(s);
+ const double time_parse = kr_timer_elapsed(&stopwatch);
+ if (ret != 0) {
+ kr_log_error(PREFILL, "error parsing zone file `%s`\n", c->zone_file);
+ goto fail;
+ }
+ kr_log_debug(PREFILL, "import started for zone file `%s`\n", c->zone_file);
+
+ KR_DNAME_GET_STR(zone_name_str, z_import->origin);
+
+ //// Choose timestamp_rr, according to config.
+ struct timespec now;
+ if (clock_gettime(CLOCK_REALTIME, &now)) {
+ ret = kr_error(errno);
+ kr_log_error(PREFILL, "failed to get current time: %s\n", kr_strerror(ret));
+ goto fail;
+ }
+ if (config.time_src == ZI_STAMP_NOW) {
+ z_import->timestamp_rr = now.tv_sec;
+ } else if (config.time_src == ZI_STAMP_MTIM) {
+ struct stat st;
+ if (stat(c->zone_file, &st) != 0) {
+ kr_log_debug(PREFILL, "failed to stat file `%s`: %s\n",
+ c->zone_file, strerror(errno));
+ goto fail;
+ }
+ z_import->timestamp_rr = st.st_mtime;
+ } else {
+ ret = kr_error(EINVAL);
+ goto fail;
+ }
+ //// Some sanity checks
+ const knot_rrset_t *soa = rrset_get(z_import->rrsets, z_import->origin,
+ KNOT_RRTYPE_SOA, 0);
+ if (z_import->timestamp_rr > now.tv_sec) {
+ kr_log_warning(PREFILL, "zone file `%s` comes from future\n", c->zone_file);
+ } else if (!soa) {
+ kr_log_warning(PREFILL, "missing %s SOA\n", zone_name_str);
+ } else if ((int64_t)z_import->timestamp_rr + soa->ttl < now.tv_sec) {
+ kr_log_warning(PREFILL, "%s SOA already expired\n", zone_name_str);
+ }
+
+ //// Initialize validator context with the DNSKEY.
+ if (c->downgrade)
+ goto zonemd;
+ struct kr_context *resolver = &the_worker->engine->resolver;
+ const knot_rrset_t * const ds = c->ds ? c->ds :
+ kr_ta_get(resolver->trust_anchors, z_import->origin);
+ if (!ds) {
+ if (!kr_ta_closest(resolver, z_import->origin, KNOT_RRTYPE_DNSKEY))
+ goto zonemd; // our TAs say we're insecure
+ kr_log_error(PREFILL, "no DS found for `%s`, fail\n", zone_name_str);
+ ret = kr_error(ENOENT);
+ goto fail;
+ }
+ if (!knot_dname_is_equal(ds->owner, z_import->origin)) {
+ kr_log_error(PREFILL, "mismatching DS owner, fail\n");
+ ret = kr_error(EINVAL);
+ goto fail;
+ }
+
+ knot_rrset_t * const dnskey = rrset_get(z_import->rrsets, z_import->origin,
+ KNOT_RRTYPE_DNSKEY, 0);
+ if (!dnskey) {
+ kr_log_error(PREFILL, "no DNSKEY found for `%s`, fail\n", zone_name_str);
+ ret = kr_error(ENOENT);
+ goto fail;
+ }
+ knot_rrset_t * const dnskey_sigs = rrset_get(z_import->rrsets, z_import->origin,
+ KNOT_RRTYPE_RRSIG, KNOT_RRTYPE_DNSKEY);
+ if (!dnskey_sigs) {
+ kr_log_error(PREFILL, "no RRSIGs for DNSKEY found for `%s`, fail\n",
+ zone_name_str);
+ ret = kr_error(ENOENT);
+ goto fail;
+ }
+
+ kr_rrset_validation_ctx_t err_ctx;
+ z_import->svldr = kr_svldr_new_ctx(ds, dnskey, &dnskey_sigs->rrs,
+ z_import->timestamp_rr, &err_ctx);
+ if (!z_import->svldr) {
+ // log RRSIG stats; very similar to log_bogus_rrsig()
+ kr_log_error(PREFILL, "failed to validate DNSKEY for `%s` "
+ "(%u matching RRSIGs, %u expired, %u not yet valid, "
+ "%u invalid signer, %u invalid label count, %u invalid key, "
+ "%u invalid crypto, %u invalid NSEC)\n",
+ zone_name_str,
+ err_ctx.rrs_counters.matching_name_type,
+ err_ctx.rrs_counters.expired, err_ctx.rrs_counters.notyet,
+ err_ctx.rrs_counters.signer_invalid,
+ err_ctx.rrs_counters.labels_invalid,
+ err_ctx.rrs_counters.key_invalid,
+ err_ctx.rrs_counters.crypto_invalid,
+ err_ctx.rrs_counters.nsec_invalid);
+ ret = kr_error(ENOENT);
+ goto fail;
+ }
+
+ //// Do all ZONEMD processing, if desired.
+zonemd: (void)0; // C can't have a variable definition following a label
+ double time_zonemd = NAN;
+ if (c->zonemd) {
+ #if ENABLE_ZONEMD
+ kr_timer_start(&stopwatch);
+ ret = zonemd_verify(z_import);
+ time_zonemd = kr_timer_elapsed(&stopwatch);
+ #else
+ kr_log_error(PREFILL,
+ "ZONEMD check requested but not supported, fail\n");
+ ret = kr_error(ENOSYS);
+ #endif
+ } else {
+ ret = kr_ok();
+ }
+ kr_log_info(PREFILL, "performance: parsing took %.3lf s, hashing took %.3lf s\n",
+ time_parse, time_zonemd);
+ if (ret) goto fail;
+
+ //// Phase two, after a pause. Validate and import all the remaining records.
+ ret = uv_timer_init(the_worker->loop, &z_import->timer);
+ if (ret) goto fail;
+ z_import->timer.data = z_import;
+ ret = uv_timer_start(&z_import->timer, zi_zone_process, ZONE_IMPORT_PAUSE, 0);
+ if (ret) goto fail;
+
+ return kr_ok();
+fail:
+ if (z_import->cb)
+ z_import->cb(kr_error(ret), z_import->cb_param);
+ if (kr_fails_assert(ret))
+ ret = ENOENT;
+ ctx_delete(z_import);
+ return kr_error(ret);
+}
+
diff --git a/daemon/zimport.h b/daemon/zimport.h
new file mode 100644
index 0000000..5bbd992
--- /dev/null
+++ b/daemon/zimport.h
@@ -0,0 +1,48 @@
+/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <libknot/rrset.h>
+#include "lib/defines.h"
+
+/**
+ * Completion callback
+ *
+ * @param state 0 for OK completion, < 0 for errors (unfinished)
+ * @param param pointer to user data
+ */
+typedef void (*zi_callback)(int state, void *param);
+typedef struct {
+ /* Parser, see zs_init() */
+ const char *zone_file;
+ const char *origin;
+ uint32_t ttl;
+
+ /// Source of time: current real time, or file modification time.
+ enum { ZI_STAMP_NOW = 0, ZI_STAMP_MTIM } time_src;
+
+ /* Validator */
+ bool downgrade; /// true -> disable validation
+ bool zonemd; /// true -> verify zonemd
+ const knot_rrset_t *ds; /// NULL -> use trust anchors
+
+ zi_callback cb;
+ void *cb_param;
+} zi_config_t;
+
+/** Import zone from a file.
+ *
+ * Error can be directly returned in the first phase (parsing + ZONEMD);
+ * otherwise it will be kr_ok() and config->cb gets (optionally) called finally.
+ *
+ * Large zone would pause other processing for longer time;
+ * that's generally not advisable.
+ *
+ * Zone origin is detected from SOA, but it's certainly not perfect now.
+ */
+KR_EXPORT
+int zi_zone_import(const zi_config_t config);
+
diff --git a/daemon/zimport.test/tz-rfc-a1-bad.zone b/daemon/zimport.test/tz-rfc-a1-bad.zone
new file mode 100644
index 0000000..593489a
--- /dev/null
+++ b/daemon/zimport.test/tz-rfc-a1-bad.zone
@@ -0,0 +1,14 @@
+$ORIGIN example.
+example. 86400 IN SOA ns1 admin 2018031900 (
+ 1800 900 604800 86400 )
+ 86400 IN NS ns1
+ 86400 IN NS ns2
+ 86400 IN ZONEMD 2018031900 1 1 (
+ BAAAAAAADa7aed71
+ 6bc459f9340e3d7c
+ 1370d4d24b7e2fc3
+ a1ddc0b9a87153b9
+ a9713b3c9ae5cc27
+ 777f98b8e730044c )
+ns1 3600 IN A 203.0.113.63
+ns2 3600 IN AAAA 2001:db8::63
diff --git a/daemon/zimport.test/tz-rfc-a1.zone b/daemon/zimport.test/tz-rfc-a1.zone
new file mode 100644
index 0000000..5c43ec0
--- /dev/null
+++ b/daemon/zimport.test/tz-rfc-a1.zone
@@ -0,0 +1,14 @@
+$ORIGIN example.
+example. 86400 IN SOA ns1 admin 2018031900 (
+ 1800 900 604800 86400 )
+ 86400 IN NS ns1
+ 86400 IN NS ns2
+ 86400 IN ZONEMD 2018031900 1 1 (
+ c68090d90a7aed71
+ 6bc459f9340e3d7c
+ 1370d4d24b7e2fc3
+ a1ddc0b9a87153b9
+ a9713b3c9ae5cc27
+ 777f98b8e730044c )
+ns1 3600 IN A 203.0.113.63
+ns2 3600 IN AAAA 2001:db8::63
diff --git a/daemon/zimport.test/tz-rfc-a2.zone b/daemon/zimport.test/tz-rfc-a2.zone
new file mode 100644
index 0000000..5ae7f36
--- /dev/null
+++ b/daemon/zimport.test/tz-rfc-a2.zone
@@ -0,0 +1,35 @@
+$ORIGIN example.
+example. 86400 IN SOA ns1 admin 2018031900 (
+ 1800 900 604800 86400 )
+ 86400 IN NS ns1
+ 86400 IN NS ns2
+ 86400 IN ZONEMD 2018031900 1 1 (
+ a3b69bad980a3504
+ e1cffcb0fd6397f9
+ 3848071c93151f55
+ 2ae2f6b1711d4bd2
+ d8b39808226d7b9d
+ b71e34b72077f8fe )
+ns1 3600 IN A 203.0.113.63
+NS2 3600 IN AAAA 2001:db8::63
+occluded.sub 7200 IN TXT "I'm occluded but must be digested"
+sub 7200 IN NS ns1
+duplicate 300 IN TXT "I must be digested just once"
+duplicate 300 IN TXT "I must be digested just once"
+foo.test. 555 IN TXT "out-of-zone data must be excluded"
+UPPERCASE 3600 IN TXT "canonicalize uppercase owner names"
+* 777 IN PTR dont-forget-about-wildcards
+mail 3600 IN MX 20 MAIL1
+mail 3600 IN MX 10 Mail2.Example.
+sortme 3600 IN AAAA 2001:db8::5:61
+sortme 3600 IN AAAA 2001:db8::3:62
+sortme 3600 IN AAAA 2001:db8::4:63
+sortme 3600 IN AAAA 2001:db8::1:65
+sortme 3600 IN AAAA 2001:db8::2:64
+non-apex 900 IN ZONEMD 2018031900 1 1 (
+ 616c6c6f77656420
+ 6275742069676e6f
+ 7265642e20616c6c
+ 6f77656420627574
+ 2069676e6f726564
+ 2e20616c6c6f7765 )
diff --git a/daemon/zimport.test/tz-rfc-a3.zone b/daemon/zimport.test/tz-rfc-a3.zone
new file mode 100644
index 0000000..961dba9
--- /dev/null
+++ b/daemon/zimport.test/tz-rfc-a3.zone
@@ -0,0 +1,31 @@
+$ORIGIN example.
+example. 86400 IN SOA ns1 admin 2018031900 (
+ 1800 900 604800 86400 )
+example. 86400 IN NS ns1.example.
+example. 86400 IN NS ns2.example.
+example. 86400 IN ZONEMD 2018031900 1 1 (
+ 62e6cf51b02e54b9
+ b5f967d547ce4313
+ 6792901f9f88e637
+ 493daaf401c92c27
+ 9dd10f0edb1c56f8
+ 080211f8480ee306 )
+example. 86400 IN ZONEMD 2018031900 1 2 (
+ 08cfa1115c7b948c
+ 4163a901270395ea
+ 226a930cd2cbcf2f
+ a9a5e6eb85f37c8a
+ 4e114d884e66f176
+ eab121cb02db7d65
+ 2e0cc4827e7a3204
+ f166b47e5613fd27 )
+example. 86400 IN ZONEMD 2018031900 1 240 (
+ e2d523f654b9422a
+ 96c5a8f44607bbee )
+example. 86400 IN ZONEMD 2018031900 241 1 (
+ e1846540e33a9e41
+ 89792d18d5d131f6
+ 05fc283e )
+ns1.example. 3600 IN A 203.0.113.63
+ns2.example. 86400 IN TXT "This example has multiple digests"
+NS2.EXAMPLE. 3600 IN AAAA 2001:db8::63
diff --git a/daemon/zimport.test/tz-rfc-a4.zone b/daemon/zimport.test/tz-rfc-a4.zone
new file mode 100644
index 0000000..19d89d6
--- /dev/null
+++ b/daemon/zimport.test/tz-rfc-a4.zone
@@ -0,0 +1,37 @@
+$ORIGIN example.
+;; White-space had to be changed from the RFC, as libzscanner only allows spaces in base64 on some places.
+uri.arpa. 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2018100702 10800 3600 1209600 3600
+uri.arpa. 3600 IN RRSIG SOA 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. GzQw+QzwLDJr13REPGVmpEChjD1D2XlX0ie1DnWHpgaEw1E/dhs3lCN3 +BmHd4Kx3tffTRgiyq65HxR6feQ5v7VmAifjyXUYB1DZur1eP5q0Ms2y gCB3byoeMgCNsFS1oKZ2LdzNBRpy3oace8xQn1SpmHGfyrsgg+WbHKCT 1dY=
+uri.arpa. 86400 IN NS a.iana-servers.net.
+uri.arpa. 86400 IN NS b.iana-servers.net.
+uri.arpa. 86400 IN NS c.iana-servers.net.
+uri.arpa. 86400 IN NS ns2.lacnic.net.
+uri.arpa. 86400 IN NS sec3.apnic.net.
+uri.arpa. 86400 IN RRSIG NS 8 2 86400 20210217232440 20210120232440 37444 uri.arpa. M+Iei2lcewWGaMtkPlrhM9FpUAHXFkCHTVpeyrjxjEONeNgKtHZor5e4 V4qJBOzNqo8go/qJpWlFBm+T5Hn3asaBZVstFIYky38/C8UeRLPKq1hT THARYUlFrexr5fMtSUAVOgOQPSBfH3xBq/BgSccTdRb9clD+HE7djpqr LS4=
+uri.arpa. 600 IN MX 10 pechora.icann.org.
+uri.arpa. 600 IN RRSIG MX 8 2 600 20210217232440 20210120232440 37444 uri.arpa. kQAJQivmv6A5hqYBK8h6Z13ESY69gmosXwKI6WE09I8RFetfrxr24ecd nYd0lpnDtgNNSoHkYRSOoB+C4+zuJsoyAAzGo9uoWMWj97/2xeGhf3PT C9meQ9Ohi6hul9By7OR76XYmGhdWX8PBi60RUmZ1guslFBfQ8izwPqzu phs=
+uri.arpa. 3600 IN NSEC ftp.uri.arpa. NS SOA MX RRSIG NSEC DNSKEY ZONEMD
+uri.arpa. 3600 IN RRSIG NSEC 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. dU/rXLM/naWd1+1PiWiYVaNJyCkiuyZJSccr91pJI673T8r3685B4ODM YFafZRboVgwnl3ZrXddY6xOhZL3n9V9nxXZwjLJ2HJUojFoKcXTlpnUy YUYvVQ2kj4GHAo6fcGCEp5QFJ2KbCpeJoS+PhKGRRx28icCiNT4/uXQv O2E=
+uri.arpa. 3600 IN DNSKEY 256 3 8 AwEAAbMxuFuLeVDuOwIMzYOTD/bTREjLflo7wOi6ieIJhqltEzgjNzmW Jf9kGwwDmzxU7kbthMEhBNBZNn84zmcyRSCMzuStWveL7xmqqUlE3swL 8kLOvdZvc75XnmpHrk3ndTyEb6eZM7slh2C63Oh6K8VR5VkiZAkEGg0u ZIT3NjsF
+uri.arpa. 3600 IN DNSKEY 257 3 8 AwEAAdkTaWkZtZuRh7/OobBUFxM+ytTst+bCu0r9w+rEwXD7GbDs0pIM hMenrZzoAvmv1fQxw2MGs6Ri6yPKfNULcFOSt9l8i6BVBLI+SKTY6XXe DUQpSEmSaxohHeRPMQFzpysfjxINp/L2rGtZ7yPmxY/XRiFPSO0myqwG Ja9r06Zw9CHM5UDHKWV/E+zxPFq/I7CfPbrrzbUotBX7Z6Vh3Sarllbe 8cGUB2UFNaTRgwB0TwDBPRD5ER3w2Dzbry9NhbElTr7vVfhaGWeOGuqA UXwlXEg6CrNkmJXJ2F1Rzr9WHUzhp7uWxhAbmJREGfi2dEyPAbUAyCjB qhFaqglknvc=
+uri.arpa. 3600 IN DNSKEY 257 3 8 AwEAAenQaBoFmDmvRT+/H5oNbm0Tr5FmNRNDEun0Jpj/ELkzeUrTWhNp QmZeIMC8I0kZ185tEvOnRvn8OvV39B17QIdrvvKGIh2HlgeDRCLolhao jfn2QM0DStjF/WWHpxJOmE6CIuvhqYEU37yoJscGAPpPVPzNvnL1HhYT aao1VRYWQ/maMrJ+bfHg+YX1N6M/8MnRjIKBif1FWjbCKvsn6dnuGGL9 oCWYUFJ3DwofXuhgPyZMkzPc88YkJj5EMvbMH4wtelbCwC+ivx732l0w /rXJn0ciQSOgoeVvDio8dIJmWQITWQAuP+q/ZHFEFHPlrP3gvQh5mcVS 48eLX71Bq7c=
+uri.arpa. 3600 IN RRSIG DNSKEY 8 2 3600 20210217232440 20210120232440 12670 uri.arpa. DBE2gkKAoxJCfz47KKxzoImN/0AKArhIVHE7TyTwy0DdRPo44V5R+vL6 thUxlQ1CJi2Rw0jwAXymx5Y3Q873pOEllH+4bJoIT4dmoBmPXfYWW7Cl vw9UPKHRP0igKHmCVwIeBYDTU3gfLcMTbR4nEWPDN0GxlL1Mf7ITaC2I oabo79Ip3M/MR8I3Vx/xZ4ZKKPHtLn3xUuJluPNanqJrED2gTslL2xWZ 1tqjsAjJv7JnJo2HJ8XVRB5zBto0IaJ2oBlqcjdcQ/0VlyoM8uOy1pDw HQ2BJl7322gNMHBP9HSiUPIOaIDNUCwW8eUcW6DIUk+s9u3GN1uTqwWz sYB/rA==
+uri.arpa. 3600 IN RRSIG DNSKEY 8 2 3600 20210217232440 20210120232440 30577 uri.arpa. Kx6HwP4UlkGc1UZ7SERXtQjPajOF4iUvkwDj7MEG1xbQFB1KoJiEb/ei W0qmSWdIhMDv8myhgauejRLyJxwxz8HDRV4xOeHWnRGfWBk4XGYwkejV zOHzoIArVdUVRbr2JKigcTOoyFN+uu52cNB7hRYu7dH5y1hlc6UbOnzR pMtGxcgVyKQ+/ARbIqGG3pegdEOvV49wTPWEiyY65P2urqhvnRg5ok/j zwAdMx4XGshiib7Ojq0sRVl2ZIzj4rFgY/qsSO8SEXEhMo2VuSkoJNio fVzYoqpxEeGnANkIT7Tx2xJL1BWyJxyc7E8Wr2QSgCcc+rYL6IkHDtJG Hy7TaQ==
+uri.arpa. 3600 IN ZONEMD 2018100702 1 1 0DBC3C4DBFD75777C12CA19C337854B1577799901307C482E9D91D5D 15CD934D16319D98E30C4201CF25A1D5A0254960
+uri.arpa. 3600 IN RRSIG ZONEMD 8 2 3600 20210217232440 20210120232440 37444 uri.arpa. QDo4XZcL3HMyn8aAHyCUsu/Tqj4Gkth8xY1EqByOb8XOTwVtA4ZNQORE 1siqNqjtJUbeJPtJSbLNqCL7rCq0CzNNnBscv6IIf4gnqJZjlGtHO30o hXtKvEc4z7SU3IASsi6bB3nLmEAyERdYSeU6UBfx8vatQDIRhkgEnnWU Th4=
+ftp.uri.arpa. 604800 IN NAPTR 0 0 "" "" "!^ftp://([^:/?#]*).*$!\\1!i" .
+ftp.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. EygekDgl+Lyyq4NMSEpPyOrOywYf9Y3FAB4v1DT44J3R5QGidaH8l7ZF jHoYFI8sY64iYOCV4sBnX/dh6C1L5NgpY+8l5065Xu3vvjyzbtuJ2k6Y YwJrrCbvl5DDn53zAhhO2hL9uLgyLraZGi9i7TFGd0sm3zNyUF/EVL0C cxU=
+ftp.uri.arpa. 3600 IN NSEC http.uri.arpa. NAPTR RRSIG NSEC
+ftp.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. pbP4KxevPXCu/bDqcvXiuBppXyFEmtHyiy0eAN5gS7mi6mp9Z9bWFjx/ LdH9+6oFGYa5vGmJ5itu/4EDMe8iQeZbI8yrpM4TquB7RR/MGfBnTd8S +sjyQtlRYG7yqEu77Vd78Fme22BKPJ+MVqjS0JHMUE/YUGomPkAjLJJw wGw=
+http.uri.arpa. 604800 IN NAPTR 0 0 "" "" "!^http://([^:/?#]*).*$!\\1!i" .
+http.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. eTqbWvt1GvTeXozuvm4ebaAfkXFQKrtdu0cEiExto80sHIiCbO0WL8UD a/J3cDivtQca7LgUbOb6c17NESsrsVkc6zNPx5RK2tG7ZQYmhYmtqtfg 1oU5BRdHZ5TyqIXcHlw9Blo2pir1Y9IQgshhD7UOGkbkEmvB1Lrd0aHh AAg=
+http.uri.arpa. 3600 IN NSEC mailto.uri.arpa. NAPTR RRSIG NSEC
+http.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. R9rlNzw1CVz2N08q6DhULzcsuUm0UKcPaGAWEU40tr81jEDHsFHNM+kh CdOI8nDstzA42aee4rwCEgijxJpRCcY9hrO1Ysrrr2fdqNz60JikMdar vU5O0p0VXeaaJDfJQT44+o+YXaBwI7Qod3FTMx7aRib8i7istvPm1Rr7 ixA=
+mailto.uri.arpa. 604800 IN NAPTR 0 0 "" "" "!^mailto:(.*)@(.*)$!\\2!i" .
+mailto.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. Ch2zTG2F1plEvQPyIH4Yd80XXLjXOPvMbiqDjpJBcnCJsV8QF7kr0wTL nUT3dB+asQudOjPyzaHGwFlMzmrrAsszN4XAMJ6htDtFJdsgTMP/NkHh YRSmVv6rLeAhd+mVfObY12M//b/GGVTjeUI/gJaLW0fLVZxr1Fp5U5CR jyw=
+mailto.uri.arpa. 3600 IN NSEC urn.uri.arpa. NAPTR RRSIG NSEC
+mailto.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. fQUbSIE6E7JDi2rosah4SpCOTrKufeszFyj5YEavbQuYlQ5cNFvtm8Ku E2xXMRgRI4RGvM2leVqcoDw5hS3m2pOJLxH8l2WE72YjYvWhvnwc5Rof e/8yB/vaSK9WCnqN8y2q6Vmy73AGP0fuiwmuBra7LlkOiqmyx3amSFiz wms=
+urn.uri.arpa. 604800 IN NAPTR 0 0 "" "" "/urn:([^:]+)/\\1/i" .
+urn.uri.arpa. 604800 IN RRSIG NAPTR 8 3 604800 20210217232440 20210120232440 37444 uri.arpa. CVt2Tgz0e5ZmaSXqRfNys/8OtVCk9nfP0zhezhN8Bo6MDt6yyKZ2kEEW JPjkN7PCYHjO8fGjnUn0AHZI2qBNv7PKHcpR42VY03q927q85a65weOO 1YE0vPYMzACpua9TOtfNnynM2Ws0uN9URxUyvYkXBdqOC81N3sx1dVEL cwc=
+urn.uri.arpa. 3600 IN NSEC uri.arpa. NAPTR RRSIG NSEC
+urn.uri.arpa. 3600 IN RRSIG NSEC 8 3 3600 20210217232440 20210120232440 37444 uri.arpa. JuKkMiC3/j9iM3V8/izcouXWAVGnSZjkOgEgFPhutMqoylQNRcSkbEZQ zFK8B/PIVdzZF0Y5xkO6zaKQjOzz6OkSaNPIo1a7Vyyl3wDY/uLCRRAH RJfpknuY7O+AUNXvVVIEYJqZggd4kl/Rjh1GTzPYZTRrVi5eQidI1LqC Oeg=
diff --git a/daemon/zimport.test/tz-rfc-a5.zone b/daemon/zimport.test/tz-rfc-a5.zone
new file mode 100644
index 0000000..246f5e2
--- /dev/null
+++ b/daemon/zimport.test/tz-rfc-a5.zone
@@ -0,0 +1,48 @@
+root-servers.net. 3600000 IN SOA a.root-servers.net. (
+ nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 )
+root-servers.net. 3600000 IN NS a.root-servers.net.
+root-servers.net. 3600000 IN NS b.root-servers.net.
+root-servers.net. 3600000 IN NS c.root-servers.net.
+root-servers.net. 3600000 IN NS d.root-servers.net.
+root-servers.net. 3600000 IN NS e.root-servers.net.
+root-servers.net. 3600000 IN NS f.root-servers.net.
+root-servers.net. 3600000 IN NS g.root-servers.net.
+root-servers.net. 3600000 IN NS h.root-servers.net.
+root-servers.net. 3600000 IN NS i.root-servers.net.
+root-servers.net. 3600000 IN NS j.root-servers.net.
+root-servers.net. 3600000 IN NS k.root-servers.net.
+root-servers.net. 3600000 IN NS l.root-servers.net.
+root-servers.net. 3600000 IN NS m.root-servers.net.
+a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30
+a.root-servers.net. 3600000 IN A 198.41.0.4
+b.root-servers.net. 3600000 IN MX 20 mail.isi.edu.
+b.root-servers.net. 3600000 IN AAAA 2001:500:200::b
+b.root-servers.net. 3600000 IN A 199.9.14.201
+c.root-servers.net. 3600000 IN AAAA 2001:500:2::c
+c.root-servers.net. 3600000 IN A 192.33.4.12
+d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d
+d.root-servers.net. 3600000 IN A 199.7.91.13
+e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e
+e.root-servers.net. 3600000 IN A 192.203.230.10
+f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f
+f.root-servers.net. 3600000 IN A 192.5.5.241
+g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d
+g.root-servers.net. 3600000 IN A 192.112.36.4
+h.root-servers.net. 3600000 IN AAAA 2001:500:1::53
+h.root-servers.net. 3600000 IN A 198.97.190.53
+i.root-servers.net. 3600000 IN MX 10 mx.i.root-servers.org.
+i.root-servers.net. 3600000 IN AAAA 2001:7fe::53
+i.root-servers.net. 3600000 IN A 192.36.148.17
+j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30
+j.root-servers.net. 3600000 IN A 192.58.128.30
+k.root-servers.net. 3600000 IN AAAA 2001:7fd::1
+k.root-servers.net. 3600000 IN A 193.0.14.129
+l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42
+l.root-servers.net. 3600000 IN A 199.7.83.42
+m.root-servers.net. 3600000 IN AAAA 2001:dc3::35
+m.root-servers.net. 3600000 IN A 202.12.27.33
+root-servers.net. 3600000 IN SOA a.root-servers.net. (
+ nstld.verisign-grs.com. 2018091100 14400 7200 1209600 3600000 )
+root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 (
+ f1ca0ccd91bd5573d9f431c00ee0101b2545c97602be0a97
+ 8a3b11dbfc1c776d5b3e86ae3d973d6b5349ba7f04340f79 )
diff --git a/daemon/zimport.test/zimport.test.lua b/daemon/zimport.test/zimport.test.lua
new file mode 100644
index 0000000..f6abc02
--- /dev/null
+++ b/daemon/zimport.test/zimport.test.lua
@@ -0,0 +1,47 @@
+-- unload modules which are not related to this test
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+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
+
+-- do not listen, test is driven by config code
+env.KRESD_NO_LISTEN = true
+
+
+cache.size = 5*MB
+log_groups({'prefil'})
+
+--[[ This test checks ZONEMD computation on some model cases. (no DNSSEC validation)
+ https://www.rfc-editor.org/rfc/rfc8976.html#name-example-zones-with-digests
+--]]
+
+
+local function test_zone(file_name, success) return function()
+ local import_res = require('ffi').C.zi_zone_import({
+ zone_file = file_name,
+ zonemd = true,
+ downgrade = true,
+ })
+ if success == nil or success then
+ is(import_res, 0, 'zone import should start OK for file ' .. file_name)
+ else
+ isnt(import_res, 0, 'zone import should fail for file ' .. file_name)
+ end
+ worker.sleep(0.2) -- zimport is delayed by 100 ms from function call
+end end
+
+return {
+ test_zone('tz-rfc-a1.zone'),
+ test_zone('tz-rfc-a1-bad.zone', false),
+ test_zone('tz-rfc-a2.zone'),
+ test_zone('tz-rfc-a3.zone'),
+ test_zone('tz-rfc-a4.zone'),
+ test_zone('tz-rfc-a5.zone'),
+}