aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--website/data/security/advisories.toml4
-rw-r--r--website/data/security/errata.toml16
-rw-r--r--website/static/security/advisories/FreeBSD-EN-24:05.tty.asc132
-rw-r--r--website/static/security/advisories/FreeBSD-EN-24:06.wireguard.asc138
-rw-r--r--website/static/security/advisories/FreeBSD-EN-24:07.clang.asc127
-rw-r--r--website/static/security/advisories/FreeBSD-EN-24:08.kerberos.asc127
-rw-r--r--website/static/security/advisories/FreeBSD-SA-24:03.unbound.asc147
-rw-r--r--website/static/security/patches/EN-24:05/tty.patch23
-rw-r--r--website/static/security/patches/EN-24:05/tty.patch.asc16
-rw-r--r--website/static/security/patches/EN-24:06/wireguard.patch40
-rw-r--r--website/static/security/patches/EN-24:06/wireguard.patch.asc16
-rw-r--r--website/static/security/patches/EN-24:07/clang.patch25
-rw-r--r--website/static/security/patches/EN-24:07/clang.patch.asc16
-rw-r--r--website/static/security/patches/EN-24:08/kerberos.patch203
-rw-r--r--website/static/security/patches/EN-24:08/kerberos.patch.asc16
-rw-r--r--website/static/security/patches/SA-24:03/unbound-13.patch24911
-rw-r--r--website/static/security/patches/SA-24:03/unbound-13.patch.asc16
-rw-r--r--website/static/security/patches/SA-24:03/unbound-14.patch24911
-rw-r--r--website/static/security/patches/SA-24:03/unbound-14.patch.asc16
19 files changed, 50900 insertions, 0 deletions
diff --git a/website/data/security/advisories.toml b/website/data/security/advisories.toml
index 15f9a96938..d2a4603d5f 100644
--- a/website/data/security/advisories.toml
+++ b/website/data/security/advisories.toml
@@ -2,6 +2,10 @@
# $FreeBSD$
[[advisories]]
+name = "FreeBSD-SA-24:03.unbound"
+date = "2024-03-28"
+
+[[advisories]]
name = "FreeBSD-SA-24:02.tty"
date = "2024-02-14"
diff --git a/website/data/security/errata.toml b/website/data/security/errata.toml
index c292820014..50d0a89e3a 100644
--- a/website/data/security/errata.toml
+++ b/website/data/security/errata.toml
@@ -2,6 +2,22 @@
# $FreeBSD$
[[notices]]
+name = "FreeBSD-EN-24:08.kerberos"
+date = "2024-03-28"
+
+[[notices]]
+name = "FreeBSD-EN-24:07.clang"
+date = "2024-03-28"
+
+[[notices]]
+name = "FreeBSD-EN-24:06.wireguard"
+date = "2024-03-28"
+
+[[notices]]
+name = "FreeBSD-EN-24:05.tty"
+date = "2024-03-28"
+
+[[notices]]
name = "FreeBSD-EN-24:04.ip"
date = "2024-02-14"
diff --git a/website/static/security/advisories/FreeBSD-EN-24:05.tty.asc b/website/static/security/advisories/FreeBSD-EN-24:05.tty.asc
new file mode 100644
index 0000000000..979d0c2c5f
--- /dev/null
+++ b/website/static/security/advisories/FreeBSD-EN-24:05.tty.asc
@@ -0,0 +1,132 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+=============================================================================
+FreeBSD-EN-24:05.tty Erratum Notice
+ The FreeBSD Project
+
+Topic: TTY Kernel Panic
+
+Category: core
+Module: kernel
+Announced: 2024-03-28
+Affects: FreeBSD 13.2 and FreeBSD 14.0
+Corrected: 2024-02-29 00:29:13 UTC (stable/14, 14.0-STABLE)
+ 2024-03-28 05:06:21 UTC (releng/14.0, 14.0-RELEASE-p6)
+ 2024-02-29 00:30:12 UTC (stable/13, 13.2-STABLE)
+ 2024-03-28 05:07:53 UTC (releng/13.2, 13.2-RELEASE-p11)
+
+For general information regarding FreeBSD Errata Notices and Security
+Advisories, including descriptions of the fields above, security
+branches, and the following sections, please visit
+<URL:https://security.FreeBSD.org/>.
+
+I. Background
+
+tty(4) is the general terminal device. The kern.ttys sysctl provides tty
+information for tools such as `pstat -t`.
+
+FreeBSD-SA-24:02.tty addressed an information leak about outside processes
+from within a jail.
+
+II. Problem Description
+
+A missing check resulted in a null pointer dereference if a tty had a session
+associated, but no session leader.
+
+III. Impact
+
+Under certain conditions an unprivileged user could provoke a kernel panic.
+
+IV. Workaround
+
+No workaround is available.
+
+V. Solution
+
+Upgrade your system to a supported FreeBSD stable or release / security
+branch (releng) dated after the correction date, and reboot.
+
+Perform one of the following:
+
+1) To update your system via a binary patch:
+
+Systems running a RELEASE version of FreeBSD on the amd64 or arm64 platforms,
+or the i386 platform on FreeBSD 13, can be updated via the freebsd-update(8)
+utility:
+
+# freebsd-update fetch
+# freebsd-update install
+# shutdown -r +10min "Rebooting for an erratum update"
+
+2) To update your system via a source code patch:
+
+The following patches have been verified to apply to the applicable
+FreeBSD release branches.
+
+a) Download the relevant patch from the location below, and verify the
+detached PGP signature using your PGP utility.
+
+# fetch https://security.FreeBSD.org/patches/EN-24:05/tty.patch
+# fetch https://security.FreeBSD.org/patches/EN-24:05/tty.patch.asc
+# gpg --verify tty.patch.asc
+
+b) Apply the patch. Execute the following commands as root:
+
+# cd /usr/src
+# patch < /path/to/patch
+
+c) Recompile your kernel as described in
+<URL:https://www.FreeBSD.org/handbook/kernelconfig.html> and reboot the
+system.
+
+VI. Correction details
+
+This issue is corrected as of the corresponding Git commit hash in the
+following stable and release branches:
+
+Branch/path Hash Revision
+- -------------------------------------------------------------------------
+stable/14/ 8d22744f5be1 stable/14-n266915
+releng/14.0/ a3ec3054762f releng/14.0-n265411
+stable/13/ a60220bbb551 stable/13-n257543
+releng/13.2/ f3195cc08ccc releng/13.2-n254662
+- -------------------------------------------------------------------------
+
+Run the following command to see which files were modified by a
+particular commit:
+
+# git show --stat <commit hash>
+
+Or visit the following URL, replacing NNNNNN with the hash:
+
+<URL:https://cgit.freebsd.org/src/commit/?id=NNNNNN>
+
+To determine the commit count in a working tree (for comparison against
+nNNNNNN in the table above), run:
+
+# git rev-list --count --first-parent HEAD
+
+VII. References
+
+<URL:https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=277240>
+<URL:https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=277329>
+
+The latest revision of this advisory is available at
+<URL:https://security.FreeBSD.org/advisories/FreeBSD-EN-24:05.tty.asc>
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAEBCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGaEACgkQbljekB8A
+Gu8NTw//Rqyq8heDUZZyz0TKMs/ObZY9h7VbL3Pces9mpnE6mgZx9g1kalo1xml3
+x0kRIJ0L606oBxhrJYqam3DrcJsPWs/8LOmmUa9u4/M2sAPuw03pyPEYNnokhf05
+NvC6mjNCpuJY4jzoa1hYdjvUHJe6u66reEoWuARPxoT6ZGPLiVhYPmoYIJFtoEAy
+tLEIH4GRjfRuOEgSDY7sIy5MoxjObBqPQl4VtbCSZDN/PN4z6WuxC/f2N0vpN1uq
+IyDGWCvEOa6g+7kDEiBJo4LRp30mQtMJalfQUlLm653Do2Jh6L5tUuQ+T0qIOlqc
+gTlKnnaa0m/hMUD9t4lJHQbLfGFaYpXbyJpblO8hPoM7Trk2vsoGubksMYZSRHIy
+/9IiZafdnNoHxa5+ZTRSqxYw9e38gwTlWsNjQpCezhtaZo0FWkhcgC7zUG+yMUXz
+zYhYXCQkZXpEvIg+BJs3ZdigGK7wRjC9qsC8jfnhOU+q452qqnKjg8bxJdGxBbZ0
+HKFfAVgtqAEgU3PzPN7Nmu4QJ+VOu9L/e1mOhrqcmHtYDYLfdelCT8DjHj85oggn
+C5iDPG6AxnLczTlTxVsHTiQcmTy6awfeTf1N1JCbfZPovrO/CTaOLnMy/PNeZIml
+UnarxLtQNeK6BDKd0E/rEym9wL0YJ1Xj/3XE1qPAjz52YufRHHM=
+=w167
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/advisories/FreeBSD-EN-24:06.wireguard.asc b/website/static/security/advisories/FreeBSD-EN-24:06.wireguard.asc
new file mode 100644
index 0000000000..a7c7fb1c09
--- /dev/null
+++ b/website/static/security/advisories/FreeBSD-EN-24:06.wireguard.asc
@@ -0,0 +1,138 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+=============================================================================
+FreeBSD-EN-24:06.wireguard Errata Notice
+ The FreeBSD Project
+
+Topic: Insufficient barriers in WireGuard if_wg(4)
+
+Category: core
+Module: if_wg
+Announced: 2024-03-28
+Affects: All supported versions of FreeBSD.
+Corrected: 2024-03-22 15:21:39 UTC (stable/14, 14.0-STABLE)
+ 2024-03-28 05:06:22 UTC (releng/14.0, 14.0-RELEASE-p6)
+ 2024-03-22 15:21:42 UTC (stable/13, 13.3-STABLE)
+ 2024-03-28 07:14:19 UTC (releng/13.3, 13.3-RELEASE-p1)
+ 2024-03-28 05:07:54 UTC (releng/13.2, 13.2-RELEASE-p11)
+
+For general information regarding FreeBSD Errata Notices and Security
+Advisories, including descriptions of the fields above, security
+branches, and the following sections, please visit
+<URL:https://security.FreeBSD.org/>.
+
+I. Background
+
+if_wg is the kernel module that implements WireGuard tunnels between two
+endpoints. When packets arrive from the tunnel or are sent over the tunnel,
+they are decrypted or encrypted in a separate thread from the one that delivers
+the packet to its final destination.
+
+II. Problem Description
+
+Insufficient barriers between the encrypt/decrypt threads and the delivery
+threads may result in the wrong part of an mbuf chain being read and sent along
+through the network stack on architectures with a weaker memory model, e.g.,
+aarch64, under certain workloads.
+
+III. Impact
+
+The part of the mbuf chain being sent along may contain some invalid state that
+causes a later fault and panic.
+
+IV. Workaround
+
+No workaround is available, but X86 platforms (that is, i386 and amd64) are
+not affected.
+
+V. Solution
+
+Upgrade your system to a supported FreeBSD stable or release / security
+branch (releng) dated after the correction date and reboot or reload the
+if_wg kernel module.
+
+Perform one of the following:
+
+1) To update your system via a binary patch:
+
+Systems running a RELEASE version of FreeBSD arm64 platform can be updated
+via the freebsd-update(8) utility:
+
+# freebsd-update fetch
+# freebsd-update install
+
+After the updates have installed, you will need to reboot the system or reload
+the if_wg kernel module.
+
+2) To update your system via a source code patch:
+
+The following patches have been verified to apply to the applicable
+FreeBSD release branches.
+
+a) Download the relevant patch from the location below, and verify the
+detached PGP signature using your PGP utility.
+
+# fetch https://security.FreeBSD.org/patches/EN-24:06/wireguard.patch
+# fetch https://security.FreeBSD.org/patches/EN-24:06/wireguard.patch.asc
+# gpg --verify wireguard.patch.asc
+
+b) Apply the patch. Execute the following commands as root:
+
+# cd /usr/src
+# patch < /path/to/patch
+
+c) Recompile your kernel as described in
+<URL:https://www.FreeBSD.org/handbook/kernelconfig.html> and reboot the
+system or reload the if_wg kernel module.
+
+VI. Correction details
+
+This issue is corrected as of the corresponding Git commit hash or Subversion
+revision number in the following stable and release branches:
+
+Branch/path Hash Revision
+- -------------------------------------------------------------------------
+stable/14/ 590e02d3c088 stable/14-2576116
+releng/14.0/ 56be7cd84447 releng/14.0-n265412
+stable/13/ 806e51f81dba stable/13-n257611
+releng/13.3/ f07351f90aa3 releng/13.3-n257429
+releng/13.2/ 8f1f4e60ceb9 releng/13.2-n254663
+- -------------------------------------------------------------------------
+
+Run the following command to see which files were modified by a
+particular commit:
+
+# git show --stat <commit hash>
+
+Or visit the following URL, replacing NNNNNN with the hash:
+
+<URL:https://cgit.freebsd.org/src/commit/?id=NNNNNN>
+
+To determine the commit count in a working tree (for comparison against
+nNNNNNN in the table above), run:
+
+# git rev-list --count --first-parent HEAD
+
+VII. References
+
+<URL:https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=264115>
+
+The latest revision of this advisory is available at
+<URL:https://security.FreeBSD.org/advisories/FreeBSD-EN-24:06.wireguard.asc>
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAEBCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGagACgkQbljekB8A
+Gu/p2g//cupzJnkQB/sXm0EWroHjy/I6X6gbZlDpHZFbetGx8niyCH/xK3FMySuq
+q1XGKpXqQKBR3R+VmTNs+Tfd0DbFK8nwStPHXnewKZJ+Qddah27Y3zEuj9+vmmmq
+rzgJNDNv53eZj0c2ExIWVSfjn1faiE4ctVUOROtvxvxr9RtFpatGTzT5i/wgoNnj
+gyO/VoFIn3C4ya8F/7EMicnEdQuXW55Ds+3ub9MO4DcXDds3QLWnYIVYfnvnBNV4
+YX7N+yynBxGOwD1Isbee6dCFTslsOgqV8WGkN4hMXvikPGvD+lXwCpDftfJCEFbR
+xDUzf+M/6eBDgTztMmg7bTQO53Dp1iv5nd6Sw71rqS6tCwJ4BoxHV8Cx31yBbPRq
+S2JsUjT0UsH5Cdvq8Ky5vMPSuSa/n8Ma/CeNtAQ0wvMw9WXkDGOZQSfBuEvJIItB
+WQyfpBgrWjUZ3fMX7URPc5hca04y/bLyBV+gRfRqVy2nc4T4AwplWYOvBb5f8EXs
+2+Jq1Bh3PQTBM4ZdXJtGmBct7ciZn3tZSrAt8c2sNLV5tUfVhWgNTYmcj5ffpPGh
+r6D9m++Oq4ZORrFpydDfgv/0qXJQrp/9nFVxv8TdhwHBOkdYWP9mJpIUJxVxwfYp
+jlFBr6yZWp4bWsGGgdtQqQ5+gKo8B25aQ52IE22weZsFxxaYn24=
+=oKHT
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/advisories/FreeBSD-EN-24:07.clang.asc b/website/static/security/advisories/FreeBSD-EN-24:07.clang.asc
new file mode 100644
index 0000000000..eeaceee0b4
--- /dev/null
+++ b/website/static/security/advisories/FreeBSD-EN-24:07.clang.asc
@@ -0,0 +1,127 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+=============================================================================
+FreeBSD-EN-24:07.clang Errata Notice
+ The FreeBSD Project
+
+Topic: Clang crash when certain optimization is enabled
+
+Category: contrib
+Module: clang
+Announced: 2024-03-28
+Affects: FreeBSD 14.0 and FreeBSD 13.3
+Corrected: 2024-03-08 08:19:28 UTC (stable/14, 14.0-STABLE)
+ 2024-03-28 05:06:23 UTC (releng/14.0, 14.0-RELEASE-p6)
+ 2024-03-08 08:19:49 UTC (stable/13, 13.3-STABLE)
+ 2024-03-28 07:14:20 UTC (releng/13.3, 13.3-RELEASE-p1)
+
+For general information regarding FreeBSD Errata Notices and Security
+Advisories, including descriptions of the fields above, security
+branches, and the following sections, please visit
+<URL:https://security.FreeBSD.org/>.
+
+I. Background
+
+FreeBSD includes the Clang C/C++ compiler in the base system. FreeBSD 14.0
+and FreeBSD 13.3 include Clang version 17.
+
+II. Problem Description
+
+Clang 17 has a bug that results in a crash under certain circumstances.
+
+III. Impact
+
+The compiler crashes instead of generating an object file.
+
+IV. Workaround
+
+Avoid use of -fzero-call-used-regs, or install a version of Clang other than
+17 from ports or packages.
+
+V. Solution
+
+Upgrade your system to a supported FreeBSD stable or release / security
+branch (releng) dated after the correction date.
+
+Perform one of the following:
+
+1) To update your system via a binary patch:
+
+Systems running a RELEASE version of FreeBSD on the amd64 or arm64 platforms,
+or the i386 platform on FreeBSD 13, can be updated via the freebsd-update(8)
+utility:
+
+# freebsd-update fetch
+# freebsd-update install
+
+2) To update your system via a source code patch:
+
+The following patches have been verified to apply to the applicable
+FreeBSD release branches.
+
+a) Download the relevant patch from the location below, and verify the
+detached PGP signature using your PGP utility.
+
+# fetch https://security.FreeBSD.org/patches/EN-24:07/clang.patch
+# fetch https://security.FreeBSD.org/patches/EN-24:07/clang.patch.asc
+# gpg --verify clang.patch.asc
+
+b) Apply the patch. Execute the following commands as root:
+
+# cd /usr/src
+# patch < /path/to/patch
+
+c) Recompile the operating system using buildworld and installworld as
+described in <URL:https://www.FreeBSD.org/handbook/makeworld.html>.
+
+VI. Correction details
+
+This issue is corrected as of the corresponding Git commit hash in the
+following stable and release branches:
+
+Branch/path Hash Revision
+- -------------------------------------------------------------------------
+stable/14/ fc31d474c40a stable/14-n266942
+releng/14.0/ 711422d54795 releng/14.0-n265413
+stable/13/ 961271f952fc stable/13-n257558
+releng/13.3/ 26059a4f2c14 releng/13.3-n257430
+- -------------------------------------------------------------------------
+
+Run the following command to see which files were modified by a
+particular commit:
+
+# git show --stat <commit hash>
+
+Or visit the following URL, replacing NNNNNN with the hash:
+
+<URL:https://cgit.freebsd.org/src/commit/?id=NNNNNN>
+
+To determine the commit count in a working tree (for comparison against
+nNNNNNN in the table above), run:
+
+# git rev-list --count --first-parent HEAD
+
+VII. References
+
+<URL:https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=277474>
+<URL:https://github.com/llvm/llvm-project/issues/75168>
+
+The latest revision of this advisory is available at
+<URL:https://security.FreeBSD.org/advisories/FreeBSD-EN-24:07.clang.asc>
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAEBCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGaoACgkQbljekB8A
+Gu/y4RAAqXAE1WeZIk1tYMnlgqcw1SM5ojKvzK2iZegpPND0Yov7gzkwmNYNqCGY
+GLEKVJcVqS5hagCowAZkptq0dh8JtHusBVWq53IZdI5RB81xQOa2yYp+87GkVacn
+j8UnnbmAbb0rfMQyzVbMc5Kv3fkeAkZYZxiKmm+2iKt1cFHXv8yU4DIsTkxLAOUM
+AlextCl+SO6NLyZ6+64XkArc9ekcrrTs4QpKhZwHcBWNOogDzvFxCokObVGM98cb
+AN9pS09BTquuN5Yq5kXgFVzp8KLM0uruFKuEy+yNTCFJMMix1/9hj84yA2STm1iu
+AGd0lp8N7JXfnGKdktBZ4YeOL7GRTTgrInixJ3KbzjFbwmwrgQSzBC1neZqjPbAf
+iomKNIo23wsaMpjDh+RBBIOpDZnfPOO+imWh6A4ErdObMWyNw3+2MqUSHgMI9STO
+qqWIAHvQQwlB0lZAYvh6/iHntfLfIa3vdUH+g7kl8d5xzZlV18HkqsF6LtzbXbE5
+tJ6QxtqlZjLa7eq/7qyg5bQFk7eJ0bhN7al+P5FOjezJo/tCFOIStWaFgTWntNep
+FkysAdgJUnkMreaccWT3YrIKKKyjBUVYvh1UWf6GudSdPs9ZPzsAR3X1RmixGO6H
+Y5EjL5hvuaNdqM3RiCF2/Vm/sVwF8KkEJs1rDbFFhM1HKCt9000=
+=lTOH
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/advisories/FreeBSD-EN-24:08.kerberos.asc b/website/static/security/advisories/FreeBSD-EN-24:08.kerberos.asc
new file mode 100644
index 0000000000..32ac450b39
--- /dev/null
+++ b/website/static/security/advisories/FreeBSD-EN-24:08.kerberos.asc
@@ -0,0 +1,127 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+=============================================================================
+FreeBSD-EN-24:08.kerberos Errata Notice
+ The FreeBSD Project
+
+Topic: Kerberos segfaults when using weak crypto
+
+Category: contrib
+Module: heimdal
+Announced: 2024-03-28
+Affects: FreeBSD 14.0
+Corrected: 2024-01-22 15:49:24 UTC (stable/14, 14.0-STABLE)
+ 2024-03-28 05:06:25 UTC (releng/14.0, 14.0-RELEASE-p6)
+
+For general information regarding FreeBSD Errata Notices and Security
+Advisories, including descriptions of the fields above, security
+branches, and the following sections, please visit
+<URL:https://security.FreeBSD.org/>.
+
+I. Background
+
+FreeBSD includes Heimdal, an implementation of ASN.1/DER, PKIX, and Kerberos.
+It uses OpenSSL to provide a number of cryptographic routines.
+
+II. Problem Description
+
+Weak crypto is provided by the openssl "legacy" provider which is not loaded
+by default.
+
+III. Impact
+
+Attempting to use weak crypto routines when the legacy provider is not loaded
+results in the application crashing.
+
+IV. Workaround
+
+Edit /etc/ssl/openssl.cnf to load the legacy provider unconditionally.
+
+V. Solution
+
+Upgrade your system to a supported FreeBSD stable or release / security
+branch (releng) dated after the correction date.
+
+Perform one of the following:
+
+1) To update your system via a binary patch:
+
+Systems running a RELEASE version of FreeBSD on the amd64 or arm64 platforms,
+or the i386 platform on FreeBSD 13, can be updated via the freebsd-update(8)
+utility:
+
+# freebsd-update fetch
+# freebsd-update install
+
+2) To update your system via a source code patch:
+
+The following patches have been verified to apply to the applicable
+FreeBSD release branches.
+
+a) Download the relevant patch from the location below, and verify the
+detached PGP signature using your PGP utility.
+
+# fetch https://security.FreeBSD.org/patches/EN-24:08/kerberos.patch
+# fetch https://security.FreeBSD.org/patches/EN-24:08/kerberos.patch.asc
+# gpg --verify kerberos.patch.asc
+
+b) Apply the patch. Execute the following commands as root:
+
+# cd /usr/src
+# patch < /path/to/patch
+
+c) Recompile the operating system using buildworld and installworld as
+described in <URL:https://www.FreeBSD.org/handbook/makeworld.html>.
+
+Restart all daemons that use the library, or reboot the system.
+
+VI. Correction details
+
+This issue is corrected as of the corresponding Git commit hash in the
+following stable and release branches:
+
+Branch/path Hash Revision
+- -------------------------------------------------------------------------
+stable/14/ c7db2e15e404 stable/14-n266467
+releng/14.0/ c48fe39ad139 releng/14.0-n265415
+- -------------------------------------------------------------------------
+
+Run the following command to see which files were modified by a
+particular commit:
+
+# git show --stat <commit hash>
+
+Or visit the following URL, replacing NNNNNN with the hash:
+
+<URL:https://cgit.freebsd.org/src/commit/?id=NNNNNN>
+
+To determine the commit count in a working tree (for comparison against
+nNNNNNN in the table above), run:
+
+# git rev-list --count --first-parent HEAD
+
+VII. References
+
+<other info on the problem>
+
+<URL:https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=272835>
+
+The latest revision of this advisory is available at
+<URL:https://security.FreeBSD.org/advisories/FreeBSD-EN-24:08.kerberos.asc>
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAEBCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGawACgkQbljekB8A
+Gu9Euw/+LX8qcrGUvA11MNOVemD+SEH/Ol97L4gLHhzGlWSf3VMq5F1KtY0VRwGK
+ykM3VsSAk3PoYHLn+jbHPuAMjJVym+MLg27ZZWlqnx2Z7/wk2KuAb9RVCUl4FnPy
+eTXzBNt3tCSYa2ZCRWEH+uN6dZh4o8VP0DWfrNdaazH7R7ezRmTzirvcQ39MXTcE
+8wI+zQedVZG4OSuqOSFY21d70nlzqgs6ThY3K6KrtcaQGfenYBSQgFmjMJlBqtrb
+Mr1Yvgc+wE66Ara/Hz+/2L11bwjyFwT1dpO57DKrcyTaGTnSYiDQiDscUIAW0gCh
+bUMCgWCHq+kk7pAyUIMlRbdrA/6N/wmvwP/iO6GGxYmN0lNX8udxeZWz3OPPnbif
+anM5OGnvKFkkTzCqnpHumljolvJL0/VeD7XCNBBgWa1I46gFmmNZ7R2esm7UEdU8
+IR4Hk9EqGhfl+EwU7OW04/Hq3br667kXbVsq1TTVM4ht39K+WhVoxzirp7QzOGTJ
+WjRq6DK+44PyhQgnnAJgM/4gOGr5O/Y3ezRx4uj1S9L9faXTC5xlT8Vw78xU2wXq
+BjG7vXi5r9d4POjtRcNiaMVKXQPF/saGjHcPGrGnuBLC8AFG54bFycmvM5QzWqng
+AeRFOg+O8lkxLoQMDqJsNt8OMIk7vZHguwL7pt0tRtouuoaszU0=
+=UnED
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/advisories/FreeBSD-SA-24:03.unbound.asc b/website/static/security/advisories/FreeBSD-SA-24:03.unbound.asc
new file mode 100644
index 0000000000..6873ea8d0d
--- /dev/null
+++ b/website/static/security/advisories/FreeBSD-SA-24:03.unbound.asc
@@ -0,0 +1,147 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+=============================================================================
+FreeBSD-SA-24:03.unbound Security Advisory
+ The FreeBSD Project
+
+Topic: Multiple vulnerabilities in unbound
+
+Category: contrib
+Module: unbound
+Announced: 2024-03-28
+Affects: FreeBSD 13.2 and FreeBSD 14.0
+Corrected: 2024-02-17 13:45:44 UTC (stable/14, 14.0-STABLE)
+ 2024-03-28 05:06:26 UTC (releng/14.0, 14.0-RELEASE-p6)
+ 2024-02-17 13:45:44 UTC (stable/13, 13.2-STABLE)
+ 2024-03-28 05:07:55 UTC (releng/13.2, 13.2-RELEASE-p11)
+CVE Name: CVE-2023-50387, CVE-2023-50868
+
+For general information regarding FreeBSD Security Advisories,
+including descriptions of the fields above, security branches, and the
+following sections, please visit <URL:https://security.FreeBSD.org/>.
+
+I. Background
+
+Unbound is a validating, recursive, and caching DNS resolver.
+
+II. Problem Description
+
+The KeyTrap vulnerability (CVE-2023-50387) works by using a combination of Keys
+(also colliding Keys), Signatures and number of RRSETs on a malicious zone.
+Answers from that zone can force a DNSSEC validator down a very CPU intensive
+and time costly validation path.
+
+The NSEC3 vulnerability (CVE-2023-50868) uses specially crafted responses on a
+malicious zone with multiple NSEC3 RRSETs to force a DNSSEC validator down a
+very CPU intensive and time costly NSEC3 hash calculation path.
+
+
+III. Impact
+
+Both issues can force Unbound to spend an enormous time (comparative to regular
+traffic) validating a single specially crafted DNSSEC response while everything
+else is on hold for that thread. A trivially orchestrated attack could render
+all threads busy with such responses leading to denial of service.
+
+IV. Workaround
+
+No workaround is available. Systems not running Unbound are not affected.
+
+V. Solution
+
+Upgrade your vulnerable system to a supported FreeBSD stable or
+release / security branch (releng) dated after the correction date.
+
+Perform one of the following:
+
+1) To update your vulnerable system via a binary patch:
+
+Systems running a RELEASE version of FreeBSD on the amd64 or arm64 platforms,
+or the i386 platform on FreeBSD 13, can be updated via the freebsd-update(8)
+utility:
+
+# freebsd-update fetch
+# freebsd-update install
+
+2) To update your vulnerable system via a source code patch:
+
+The following patches have been verified to apply to the applicable
+FreeBSD release branches.
+
+a) Download the relevant patch from the location below, and verify the
+detached PGP signature using your PGP utility.
+
+[FreeBSD 14.0]
+# fetch https://security.FreeBSD.org/patches/SA-24:03/unbound-14.patch
+# fetch https://security.FreeBSD.org/patches/SA-24:03/unbound-14.patch.asc
+# gpg --verify unbound-14.patch.asc
+
+[FreeBSD 13.2]
+# fetch https://security.FreeBSD.org/patches/SA-24:03/unbound-13.patch
+# fetch https://security.FreeBSD.org/patches/SA-24:03/unbound-13.patch.asc
+# gpg --verify unbound-13.patch.asc
+
+b) Apply the patch. Execute the following commands as root:
+
+# cd /usr/src
+# patch -p0 < /path/to/patch
+
+c) Recompile the operating system using buildworld and installworld as
+described in <URL:https://www.FreeBSD.org/handbook/makeworld.html>.
+
+Restart the applicable daemons, or reboot the system.
+
+VI. Correction details
+
+This issue is corrected as of the corresponding Git commit hash in the
+following stable and release branches:
+
+Branch/path Hash Revision
+- -------------------------------------------------------------------------
+stable/14/ e2b44c401cc2 stable/14-n266696
+releng/14.0/ c189b94f8a22 releng/14.0-n265416
+stable/13/ abe4ced2b9de stable/13-n257436
+releng/13.2/ d9d90e5e42f6 releng/13.2-n254664
+- -------------------------------------------------------------------------
+
+Run the following command to see which files were modified by a
+particular commit:
+
+# git show --stat <commit hash>
+
+Or visit the following URL, replacing NNNNNN with the hash:
+
+<URL:https://cgit.freebsd.org/src/commit/?id=NNNNNN>
+
+To determine the commit count in a working tree (for comparison against
+nNNNNNN in the table above), run:
+
+# git rev-list --count --first-parent HEAD
+
+VII. References
+
+<URL:https://www.nlnetlabs.nl/news/2024/Feb/13/unbound-1.19.1-released/>
+
+<URL:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-50387>
+
+<URL:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-50868>
+
+The latest revision of this advisory is available at
+<URL:https://security.FreeBSD.org/advisories/FreeBSD-SA-24:03.unbound.asc>
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAEBCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGa4ACgkQbljekB8A
+Gu8Oxw/9HrzGZVx0FsUb8dhvf6Hlcfy3B0RNjxcnvvBm+P/V0+WSEaFTod9YaonO
+GN331SXI1blvqfCpOz2TLiOvHjWDPCcb8bb9YqQXRId4axnpxCCzIY0HkxgXFNDu
+XgXwM4JYapmWis/pOxifRXnB087lwbkfVx/0iOTeA0XUFoRRIbooiL/6H76hOmq7
+XR5moI8xYyAX5Xh+5/6yZgd+A+0n/KfQnOEpA7Ex9MWC17co+RGOP1JUZYIFHhAc
+W/vNuL23UWqR1TjMgVWTHEvVBTrUPEiDfp2Z1LiQexH9IaQ4cePu7qrWlzAo7rr6
+6Cf3DybH9IxALQQSSKq1JWNqQFOWvpXCy5JKBua+Z7kcFHR5tmAgolqGLGJ629Ko
+GNwsSUTZ8SzwupJ93boMaD4jF2t+zOXvBvceYywZEEvd2gq2zkfMV6WJwtUUOvdm
+z7Z7AejUFONrQyYps4rcKCthnQOLHtzcPUQom68KpUACsdOr1hkA0VOCf5HRrEe6
+DpwM9PX1T3eiHSq1eZj2MMkz+Cw/DJK+wegkULRxg2ZOmWKA2U8df+Qj1RYpX4QT
+JrPSHh4EqovfrB5H0uUgfLWBgAzGBLEeFKAMA+omlEaELyNzvG/4xv8eJVtjTG+D
+EEQCXVTJmws/ZFDC2vJhVR6vdAwMuPz8YkBtcQkqnNcF+zzbcEk=
+=PELN
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/patches/EN-24:05/tty.patch b/website/static/security/patches/EN-24:05/tty.patch
new file mode 100644
index 0000000000..8499da5c00
--- /dev/null
+++ b/website/static/security/patches/EN-24:05/tty.patch
@@ -0,0 +1,23 @@
+--- sys/kern/tty.c.orig
++++ sys/kern/tty.c
+@@ -1312,7 +1312,8 @@
+ struct xtty *xtlist, *xt;
+ struct tty *tp;
+ struct proc *p;
+- int cansee, error;
++ int error;
++ bool cansee;
+
+ sx_slock(&tty_list_sx);
+ lsize = tty_list_count * sizeof(struct xtty);
+@@ -1325,8 +1326,8 @@
+
+ TAILQ_FOREACH(tp, &tty_list, t_list) {
+ tty_lock(tp);
+- if (tp->t_session != NULL) {
+- p = tp->t_session->s_leader;
++ if (tp->t_session != NULL &&
++ (p = atomic_load_ptr(&tp->t_session->s_leader)) != NULL) {
+ PROC_LOCK(p);
+ cansee = (p_cansee(td, p) == 0);
+ PROC_UNLOCK(p);
diff --git a/website/static/security/patches/EN-24:05/tty.patch.asc b/website/static/security/patches/EN-24:05/tty.patch.asc
new file mode 100644
index 0000000000..02b14fbbcb
--- /dev/null
+++ b/website/static/security/patches/EN-24:05/tty.patch.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGacACgkQbljekB8A
+Gu/pQxAA4CSM2nWX56Ajxbg37GqmL3I72ePFiC5qZa5/JbjqowQP82PfWOd4chRC
+0qy3RFy9WN2+fS8md7blvLqqj1OiM9S6FrfQLJEaBlovZ4w4VH763Za3mr1zrrvc
+LF4Jz6KAwOhv6Zm8ROaywjHhOsNHci81pzcdTEQoKJnML85IiGdelaMwdbgN02fo
+f7muMsHsLV+BMQXAx//2UZqWZ/d11LmqlSjYWy3JYdasy+mA4Arwy3Qoyq1WyQzo
+qeBQO1V78VyG5+5trbe7YvjhsOqQUbf3ctACvVtQ4XIufXnPskPNFb/0bmDOYwkG
+e+7GMKNvnzIwa7bpFCM6B/8iqN6Mye7Nn0jiCjThH481NsbjdSnab6YbnmSYtgo9
+2Fn2u7hWDCagZgwCkEzLPUWzDR8yoiibFhsMrsnxrZZMzNT+AWP1HMo5JlArWeQf
+TveQwUfpMYdHGHPcrKCACv3w1RVEVzAKfOjDy/NgtYKSpOt4Wi7FGTzWvCkHPA2M
+CXYLi/0hWWbEJ7ZhybZilOvb+wy/8iK7mcoqYBmLGYM9Ne8quGtHfLS7FbuRaiXL
+ZXD1if338wxZj+iekZQxdqG+MB0TrNbGn50jekiv+laQ1mQBhjYcpgd3E6/uYVvZ
+TXsWzDPBjKXxKtIuw0UYtimQYg4oBwzNPVtKIZSRcSClDuv138Q=
+=ZBRs
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/patches/EN-24:06/wireguard.patch b/website/static/security/patches/EN-24:06/wireguard.patch
new file mode 100644
index 0000000000..c736ef8ee0
--- /dev/null
+++ b/website/static/security/patches/EN-24:06/wireguard.patch
@@ -0,0 +1,40 @@
+--- sys/dev/wg/if_wg.c.orig
++++ sys/dev/wg/if_wg.c
+@@ -1515,8 +1515,7 @@
+ state = WG_PACKET_CRYPTED;
+ out:
+ pkt->p_mbuf = m;
+- wmb();
+- pkt->p_state = state;
++ atomic_store_rel_int(&pkt->p_state, state);
+ GROUPTASK_ENQUEUE(&peer->p_send);
+ noise_remote_put(remote);
+ }
+@@ -1588,8 +1587,7 @@
+ state = WG_PACKET_CRYPTED;
+ out:
+ pkt->p_mbuf = m;
+- wmb();
+- pkt->p_state = state;
++ atomic_store_rel_int(&pkt->p_state, state);
+ GROUPTASK_ENQUEUE(&peer->p_recv);
+ noise_remote_put(remote);
+ }
+@@ -1645,7 +1643,7 @@
+ wg_peer_get_endpoint(peer, &endpoint);
+
+ while ((pkt = wg_queue_dequeue_serial(&peer->p_encrypt_serial)) != NULL) {
+- if (pkt->p_state != WG_PACKET_CRYPTED)
++ if (atomic_load_acq_int(&pkt->p_state) != WG_PACKET_CRYPTED)
+ goto error;
+
+ m = pkt->p_mbuf;
+@@ -1687,7 +1685,7 @@
+ struct epoch_tracker et;
+
+ while ((pkt = wg_queue_dequeue_serial(&peer->p_decrypt_serial)) != NULL) {
+- if (pkt->p_state != WG_PACKET_CRYPTED)
++ if (atomic_load_acq_int(&pkt->p_state) != WG_PACKET_CRYPTED)
+ goto error;
+
+ m = pkt->p_mbuf;
diff --git a/website/static/security/patches/EN-24:06/wireguard.patch.asc b/website/static/security/patches/EN-24:06/wireguard.patch.asc
new file mode 100644
index 0000000000..8d89aa382d
--- /dev/null
+++ b/website/static/security/patches/EN-24:06/wireguard.patch.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGakACgkQbljekB8A
+Gu8yxQ//RsSn/2skiogUdzdv2r/2Ax4jYBMCDOI5QyBorhXEr188pU5iqG070NU3
+WnjD5YjjgIlfmZfHhN+ONwaoG7RuDou6lmCKydzXg1NJawUsUoa3Wwjn5JBwg9LB
+5VzSGa3zoQkcKT/mbyTuqUgtsFjRJc130UtnUtOt9HNRTiMMExnTE3XFgrutFv4t
+Rlhmw4puKPvNucsjjQWU94ra2eOEQVKqyBEOLO5hABIfz0G4LpLplkeEnW1O43RF
+uNCOoFU2vSOovGQX1DpdhNDrQPYX+0BjKZD4SGb+RnXSH3GekxhtKVZu2B7Vx82F
+w0kNtxK+6cD91R3JdIRl9Fl/uDI6NoYD4WKzO5dLS2Xon0VpIsQl6R1YNVnYF2nT
+wfvBNaLyB4mmu02/9LXQx4NtKf6Fln7bdB1Aie00/nJx/2X2pTJMA+Juc+stMarQ
+MNJikdx3ilbVW4Jd5iLGA1yH1VrK4lLdgLUs4XoaS8jp7pFefsElbVBH9SzSJIJn
+7EExx16JMynUltN88pe/b79JNjFH33HQBD8ncFh8JXh8wKQCYX+A5fM23QUxwMPO
+o3VL//YLuwyU/v+b9xDurkNAgi0tlq+TOgviNFDJbrxQADgdvZmYCZI8H2XViA+M
+SAwx5zLRJJAao/78A+n0Q8ixjJ89h5bExdozkXZwqUNsOLUe29o=
+=fsNv
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/patches/EN-24:07/clang.patch b/website/static/security/patches/EN-24:07/clang.patch
new file mode 100644
index 0000000000..5ac7e17e7a
--- /dev/null
+++ b/website/static/security/patches/EN-24:07/clang.patch
@@ -0,0 +1,25 @@
+--- contrib/llvm-project/llvm/lib/CodeGen/PrologEpilogInserter.cpp.orig
++++ contrib/llvm-project/llvm/lib/CodeGen/PrologEpilogInserter.cpp
+@@ -1285,6 +1285,8 @@
+ continue;
+
+ MCRegister Reg = MO.getReg();
++ if (!Reg)
++ continue;
+
+ // This picks up sibling registers (e.q. %al -> %ah).
+ for (MCRegUnit Unit : TRI.regunits(Reg))
+@@ -1308,8 +1310,11 @@
+ if (!MO.isReg())
+ continue;
+
+- for (const MCPhysReg &Reg :
+- TRI.sub_and_superregs_inclusive(MO.getReg()))
++ MCRegister Reg = MO.getReg();
++ if (!Reg)
++ continue;
++
++ for (const MCPhysReg Reg : TRI.sub_and_superregs_inclusive(Reg))
+ RegsToZero.reset(Reg);
+ }
+ }
diff --git a/website/static/security/patches/EN-24:07/clang.patch.asc b/website/static/security/patches/EN-24:07/clang.patch.asc
new file mode 100644
index 0000000000..957b801ada
--- /dev/null
+++ b/website/static/security/patches/EN-24:07/clang.patch.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGasACgkQbljekB8A
+Gu9AWw/+LI09U9VZahOf/j6NPfrneFaDy0SWK5GEQeUausZ8bdotxzwIlu0OC8S0
+1hmpPlaXtINY0xNru5sSsENCFnj0JXyB/CFVXL/gLnCYnYx99ae77jBcDzX86VAV
+XcX89hwcqIk9u/a8MHxvIyoRBunqO1617lRxP0ZGZ6CxGLwCk/ys8HFkPavYvfQT
+y/h8sbbKp7VDlKEjVwr8uPy1XnO+e5dRLHsinWXp2DM/JPRLi5slcbO9SmPiGyVB
+F1Pca5ryAsWJYsnqA6O7aDviBDodrR8EigzkEbYGOlh3QLLHKCmfQRI9i6zMTzKK
+G9LvIcodR1w/DQ9S4TwozpXG5zfuvU8vz/BGNtySL5DzZ2zyHHhwC3wWqZZRFVAY
+2KggMsXnCo7pVSQWVofQ9zL+w3lUEiJSCiYvbHW9gFqMnv0891zTIbHTfu5ktYeV
++p5vQv6qxhkp9PC/LYGOxHTYRAeZT4BBWG92dqrEivZTPd4LNzLmOn0WlZBOjlfl
+Ztdes1BhxmY4+wBgxivvVWoNb15jQjUU9+HBGgF6GhiZRuXw84KzzAjP12FkN0lH
+5IH4qbhsMHzD2256rMKGPpvS35DzKoMkWBYgVup/ZnScw0luTEOybHiTswzg3T/c
+6Rgj545wxkyRYYw2zIHTmr2pMPQnTUXWETn51WTqk3UsVREFhiY=
+=GKxn
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/patches/EN-24:08/kerberos.patch b/website/static/security/patches/EN-24:08/kerberos.patch
new file mode 100644
index 0000000000..adf73729d8
--- /dev/null
+++ b/website/static/security/patches/EN-24:08/kerberos.patch
@@ -0,0 +1,203 @@
+--- crypto/heimdal/lib/kadm5/create_s.c.orig
++++ crypto/heimdal/lib/kadm5/create_s.c
+@@ -169,6 +169,10 @@
+ ent.entry.keys.len = 0;
+ ent.entry.keys.val = NULL;
+
++ ret = fbsd_ossl_provider_load();
++ if (ret)
++ goto out;
++
+ ret = _kadm5_set_keys(context, &ent.entry, password);
+ if (ret)
+ goto out;
+--- crypto/heimdal/lib/kadm5/kadm5_locl.h.orig
++++ crypto/heimdal/lib/kadm5/kadm5_locl.h
+@@ -79,5 +79,6 @@
+ #include <der.h>
+ #include <parse_units.h>
+ #include "private.h"
++#include "fbsd_ossl_provider.h"
+
+ #endif /* __KADM5_LOCL_H__ */
+--- crypto/heimdal/lib/krb5/context.c.orig
++++ crypto/heimdal/lib/krb5/context.c
+@@ -392,6 +392,10 @@
+ }
+ HEIMDAL_MUTEX_init(p->mutex);
+
++ ret = fbsd_ossl_provider_load();
++ if(ret)
++ goto out;
++
+ p->flags |= KRB5_CTX_F_HOMEDIR_ACCESS;
+
+ ret = krb5_get_default_config_files(&files);
+--- crypto/heimdal/lib/krb5/crypto.c.orig
++++ crypto/heimdal/lib/krb5/crypto.c
+@@ -2054,6 +2054,9 @@
+ *crypto = NULL;
+ return ret;
+ }
++ ret = fbsd_ossl_provider_load();
++ if (ret)
++ return ret;
+ (*crypto)->key.schedule = NULL;
+ (*crypto)->num_key_usage = 0;
+ (*crypto)->key_usage = NULL;
+--- crypto/heimdal/lib/krb5/salt.c.orig
++++ crypto/heimdal/lib/krb5/salt.c
+@@ -43,6 +43,8 @@
+ struct _krb5_encryption_type *e;
+ struct salt_type *st;
+
++ (void) fbsd_ossl_provider_load();
++
+ e = _krb5_find_enctype (etype);
+ if (e == NULL) {
+ krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
+@@ -75,6 +77,8 @@
+ struct _krb5_encryption_type *e;
+ struct salt_type *st;
+
++ (void) fbsd_ossl_provider_load();
++
+ e = _krb5_find_enctype (etype);
+ if (e == NULL) {
+ krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
+@@ -196,6 +200,7 @@
+ enctype);
+ return KRB5_PROG_ETYPE_NOSUPP;
+ }
++ (void) fbsd_ossl_provider_load();
+ for(st = et->keytype->string_to_key; st && st->type; st++)
+ if(st->type == salt.salttype)
+ return (*st->string_to_key)(context, enctype, password,
+--- crypto/heimdal/lib/roken/version-script.map.orig
++++ crypto/heimdal/lib/roken/version-script.map
+@@ -13,6 +13,7 @@
+ ct_memcmp;
+ err;
+ errx;
++ fbsd_ossl_provider_load;
+ free_getarg_strings;
+ get_default_username;
+ get_window_size;
+--- kerberos5/include/crypto-headers.h.orig
++++ kerberos5/include/crypto-headers.h
+@@ -17,5 +17,9 @@
+ #include <openssl/ec.h>
+ #include <openssl/ecdsa.h>
+ #include <openssl/ecdh.h>
++#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
++#include <openssl/provider.h>
++#include "fbsd_ossl_provider.h"
++#endif
+
+ #endif /* __crypto_headers_h__ */
+--- /dev/null
++++ kerberos5/include/fbsd_ossl_provider.h
+@@ -0,0 +1,4 @@
++#ifndef __fbsd_ossl_provider_h
++#define __fbsd_ossl_provider_h
++int fbsd_ossl_provider_load(void);
++#endif
+--- kerberos5/lib/libroken/Makefile.orig
++++ kerberos5/lib/libroken/Makefile
+@@ -74,9 +74,13 @@
+ vis.c \
+ warnerr.c \
+ write_pid.c \
+- xfree.c
++ xfree.c \
++ fbsd_ossl_provider_load.c
+
+-CFLAGS+=-I${KRB5DIR}/lib/roken -I.
++CFLAGS+=-I${KRB5DIR}/lib/roken \
++ -I${SRCTOP}/kerberos5/include \
++ -I${KRB5DIR}/lib/krb5 \
++ -I${SRCTOP}/crypto/openssl/include -I.
+
+ CLEANFILES= roken.h
+
+--- /dev/null
++++ kerberos5/lib/libroken/fbsd_ossl_provider_load.c
+@@ -0,0 +1,78 @@
++#include <dlfcn.h>
++#include <errno.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <openssl/provider.h>
++
++#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
++#define CRYPTO_LIBRARY "/lib/libcrypto.so.30"
++static void fbsd_ossl_provider_unload(void);
++static void print_dlerror(char *);
++static OSSL_PROVIDER *legacy;
++static OSSL_PROVIDER *deflt;
++static int providers_loaded = 0;
++static OSSL_PROVIDER * (*ossl_provider_load)(OSSL_LIB_CTX *, const char*) = NULL;
++static int (*ossl_provider_unload)(OSSL_PROVIDER *) = NULL;
++static void *crypto_lib_handle = NULL;
++
++static void
++fbsd_ossl_provider_unload(void)
++{
++ if (ossl_provider_unload == NULL) {
++ if (!(ossl_provider_unload = (int (*)(OSSL_PROVIDER*)) dlsym(crypto_lib_handle, "OSSL_PROVIDER_unload"))) {
++ print_dlerror("Unable to link OSSL_PROVIDER_unload");
++ return;
++ }
++ }
++ if (providers_loaded == 1) {
++ (*ossl_provider_unload)(legacy);
++ (*ossl_provider_unload)(deflt);
++ providers_loaded = 0;
++ }
++}
++
++static void
++print_dlerror(char *message)
++{
++ char *errstr;
++
++ if ((errstr = dlerror()) != NULL)
++ fprintf(stderr, "%s: %s\n",
++ message, errstr);
++}
++#endif
++
++int
++fbsd_ossl_provider_load(void)
++{
++#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
++ if (crypto_lib_handle == NULL) {
++ if (!(crypto_lib_handle = dlopen(CRYPTO_LIBRARY,
++ RTLD_LAZY|RTLD_GLOBAL))) {
++ print_dlerror("Unable to load libcrypto.so");
++ return (EINVAL);
++ }
++ }
++ if (ossl_provider_load == NULL) {
++ if (!(ossl_provider_load = (OSSL_PROVIDER * (*)(OSSL_LIB_CTX*, const char *)) dlsym(crypto_lib_handle, "OSSL_PROVIDER_load"))) {
++ print_dlerror("Unable to link OSSL_PROVIDER_load");
++ return(ENOENT);
++ }
++ }
++
++ if (providers_loaded == 0) {
++ if ((legacy = (*ossl_provider_load)(NULL, "legacy")) == NULL)
++ return (EINVAL);
++ if ((deflt = (*ossl_provider_load)(NULL, "default")) == NULL) {
++ (*ossl_provider_unload)(legacy);
++ return (EINVAL);
++ }
++ if (atexit(fbsd_ossl_provider_unload)) {
++ fbsd_ossl_provider_unload();
++ return (errno);
++ }
++ providers_loaded = 1;
++ }
++#endif
++ return (0);
++}
diff --git a/website/static/security/patches/EN-24:08/kerberos.patch.asc b/website/static/security/patches/EN-24:08/kerberos.patch.asc
new file mode 100644
index 0000000000..954dbc0905
--- /dev/null
+++ b/website/static/security/patches/EN-24:08/kerberos.patch.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGa0ACgkQbljekB8A
+Gu8EnhAArg8V5CFlUUyBx3icRwt3S6dUvySU8UI/IMkYrk7oyaZadWg6T6sHiMmr
+Zvwvxvkfp/OukFnzP80SKlKN0OFu0graNFE/UJpfVkcSlFB9mJw6Nl3Y6rXzpqdQ
+kuMPwpDqgzmy5dNFbL0xiGLPN0wKY3vrLjIEufyc2DCemQSxz/Kplg+WQOUnVodj
+Icp7KjUQzxmtbgoP+2U6mGsleGkPNlnSoaQLctrKocFv8zd5Xpz0Km+TLXR5C/yc
+z7kb7N5FEMKNM/CuuyNyOV3f6mNIBi+km3LRgBiKrEgIMY9/Oqk06YNR46+fKagU
+6i0K2MPJJ1zW3oNxourT8M54Ss2D+Y71+TT0TCsJxwRMITr66+cm4zQ5w8FGJq1a
+FCBASeTs/cdL5HqDEGsYQW2gyYm3MLyeiSBHlofj/pI0Y8sffS71BNXbGJgf9Dtc
+vdge1n2d+MUZAKc15NgQ+XFgXR/3VqTkOIdLIMyLUphZ6aY3c/BBLN7m0OUkhhwu
+ro37rEqFgYp2rFgGQ2gYAXg7FnH4llY62OnBw3JVSztH0Ev6nOuyKzCGC8bZKcEL
+CI281T7LRO/iTvGBSO742AQat9zpBDodypWYVLBRexaelPOua7zem/+M10Z6G8Be
+hCB8ZnFT6fuaQcgW3pja5K2HexlybZVKnVMTsaJZ7hs31hOT7g0=
+=JRYJ
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/patches/SA-24:03/unbound-13.patch b/website/static/security/patches/SA-24:03/unbound-13.patch
new file mode 100644
index 0000000000..baa2e0ca48
--- /dev/null
+++ b/website/static/security/patches/SA-24:03/unbound-13.patch
@@ -0,0 +1,24911 @@
+--- contrib/unbound/Makefile.in.orig
++++ contrib/unbound/Makefile.in
+@@ -122,15 +122,15 @@
+ iterator/iter_hints.c iterator/iter_priv.c iterator/iter_resptype.c \
+ iterator/iter_scrub.c iterator/iter_utils.c services/listen_dnsport.c \
+ services/localzone.c services/mesh.c services/modstack.c services/view.c \
+-services/rpz.c \
++services/rpz.c util/rfc_1982.c \
+ services/outbound_list.c services/outside_network.c util/alloc.c \
+ util/config_file.c util/configlexer.c util/configparser.c \
+ util/shm_side/shm_main.c services/authzone.c \
+ util/fptr_wlist.c util/locks.c util/log.c util/mini_event.c util/module.c \
+ util/netevent.c util/net_help.c util/random.c util/rbtree.c util/regional.c \
+-util/rtt.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
++util/rtt.c util/siphash.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
+ util/storage/lruhash.c util/storage/slabhash.c util/tcp_conn_limit.c \
+-util/timehist.c util/tube.c util/proxy_protocol.c \
++util/timehist.c util/tube.c util/proxy_protocol.c util/timeval_func.c \
+ util/ub_event.c util/ub_event_pluggable.c util/winsock_event.c \
+ validator/autotrust.c validator/val_anchor.c validator/validator.c \
+ validator/val_kcache.c validator/val_kentry.c validator/val_neg.c \
+@@ -145,14 +145,14 @@
+ iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \
+ iter_scrub.lo iter_utils.lo localzone.lo mesh.lo modstack.lo view.lo \
+ outbound_list.lo alloc.lo config_file.lo configlexer.lo configparser.lo \
+-fptr_wlist.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
++fptr_wlist.lo siphash.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
+ random.lo rbtree.lo regional.lo rtt.lo dnstree.lo lookup3.lo lruhash.lo \
+ slabhash.lo tcp_conn_limit.lo timehist.lo tube.lo winsock_event.lo \
+-autotrust.lo val_anchor.lo rpz.lo proxy_protocol.lo \
++autotrust.lo val_anchor.lo rpz.lo rfc_1982.lo proxy_protocol.lo \
+ validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \
+ val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo $(CACHEDB_OBJ) authzone.lo \
+ $(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \
+-$(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo
++$(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo timeval_func.lo
+ COMMON_OBJ_WITHOUT_UB_EVENT=$(COMMON_OBJ_WITHOUT_NETCALL) netevent.lo listen_dnsport.lo \
+ outside_network.lo
+ COMMON_OBJ=$(COMMON_OBJ_WITHOUT_UB_EVENT) ub_event.lo
+@@ -198,7 +198,7 @@
+ CHECKCONF_OBJ_LINK=$(CHECKCONF_OBJ) $(COMMON_OBJ_ALL_SYMBOLS) $(SLDNS_OBJ) \
+ $(COMPAT_OBJ) @WIN_CHECKCONF_OBJ_LINK@
+ CONTROL_SRC=smallapp/unbound-control.c
+-CONTROL_OBJ=unbound-control.lo
++CONTROL_OBJ=unbound-control.lo
+ CONTROL_OBJ_LINK=$(CONTROL_OBJ) worker_cb.lo $(COMMON_OBJ_ALL_SYMBOLS) \
+ $(SLDNS_OBJ) $(COMPAT_OBJ) @WIN_CONTROL_OBJ_LINK@
+ HOST_SRC=smallapp/unbound-host.c
+@@ -455,6 +455,7 @@
+ dynlibmod.lo dynlibdmod.o: $(srcdir)/dynlibmod/dynlibmod.c config.h $(srcdir)/dynlibmod/dynlibmod.h
+ cachedb.lo cachedb.o: $(srcdir)/cachedb/cachedb.c config.h $(srcdir)/cachedb/cachedb.h
+ redis.lo redis.o: $(srcdir)/cachedb/redis.c config.h $(srcdir)/cachedb/redis.h
++timeval_func.lo timeval_func.o: $(srcdir)/util/timeval_func.c $(srcdir)/util/timeval_func.h
+
+ # dnscrypt
+ dnscrypt.lo dnscrypt.o: $(srcdir)/dnscrypt/dnscrypt.c config.h \
+@@ -498,6 +499,7 @@
+ echo "#include \"util/configyyrename.h\"" >> $@ ;\
+ $(LEX) -t $(srcdir)/util/configlexer.lex >> $@ ;\
+ fi
++ @if test ! -f $@; then echo "No $@ : need flex and bison to compile from source repository"; exit 1; fi
+
+ util/configparser.c util/configparser.h: $(srcdir)/util/configparser.y
+ @-if test ! -d util; then $(INSTALL) -d util; fi
+@@ -516,7 +518,7 @@
+ rm -f doc/example.conf doc/libunbound.3 doc/unbound-anchor.8 doc/unbound-checkconf.8 doc/unbound-control.8 doc/unbound.8 doc/unbound.conf.5 doc/unbound-host.1
+ rm -f smallapp/unbound-control-setup.sh dnstap/dnstap_config.h dnscrypt/dnscrypt_config.h contrib/libunbound.pc contrib/unbound.socket contrib/unbound.service
+ rm -f $(TEST_BIN)
+- rm -f Makefile
++ rm -f Makefile
+
+ maintainer-clean: distclean
+ rm -f util/configlexer.c util/configparser.c util/configparser.h
+@@ -649,7 +651,7 @@
+
+ iana_update:
+ curl -o port-numbers.tmp https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml --compressed
+- if file port-numbers.tmp | grep 'gzip' >/dev/null; then zcat port-numbers.tmp; else cat port-numbers.tmp; fi | awk '/<record>/ {p=0;} /<protocol>udp/ {p=1;} /<protocol>[^u]/ {p=0;} /Decomissioned|Decommissioned|Removed|De-registered|unassigned|Unassigned|Reserved/ {u=1;} /<number>/ { if(u==1) {u=0;} else { if(p==1) { match($$0,/[0-9]+/); print substr($$0, RSTART, RLENGTH) ","}}}' | sort -nu > util/iana_ports.inc
++ if file port-numbers.tmp | grep 'gzip' >/dev/null; then zcat port-numbers.tmp; else cat port-numbers.tmp; fi | awk '/<record>/ {p=0;} /<protocol>udp/ {p=1;} /<protocol>[^u]/ {p=0;} /Decomissioned|Decommissioned|Removed|De-registered|unassigned|Unassigned|Reserved/ {u=1;} /<number>/ { if(u==1) {u=0;} else { if(p==1) { match($$0,/[0-9]+/); print substr($$0, RSTART, RLENGTH) ","}}}' | sort -nu > util/iana_ports.inc
+ rm -f port-numbers.tmp
+
+ # dependency generation
+@@ -736,7 +738,7 @@
+ msgparse.lo msgparse.o: $(srcdir)/util/data/msgparse.c config.h $(srcdir)/util/data/msgparse.h \
+ $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/sldns/pkthdr.h \
+ $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h \
+- $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lookup3.h $(srcdir)/util/regional.h $(srcdir)/sldns/sbuffer.h \
++ $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lookup3.h $(srcdir)/util/regional.h $(srcdir)/util/net_help.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/sldns/parseutil.h $(srcdir)/sldns/wire2str.h
+ msgreply.lo msgreply.o: $(srcdir)/util/data/msgreply.c config.h $(srcdir)/util/data/msgreply.h \
+ $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h \
+@@ -791,7 +793,7 @@
+ $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/storage/dnstree.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/sbuffer.h
+ iter_resptype.lo iter_resptype.o: $(srcdir)/iterator/iter_resptype.c config.h \
+- $(srcdir)/iterator/iter_resptype.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/util/log.h \
++ $(srcdir)/iterator/iter_resptype.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/iterator/iterator.h $(srcdir)/util/log.h \
+ $(srcdir)/services/cache/dns.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
+ $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/data/dname.h $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/pkthdr.h
+@@ -877,7 +879,7 @@
+ outbound_list.lo outbound_list.o: $(srcdir)/services/outbound_list.c config.h \
+ $(srcdir)/services/outbound_list.h $(srcdir)/services/outside_network.h $(srcdir)/util/rbtree.h \
+ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+-
++
+ outside_network.lo outside_network.o: $(srcdir)/services/outside_network.c config.h \
+ $(srcdir)/services/outside_network.h $(srcdir)/util/rbtree.h $(srcdir)/util/netevent.h \
+ $(srcdir)/dnscrypt/dnscrypt.h \
+@@ -915,7 +917,8 @@
+ configlexer.lo configlexer.o: util/configlexer.c config.h $(srcdir)/util/configyyrename.h \
+ $(srcdir)/util/config_file.h util/configparser.h
+ configparser.lo configparser.o: util/configparser.c config.h $(srcdir)/util/configyyrename.h \
+- $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h
++ $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/sldns/str2wire.h \
++ $(srcdir)/sldns/rrdef.h
+ shm_main.lo shm_main.o: $(srcdir)/util/shm_side/shm_main.c config.h $(srcdir)/util/shm_side/shm_main.h \
+ $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/daemon.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
+ $(srcdir)/util/alloc.h $(srcdir)/services/modstack.h \
+@@ -928,7 +931,7 @@
+ $(srcdir)/services/view.h $(srcdir)/util/config_file.h $(srcdir)/services/authzone.h $(srcdir)/respip/respip.h \
+ $(srcdir)/services/cache/rrset.h $(srcdir)/util/storage/slabhash.h $(srcdir)/services/cache/infra.h \
+ $(srcdir)/util/rtt.h $(srcdir)/validator/validator.h $(srcdir)/validator/val_utils.h $(srcdir)/util/fptr_wlist.h \
+- $(srcdir)/util/tube.h
++ $(srcdir)/util/tube.h $(srcdir)/util/timeval_func.h
+ authzone.lo authzone.o: $(srcdir)/services/authzone.c config.h $(srcdir)/services/authzone.h \
+ $(srcdir)/util/rbtree.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/services/mesh.h $(srcdir)/util/netevent.h \
+ $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/data/msgparse.h \
+@@ -983,7 +986,7 @@
+ $(srcdir)/services/modstack.h $(srcdir)/services/rpz.h $(srcdir)/services/localzone.h $(srcdir)/services/view.h \
+ $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h $(srcdir)/services/authzone.h $(srcdir)/daemon/stats.h \
+ $(srcdir)/util/timehist.h $(srcdir)/libunbound/unbound.h $(srcdir)/respip/respip.h $(srcdir)/sldns/str2wire.h \
+- $(srcdir)/dnstap/dnstap.h $(srcdir)/services/listen_dnsport.h
++ $(srcdir)/dnstap/dnstap.h $(srcdir)/services/listen_dnsport.h $(srcdir)/util/timeval_func.h
+ proxy_protocol.lo proxy_protocol.o: $(srcdir)/util/proxy_protocol.c config.h \
+ $(srcdir)/util/proxy_protocol.h $(srcdir)/sldns/sbuffer.h
+ net_help.lo net_help.o: $(srcdir)/util/net_help.c config.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h \
+@@ -1006,6 +1009,8 @@
+ $(srcdir)/services/outbound_list.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/storage/lruhash.h \
+ $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/module.h \
+ $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h
++siphash.lo siphash.o: $(srcdir)/util/siphash.c
++rfc_1982.lo rfc_1982.o: $(srcdir)/util/rfc_1982.c
+ edns.lo edns.o: $(srcdir)/util/edns.c config.h $(srcdir)/util/edns.h $(srcdir)/util/storage/dnstree.h \
+ $(srcdir)/util/rbtree.h $(srcdir)/util/config_file.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+ $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/util/regional.h \
+@@ -1186,7 +1191,7 @@
+ $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+ $(srcdir)/util/random.h $(srcdir)/respip/respip.h \
+ $(srcdir)/services/localzone.h $(srcdir)/services/view.h $(srcdir)/sldns/sbuffer.h \
+- $(srcdir)/services/outside_network.h
++ $(srcdir)/services/outside_network.h
+ unitmsgparse.lo unitmsgparse.o: $(srcdir)/testcode/unitmsgparse.c config.h $(srcdir)/util/log.h \
+ $(srcdir)/testcode/unitmain.h $(srcdir)/util/data/msgparse.h $(srcdir)/util/storage/lruhash.h \
+ $(srcdir)/util/locks.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h \
+@@ -1321,7 +1326,7 @@
+ worker.lo worker.o: $(srcdir)/daemon/worker.c config.h $(srcdir)/util/log.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/random.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
+- $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
++ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/util/alloc.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \
+ $(srcdir)/sldns/rrdef.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h $(srcdir)/libunbound/unbound.h \
+ $(srcdir)/util/module.h $(srcdir)/dnstap/dnstap.h $(srcdir)/daemon/daemon.h \
+@@ -1343,7 +1348,7 @@
+ $(srcdir)/daemon/remote.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
+ $(srcdir)/util/config_file.h $(srcdir)/sldns/keyraw.h $(srcdir)/daemon/unbound.c $(srcdir)/daemon/daemon.h \
+- $(srcdir)/util/alloc.h $(srcdir)/services/modstack.h \
++ $(srcdir)/util/alloc.h $(srcdir)/util/timeval_func.h $(srcdir)/services/modstack.h \
+ $(srcdir)/util/storage/slabhash.h $(srcdir)/services/listen_dnsport.h $(srcdir)/services/cache/rrset.h \
+ $(srcdir)/services/cache/infra.h $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rtt.h \
+ $(srcdir)/util/data/msgreply.h $(srcdir)/util/fptr_wlist.h $(srcdir)/util/module.h \
+@@ -1357,7 +1362,7 @@
+ worker.lo worker.o: $(srcdir)/daemon/worker.c config.h $(srcdir)/util/log.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/random.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
+- $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
++ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/util/alloc.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \
+ $(srcdir)/sldns/rrdef.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h $(srcdir)/libunbound/unbound.h \
+ $(srcdir)/util/module.h $(srcdir)/dnstap/dnstap.h $(srcdir)/daemon/daemon.h \
+@@ -1409,7 +1414,7 @@
+ $(srcdir)/validator/val_kcache.h $(srcdir)/validator/val_neg.h
+ replay.lo replay.o: $(srcdir)/testcode/replay.c config.h $(srcdir)/util/log.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/config_file.h $(srcdir)/testcode/replay.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+- $(srcdir)/testcode/testpkts.h $(srcdir)/util/rbtree.h \
++ $(srcdir)/testcode/testpkts.h $(srcdir)/util/rbtree.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/testcode/fake_event.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/rrdef.h
+ fake_event.lo fake_event.o: $(srcdir)/testcode/fake_event.c config.h $(srcdir)/testcode/fake_event.h \
+ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+@@ -1417,7 +1422,7 @@
+ $(srcdir)/util/locks.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgencode.h $(srcdir)/util/data/dname.h \
+ $(srcdir)/util/edns.h $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/util/config_file.h \
+- $(srcdir)/services/listen_dnsport.h $(srcdir)/services/outside_network.h \
++ $(srcdir)/services/listen_dnsport.h $(srcdir)/services/outside_network.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h \
+ $(srcdir)/testcode/replay.h $(srcdir)/testcode/testpkts.h $(srcdir)/util/fptr_wlist.h $(srcdir)/util/module.h \
+ $(srcdir)/util/tube.h $(srcdir)/services/mesh.h $(srcdir)/services/modstack.h $(srcdir)/services/rpz.h \
+--- contrib/unbound/README.md.orig
++++ contrib/unbound/README.md
+@@ -1,6 +1,6 @@
+ # Unbound
+
+-[![Travis Build Status](https://travis-ci.org/NLnetLabs/unbound.svg?branch=master)](https://travis-ci.org/NLnetLabs/unbound)
++[![Github Build Status](https://github.com/NLnetLabs/unbound/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/NLnetLabs/unbound/actions)
+ [![Packaging status](https://repology.org/badge/tiny-repos/unbound.svg)](https://repology.org/project/unbound/versions)
+ [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/unbound.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:unbound)
+ [![Documentation Status](https://readthedocs.org/projects/unbound/badge/?version=latest)](https://unbound.readthedocs.io/en/latest/?badge=latest)
+@@ -17,7 +17,9 @@
+ ## Compiling
+
+ Make sure you have the C toolchain, OpenSSL and its include files, and libexpat
+-installed. Unbound can be compiled and installed using:
++installed.
++If building from the repository source you also need flex and bison installed.
++Unbound can be compiled and installed using:
+
+ ```
+ ./configure && make && make install
+@@ -27,7 +29,7 @@
+ outgoing ports. By default max 256 ports are opened at the same time and the
+ builtin alternative is equally capable and a little faster.
+
+-Use the `--with-libevent=dir` configure option to compile Unbound with libevent
++Use the `--with-libevent` configure option to compile Unbound with libevent
+ support.
+
+ ## Unbound configuration
+--- contrib/unbound/acx_nlnetlabs.m4.orig
++++ contrib/unbound/acx_nlnetlabs.m4
+@@ -2,7 +2,9 @@
+ # Copyright 2009, Wouter Wijngaards, NLnet Labs.
+ # BSD licensed.
+ #
+-# Version 44
++# Version 46
++# 2023-05-04 fix to remove unused whitespace.
++# 2023-01-26 fix -Wstrict-prototypes.
+ # 2022-09-01 fix checking if nonblocking sockets work on OpenBSD.
+ # 2021-08-17 fix sed script in ssldir split handling.
+ # 2021-08-17 fix for openssl to detect split version, with ssldir_include
+@@ -187,7 +189,7 @@
+ AC_CACHE_VAL(cv_prog_cc_flag_needed_$cache,
+ [
+ echo '$2' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -233,7 +235,7 @@
+ AC_DEFUN([ACX_DEPFLAG],
+ [
+ AC_MSG_CHECKING([$CC dependency flag])
+-echo 'void f(){}' >conftest.c
++echo 'void f(void){}' >conftest.c
+ if test "`$CC -MM conftest.c 2>&1`" = "conftest.o: conftest.c"; then
+ DEPFLAG="-MM"
+ else
+@@ -272,7 +274,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -309,7 +311,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -335,7 +337,7 @@
+ [
+ #include <stdbool.h>
+ #include <ctype.h>
+-int test() {
++int test(void) {
+ int a = 0;
+ return a;
+ }
+@@ -345,7 +347,7 @@
+ [
+ #include <ctype.h>
+
+-int test() {
++int test(void) {
+ int a;
+ a = isascii(32);
+ return a;
+@@ -356,7 +358,7 @@
+ [
+ #include <netinet/in.h>
+
+-int test() {
++int test(void) {
+ struct in6_pktinfo inf;
+ int a = (int)sizeof(inf);
+ return a;
+@@ -370,7 +372,7 @@
+ [
+ #include <unistd.h>
+
+-int test() {
++int test(void) {
+ int a = setresgid(0,0,0);
+ a = setresuid(0,0,0);
+ return a;
+@@ -385,7 +387,7 @@
+ #endif
+ #include <netdb.h>
+
+-int test() {
++int test(void) {
+ int a = 0;
+ char *t;
+ time_t time = 0;
+@@ -413,7 +415,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -475,7 +477,7 @@
+ dnl Setup ATTR_FORMAT config.h parts.
+ dnl make sure you call ACX_CHECK_FORMAT_ATTRIBUTE also.
+ AC_DEFUN([AHX_CONFIG_FORMAT_ATTRIBUTE],
+-[
++[
+ #ifdef HAVE_ATTR_FORMAT
+ # define ATTR_FORMAT(archetype, string_index, first_to_check) \
+ __attribute__ ((format (archetype, string_index, first_to_check)))
+@@ -834,7 +836,7 @@
+ ACX_CHECK_COMPILER_FLAG_NEEDED(-D_LARGEFILE_SOURCE=1,
+ [
+ #include <stdio.h>
+-int test() {
++int test(void) {
+ int a = fseeko(stdin, 0, 0);
+ return a;
+ }
+@@ -859,7 +861,7 @@
+ #ifdef __cplusplus
+ }
+ #endif
+-int main() {
++int main(void) {
+ ;
+ return 0;
+ }
+@@ -923,7 +925,7 @@
+ AC_CACHE_VAL(cv_cc_deprecated_$cache,
+ [
+ echo '$3' >conftest.c
+-echo 'void f(){ $2 }' >>conftest.c
++echo 'void f(void){ $2 }' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS -c conftest.c 2>&1 | grep -e deprecated -e unavailable`"; then
+ eval "cv_cc_deprecated_$cache=no"
+ else
+@@ -1317,7 +1319,7 @@
+ #ifdef HAVE_WINSOCK2_H
+ #define FD_SET_T (u_int)
+ #else
+-#define FD_SET_T
++#define FD_SET_T
+ #endif
+ ])
+
+@@ -1355,7 +1357,7 @@
+ AC_DEFUN([AHX_CONFIG_FLAG_OMITTED],
+ [#if defined($1) && !defined($2)
+ #define $2 $3
+-[#]endif ])
++[#]endif])
+
+ dnl Wrapper for AHX_CONFIG_FLAG_OMITTED for -D style flags
+ dnl $1: the -DNAME or -DNAME=value string.
+--- contrib/unbound/acx_python.m4.orig
++++ contrib/unbound/acx_python.m4
+@@ -17,33 +17,62 @@
+ PYTHON_VERSION=`$PYTHON -c "import sys; \
+ print(sys.version.split()[[0]])"`
+ fi
++ # calculate the version number components.
++ [
++ v="$PYTHON_VERSION"
++ PYTHON_VERSION_MAJOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MAJOR"; then PYTHON_VERSION_MAJOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_MINOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MINOR"; then PYTHON_VERSION_MINOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_PATCH=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_PATCH"; then PYTHON_VERSION_PATCH="0"; fi
++ ]
+
+- # Check if you have sysconfig
+- AC_MSG_CHECKING([for the sysconfig Python module])
+- if ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`; then
++ # For some systems, sysconfig exists, but has the wrong paths,
++ # on Debian 10, for python 2.7 and 3.7. So, we check the version,
++ # and for older versions try distutils.sysconfig first. For newer
++ # versions>=3.10, where distutils.sysconfig is deprecated, use
++ # sysconfig first and then attempt the other one.
++ py_distutils_first="no"
++ if test $PYTHON_VERSION_MAJOR -lt 3; then
++ py_distutils_first="yes"
++ fi
++ if test $PYTHON_VERSION_MAJOR -eq 3 -a $PYTHON_VERSION_MINOR -lt 10; then
++ py_distutils_first="yes"
++ fi
++
++ # Check if you have the first module
++ if test "$py_distutils_first" = "yes"; then m="distutils"; else m="sysconfig"; fi
++ sysconfig_module=""
++ AC_MSG_CHECKING([for the $m Python module])
++ if ac_modulecheck_result1=`$PYTHON -c "import $m" 2>&1`; then
+ AC_MSG_RESULT([yes])
+- sysconfig_module="sysconfig"
+- # if yes, use sysconfig, because distutils is deprecated.
++ sysconfig_module="$m"
+ else
+ AC_MSG_RESULT([no])
+- # if no, try to use distutils
++ fi
+
+- #
+- # Check if you have distutils, else fail
+- #
+- AC_MSG_CHECKING([for the distutils Python package])
+- if ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`; then
++ # if not found, try the other one.
++ if test -z "$sysconfig_module"; then
++ if test "$py_distutils_first" = "yes"; then m2="sysconfig"; else m2="distutils"; fi
++ AC_MSG_CHECKING([for the $m2 Python module])
++ if ac_modulecheck_result2=`$PYTHON -c "import $m2" 2>&1`; then
+ AC_MSG_RESULT([yes])
++ sysconfig_module="$m2"
+ else
+ AC_MSG_RESULT([no])
+- AC_MSG_ERROR([cannot import Python module "distutils".
+- Please check your Python installation. The error was:
+- $ac_distutils_result])
++ AC_MSG_ERROR([cannot import Python module "$m", or "$m2".
++ Please check your Python installation. The errors are:
++ $m
++ $ac_modulecheck_result1
++ $m2
++ $ac_modulecheck_result2])
+ PYTHON_VERSION=""
+ fi
+-
+- sysconfig_module="distutils.sysconfig"
+ fi
++ if test "$sysconfig_module" = "distutils"; then sysconfig_module="distutils.sysconfig"; fi
+
+ #
+ # Check for Python include path
+--- contrib/unbound/cachedb/cachedb.c.orig
++++ contrib/unbound/cachedb/cachedb.c
+@@ -102,7 +102,6 @@
+ testframe_init(struct module_env* env, struct cachedb_env* cachedb_env)
+ {
+ struct testframe_moddata* d;
+- (void)env;
+ verbose(VERB_ALGO, "testframe_init");
+ d = (struct testframe_moddata*)calloc(1,
+ sizeof(struct testframe_moddata));
+@@ -111,6 +110,15 @@
+ log_err("out of memory");
+ return 0;
+ }
++ /* Register an EDNS option (65534) to bypass the worker cache lookup
++ * for testing */
++ if(!edns_register_option(LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST,
++ 1 /* bypass cache */,
++ 0 /* no aggregation */, env)) {
++ log_err("testframe_init, could not register test opcode");
++ free(d);
++ return 0;
++ }
+ lock_basic_init(&d->lock);
+ lock_protect(&d->lock, d, sizeof(*d));
+ return 1;
+@@ -218,6 +226,8 @@
+ cachedb_apply_cfg(struct cachedb_env* cachedb_env, struct config_file* cfg)
+ {
+ const char* backend_str = cfg->cachedb_backend;
++ if(!backend_str || *backend_str==0)
++ return 1;
+ cachedb_env->backend = cachedb_find_backend(backend_str);
+ if(!cachedb_env->backend) {
+ log_err("cachedb: cannot find backend name '%s'", backend_str);
+@@ -228,7 +238,7 @@
+ return 1;
+ }
+
+-int
++int
+ cachedb_init(struct module_env* env, int id)
+ {
+ struct cachedb_env* cachedb_env = (struct cachedb_env*)calloc(1,
+@@ -255,11 +265,11 @@
+ return 0;
+ }
+ cachedb_env->enabled = 1;
+- if(env->cfg->serve_expired_reply_ttl)
++ if(env->cfg->serve_expired && env->cfg->serve_expired_reply_ttl)
+ log_warn(
+ "cachedb: serve-expired-reply-ttl is set but not working for data "
+- "originating from the external cache; 0 TLL is used for those.");
+- if(env->cfg->serve_expired_client_timeout)
++ "originating from the external cache; 0 TTL is used for those.");
++ if(env->cfg->serve_expired && env->cfg->serve_expired_client_timeout)
+ log_warn(
+ "cachedb: serve-expired-client-timeout is set but not working for "
+ "data originating from the external cache; expired data are used "
+@@ -267,19 +277,16 @@
+ return 1;
+ }
+
+-void
++void
+ cachedb_deinit(struct module_env* env, int id)
+ {
+ struct cachedb_env* cachedb_env;
+ if(!env || !env->modinfo[id])
+ return;
+ cachedb_env = (struct cachedb_env*)env->modinfo[id];
+- /* free contents */
+- /* TODO */
+ if(cachedb_env->enabled) {
+ (*cachedb_env->backend->deinit)(env, cachedb_env);
+ }
+-
+ free(cachedb_env);
+ env->modinfo[id] = NULL;
+ }
+@@ -406,6 +413,14 @@
+ if(qstate->return_msg->rep->ttl == 0 &&
+ !qstate->env->cfg->serve_expired)
+ return 0;
++
++ /* The EDE is added to the out-list so it is encoded in the cached message */
++ if (qstate->env->cfg->ede && qstate->return_msg->rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns.opt_list_out, qstate->env->scratch,
++ qstate->return_msg->rep->reason_bogus,
++ qstate->return_msg->rep->reason_bogus_str);
++ }
++
+ if(verbosity >= VERB_ALGO)
+ log_dns_msg("cachedb encoding", &qstate->return_msg->qinfo,
+ qstate->return_msg->rep);
+@@ -502,6 +517,7 @@
+ {
+ struct msg_parse* prs;
+ struct edns_data edns;
++ struct edns_option* ede;
+ uint64_t timestamp, expiry;
+ time_t adjust;
+ size_t lim = sldns_buffer_limit(buf);
+@@ -539,6 +555,24 @@
+ if(!qstate->return_msg)
+ return 0;
+
++ /* We find the EDE in the in-list after parsing */
++ if(qstate->env->cfg->ede &&
++ (ede = edns_opt_list_find(edns.opt_list_in, LDNS_EDNS_EDE))) {
++ if(ede->opt_len >= 2) {
++ qstate->return_msg->rep->reason_bogus =
++ sldns_read_uint16(ede->opt_data);
++ }
++ /* allocate space and store the error string and it's size */
++ if(ede->opt_len > 2) {
++ size_t ede_len = ede->opt_len - 2;
++ qstate->return_msg->rep->reason_bogus_str = regional_alloc(
++ qstate->region, sizeof(char) * (ede_len+1));
++ memcpy(qstate->return_msg->rep->reason_bogus_str,
++ ede->opt_data+2, ede_len);
++ qstate->return_msg->rep->reason_bogus_str[ede_len] = 0;
++ }
++ }
++
+ qstate->return_rcode = LDNS_RCODE_NOERROR;
+
+ /* see how much of the TTL expired, and remove it */
+@@ -630,11 +664,15 @@
+ * See if unbound's internal cache can answer the query
+ */
+ static int
+-cachedb_intcache_lookup(struct module_qstate* qstate)
++cachedb_intcache_lookup(struct module_qstate* qstate, struct cachedb_env* cde)
+ {
+ uint8_t* dpname=NULL;
+ size_t dpnamelen=0;
+ struct dns_msg* msg;
++ /* for testframe bypass this lookup */
++ if(cde->backend == &testframe_backend) {
++ return 0;
++ }
+ if(iter_stub_fwd_no_cache(qstate, &qstate->qinfo,
+ &dpname, &dpnamelen))
+ return 0; /* no cache for these queries */
+@@ -693,6 +731,7 @@
+ struct cachedb_qstate* ATTR_UNUSED(iq),
+ struct cachedb_env* ie, int id)
+ {
++ qstate->is_cachedb_answer = 0;
+ /* check if we are enabled, and skip if so */
+ if(!ie->enabled) {
+ /* pass request to next module */
+@@ -709,7 +748,7 @@
+
+ /* lookup inside unbound's internal cache.
+ * This does not look for expired entries. */
+- if(cachedb_intcache_lookup(qstate)) {
++ if(cachedb_intcache_lookup(qstate, ie)) {
+ if(verbosity >= VERB_ALGO) {
+ if(qstate->return_msg->rep)
+ log_dns_msg("cachedb internal cache lookup",
+@@ -746,6 +785,7 @@
+ qstate->ext_state[id] = module_wait_module;
+ return;
+ }
++ qstate->is_cachedb_answer = 1;
+ /* we are done with the query */
+ qstate->ext_state[id] = module_finished;
+ return;
+@@ -768,12 +808,18 @@
+ cachedb_handle_response(struct module_qstate* qstate,
+ struct cachedb_qstate* ATTR_UNUSED(iq), struct cachedb_env* ie, int id)
+ {
++ qstate->is_cachedb_answer = 0;
+ /* check if we are not enabled or instructed to not cache, and skip */
+ if(!ie->enabled || qstate->no_cache_store) {
+ /* we are done with the query */
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
++ if(qstate->env->cfg->cachedb_no_store) {
++ /* do not store the item in the external cache */
++ qstate->ext_state[id] = module_finished;
++ return;
++ }
+
+ /* store the item into the backend cache */
+ cachedb_extcache_store(qstate, ie);
+--- contrib/unbound/cachedb/redis.c.orig
++++ contrib/unbound/cachedb/redis.c
+@@ -56,19 +56,43 @@
+ int numctxs; /* number of ctx entries */
+ const char* server_host; /* server's IP address or host name */
+ int server_port; /* server's TCP port */
++ const char* server_path; /* server's unix path, or "", NULL if unused */
++ const char* server_password; /* server's AUTH password, or "", NULL if unused */
+ struct timeval timeout; /* timeout for connection setup and commands */
++ int logical_db; /* the redis logical database to use */
+ };
+
+ static redisReply* redis_command(struct module_env*, struct cachedb_env*,
+ const char*, const uint8_t*, size_t);
+
++static void
++moddata_clean(struct redis_moddata** moddata) {
++ if(!moddata || !*moddata)
++ return;
++ if((*moddata)->ctxs) {
++ int i;
++ for(i = 0; i < (*moddata)->numctxs; i++) {
++ if((*moddata)->ctxs[i])
++ redisFree((*moddata)->ctxs[i]);
++ }
++ free((*moddata)->ctxs);
++ }
++ free(*moddata);
++ *moddata = NULL;
++}
++
+ static redisContext*
+ redis_connect(const struct redis_moddata* moddata)
+ {
+ redisContext* ctx;
+
+- ctx = redisConnectWithTimeout(moddata->server_host,
+- moddata->server_port, moddata->timeout);
++ if(moddata->server_path && moddata->server_path[0]!=0) {
++ ctx = redisConnectUnixWithTimeout(moddata->server_path,
++ moddata->timeout);
++ } else {
++ ctx = redisConnectWithTimeout(moddata->server_host,
++ moddata->server_port, moddata->timeout);
++ }
+ if(!ctx || ctx->err) {
+ const char *errstr = "out of memory";
+ if(ctx)
+@@ -80,9 +104,31 @@
+ log_err("failed to set redis timeout");
+ goto fail;
+ }
++ if(moddata->server_password && moddata->server_password[0]!=0) {
++ redisReply* rep;
++ rep = redisCommand(ctx, "AUTH %s", moddata->server_password);
++ if(!rep || rep->type == REDIS_REPLY_ERROR) {
++ log_err("failed to authenticate with password");
++ freeReplyObject(rep);
++ goto fail;
++ }
++ freeReplyObject(rep);
++ }
++ if(moddata->logical_db > 0) {
++ redisReply* rep;
++ rep = redisCommand(ctx, "SELECT %d", moddata->logical_db);
++ if(!rep || rep->type == REDIS_REPLY_ERROR) {
++ log_err("failed to set logical database (%d)",
++ moddata->logical_db);
++ freeReplyObject(rep);
++ goto fail;
++ }
++ freeReplyObject(rep);
++ }
++ verbose(VERB_OPS, "Connection to Redis established");
+ return ctx;
+
+- fail:
++fail:
+ if(ctx)
+ redisFree(ctx);
+ return NULL;
+@@ -94,28 +140,36 @@
+ int i;
+ struct redis_moddata* moddata = NULL;
+
+- verbose(VERB_ALGO, "redis_init");
++ verbose(VERB_OPS, "Redis initialization");
+
+ moddata = calloc(1, sizeof(struct redis_moddata));
+ if(!moddata) {
+ log_err("out of memory");
+- return 0;
++ goto fail;
+ }
+ moddata->numctxs = env->cfg->num_threads;
+ moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
+ if(!moddata->ctxs) {
+ log_err("out of memory");
+- free(moddata);
+- return 0;
++ goto fail;
+ }
+ /* note: server_host is a shallow reference to configured string.
+ * we don't have to free it in this module. */
+ moddata->server_host = env->cfg->redis_server_host;
+ moddata->server_port = env->cfg->redis_server_port;
++ moddata->server_path = env->cfg->redis_server_path;
++ moddata->server_password = env->cfg->redis_server_password;
+ moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
+ moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
+- for(i = 0; i < moddata->numctxs; i++)
+- moddata->ctxs[i] = redis_connect(moddata);
++ moddata->logical_db = env->cfg->redis_logical_db;
++ for(i = 0; i < moddata->numctxs; i++) {
++ redisContext* ctx = redis_connect(moddata);
++ if(!ctx) {
++ log_err("redis_init: failed to init redis");
++ goto fail;
++ }
++ moddata->ctxs[i] = ctx;
++ }
+ cachedb_env->backend_data = moddata;
+ if(env->cfg->redis_expire_records) {
+ redisReply* rep = NULL;
+@@ -128,7 +182,7 @@
+ log_err("redis_init: failed to init redis, the "
+ "redis-expire-records option requires the SETEX command "
+ "(redis >= 2.0.0)");
+- return 0;
++ goto fail;
+ }
+ redis_reply_type = rep->type;
+ freeReplyObject(rep);
+@@ -140,11 +194,14 @@
+ log_err("redis_init: failed to init redis, the "
+ "redis-expire-records option requires the SETEX command "
+ "(redis >= 2.0.0)");
+- return 0;
++ goto fail;
+ }
+ }
+-
+ return 1;
++
++fail:
++ moddata_clean(&moddata);
++ return 0;
+ }
+
+ static void
+@@ -154,19 +211,8 @@
+ cachedb_env->backend_data;
+ (void)env;
+
+- verbose(VERB_ALGO, "redis_deinit");
+-
+- if(!moddata)
+- return;
+- if(moddata->ctxs) {
+- int i;
+- for(i = 0; i < moddata->numctxs; i++) {
+- if(moddata->ctxs[i])
+- redisFree(moddata->ctxs[i]);
+- }
+- free(moddata->ctxs);
+- }
+- free(moddata);
++ verbose(VERB_OPS, "Redis deinitialization");
++ moddata_clean(&moddata);
+ }
+
+ /*
+--- contrib/unbound/compat/getentropy_solaris.c.orig
++++ contrib/unbound/compat/getentropy_solaris.c
+@@ -47,7 +47,7 @@
+ #define SHA512_Update SHA512Update
+ #define SHA512_Final SHA512Final
+ #else
+-#include "openssl/sha.h"
++#include <openssl/sha.h>
+ #endif
+
+ #include <sys/vfs.h>
+--- contrib/unbound/config.guess.orig
++++ contrib/unbound/config.guess
+@@ -1,10 +1,10 @@
+ #! /bin/sh
+ # Attempt to guess a canonical system name.
+-# Copyright 1992-2022 Free Software Foundation, Inc.
++# Copyright 1992-2024 Free Software Foundation, Inc.
+
+ # shellcheck disable=SC2006,SC2268 # see below for rationale
+
+-timestamp='2022-09-17'
++timestamp='2024-01-01'
+
+ # This file is free software; you can redistribute it and/or modify it
+ # under the terms of the GNU General Public License as published by
+@@ -47,7 +47,7 @@
+ usage="\
+ Usage: $0 [OPTION]
+
+-Output the configuration name of the system \`$me' is run on.
++Output the configuration name of the system '$me' is run on.
+
+ Options:
+ -h, --help print this help, then exit
+@@ -60,13 +60,13 @@
+ GNU config.guess ($timestamp)
+
+ Originally written by Per Bothner.
+-Copyright 1992-2022 Free Software Foundation, Inc.
++Copyright 1992-2024 Free Software Foundation, Inc.
+
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+ help="
+-Try \`$me --help' for more information."
++Try '$me --help' for more information."
+
+ # Parse command line
+ while test $# -gt 0 ; do
+@@ -102,8 +102,8 @@
+ # temporary files to be created and, as you can see below, it is a
+ # headache to deal with in a portable fashion.
+
+-# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+-# use `HOST_CC' if defined, but it is deprecated.
++# Historically, 'CC_FOR_BUILD' used to be named 'HOST_CC'. We still
++# use 'HOST_CC' if defined, but it is deprecated.
+
+ # Portable tmp directory creation inspired by the Autoconf team.
+
+@@ -155,6 +155,9 @@
+
+ set_cc_for_build
+ cat <<-EOF > "$dummy.c"
++ #if defined(__ANDROID__)
++ LIBC=android
++ #else
+ #include <features.h>
+ #if defined(__UCLIBC__)
+ LIBC=uclibc
+@@ -162,6 +165,8 @@
+ LIBC=dietlibc
+ #elif defined(__GLIBC__)
+ LIBC=gnu
++ #elif defined(__LLVM_LIBC__)
++ LIBC=llvm
+ #else
+ #include <stdarg.h>
+ /* First heuristic to detect musl libc. */
+@@ -169,6 +174,7 @@
+ LIBC=musl
+ #endif
+ #endif
++ #endif
+ EOF
+ cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+ eval "$cc_set_libc"
+@@ -459,7 +465,7 @@
+ UNAME_RELEASE=`uname -v`
+ ;;
+ esac
+- # Japanese Language versions have a version number like `4.1.3-JL'.
++ # Japanese Language versions have a version number like '4.1.3-JL'.
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
+ GUESS=sparc-sun-sunos$SUN_REL
+ ;;
+@@ -904,7 +910,7 @@
+ fi
+ ;;
+ *:FreeBSD:*:*)
+- UNAME_PROCESSOR=`/usr/bin/uname -p`
++ UNAME_PROCESSOR=`uname -p`
+ case $UNAME_PROCESSOR in
+ amd64)
+ UNAME_PROCESSOR=x86_64 ;;
+@@ -976,7 +982,27 @@
+ GUESS=$UNAME_MACHINE-unknown-minix
+ ;;
+ aarch64:Linux:*:*)
+- GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
++ set_cc_for_build
++ CPU=$UNAME_MACHINE
++ LIBCABI=$LIBC
++ if test "$CC_FOR_BUILD" != no_compiler_found; then
++ ABI=64
++ sed 's/^ //' << EOF > "$dummy.c"
++ #ifdef __ARM_EABI__
++ #ifdef __ARM_PCS_VFP
++ ABI=eabihf
++ #else
++ ABI=eabi
++ #endif
++ #endif
++EOF
++ cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'`
++ eval "$cc_set_abi"
++ case $ABI in
++ eabi | eabihf) CPU=armv8l; LIBCABI=$LIBC$ABI ;;
++ esac
++ fi
++ GUESS=$CPU-unknown-linux-$LIBCABI
+ ;;
+ aarch64_be:Linux:*:*)
+ UNAME_MACHINE=aarch64_be
+@@ -1042,6 +1068,15 @@
+ k1om:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
++ kvx:Linux:*:*)
++ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
++ ;;
++ kvx:cos:*:*)
++ GUESS=$UNAME_MACHINE-unknown-cos
++ ;;
++ kvx:mbr:*:*)
++ GUESS=$UNAME_MACHINE-unknown-mbr
++ ;;
+ loongarch32:Linux:*:* | loongarch64:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+@@ -1197,7 +1232,7 @@
+ GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
+ ;;
+ i*86:OS/2:*:*)
+- # If we were able to find `uname', then EMX Unix compatibility
++ # If we were able to find 'uname', then EMX Unix compatibility
+ # is probably installed.
+ GUESS=$UNAME_MACHINE-pc-os2-emx
+ ;;
+@@ -1338,7 +1373,7 @@
+ GUESS=ns32k-sni-sysv
+ fi
+ ;;
+- PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
++ PENTIUM:*:4.0*:*) # Unisys 'ClearPath HMP IX 4000' SVR4/MP effort
+ # says <Richard.M.Bartel@ccMail.Census.GOV>
+ GUESS=i586-unisys-sysv4
+ ;;
+@@ -1560,6 +1595,9 @@
+ *:Unleashed:*:*)
+ GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
+ ;;
++ *:Ironclad:*:*)
++ GUESS=$UNAME_MACHINE-unknown-ironclad
++ ;;
+ esac
+
+ # Do we have a guess based on uname results?
+--- contrib/unbound/config.h.in.orig
++++ contrib/unbound/config.h.in
+@@ -364,6 +364,9 @@
+ /* Define if we have LibreSSL */
+ #undef HAVE_LIBRESSL
+
++/* Define to 1 if you have the <linux/net_tstamp.h> header file. */
++#undef HAVE_LINUX_NET_TSTAMP_H
++
+ /* Define to 1 if you have the `localtime_r' function. */
+ #undef HAVE_LOCALTIME_R
+
+@@ -1068,39 +1071,39 @@
+
+ #if defined(OMITTED__D_GNU_SOURCE) && !defined(_GNU_SOURCE)
+ #define _GNU_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_BSD_SOURCE) && !defined(_BSD_SOURCE)
+ #define _BSD_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_DEFAULT_SOURCE) && !defined(_DEFAULT_SOURCE)
+ #define _DEFAULT_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D__EXTENSIONS__) && !defined(__EXTENSIONS__)
+ #define __EXTENSIONS__ 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_POSIX_C_SOURCE_200112) && !defined(_POSIX_C_SOURCE)
+ #define _POSIX_C_SOURCE 200112
+-#endif
++#endif
+
+ #if defined(OMITTED__D_XOPEN_SOURCE_600) && !defined(_XOPEN_SOURCE)
+ #define _XOPEN_SOURCE 600
+-#endif
++#endif
+
+ #if defined(OMITTED__D_XOPEN_SOURCE_EXTENDED_1) && !defined(_XOPEN_SOURCE_EXTENDED)
+ #define _XOPEN_SOURCE_EXTENDED 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_ALL_SOURCE) && !defined(_ALL_SOURCE)
+ #define _ALL_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_LARGEFILE_SOURCE_1) && !defined(_LARGEFILE_SOURCE)
+ #define _LARGEFILE_SOURCE 1
+-#endif
++#endif
+
+
+
+@@ -1184,7 +1187,7 @@
+ #endif
+
+
+-
++
+ #ifdef HAVE_ATTR_FORMAT
+ # define ATTR_FORMAT(archetype, string_index, first_to_check) \
+ __attribute__ ((format (archetype, string_index, first_to_check)))
+@@ -1294,7 +1297,7 @@
+ #ifdef HAVE_WINSOCK2_H
+ #define FD_SET_T (u_int)
+ #else
+-#define FD_SET_T
++#define FD_SET_T
+ #endif
+
+
+--- contrib/unbound/config.sub.orig
++++ contrib/unbound/config.sub
+@@ -1,10 +1,10 @@
+ #! /bin/sh
+ # Configuration validation subroutine script.
+-# Copyright 1992-2022 Free Software Foundation, Inc.
++# Copyright 1992-2024 Free Software Foundation, Inc.
+
+ # shellcheck disable=SC2006,SC2268 # see below for rationale
+
+-timestamp='2022-09-17'
++timestamp='2024-01-01'
+
+ # This file is free software; you can redistribute it and/or modify it
+ # under the terms of the GNU General Public License as published by
+@@ -76,13 +76,13 @@
+ version="\
+ GNU config.sub ($timestamp)
+
+-Copyright 1992-2022 Free Software Foundation, Inc.
++Copyright 1992-2024 Free Software Foundation, Inc.
+
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+ help="
+-Try \`$me --help' for more information."
++Try '$me --help' for more information."
+
+ # Parse command line
+ while test $# -gt 0 ; do
+@@ -130,7 +130,7 @@
+ # Separate into logical components for further validation
+ case $1 in
+ *-*-*-*-*)
+- echo Invalid configuration \`"$1"\': more than four components >&2
++ echo "Invalid configuration '$1': more than four components" >&2
+ exit 1
+ ;;
+ *-*-*-*)
+@@ -145,7 +145,8 @@
+ nto-qnx* | linux-* | uclinux-uclibc* \
+ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
+ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
+- | storm-chaos* | os2-emx* | rtmk-nova* | managarm-*)
++ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \
++ | windows-* )
+ basic_machine=$field1
+ basic_os=$maybe_os
+ ;;
+@@ -943,7 +944,7 @@
+ EOF
+ IFS=$saved_IFS
+ ;;
+- # We use `pc' rather than `unknown'
++ # We use 'pc' rather than 'unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+@@ -1075,7 +1076,7 @@
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ cpu=i586
+ ;;
+- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
++ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*)
+ cpu=i686
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+@@ -1180,7 +1181,7 @@
+ case $cpu in
+ 1750a | 580 \
+ | a29k \
+- | aarch64 | aarch64_be \
++ | aarch64 | aarch64_be | aarch64c | arm64ec \
+ | abacus \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
+@@ -1199,12 +1200,14 @@
+ | d10v | d30v | dlx | dsp16xx \
+ | e2k | elxsi | epiphany \
+ | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
++ | javascript \
+ | h8300 | h8500 \
+ | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | hexagon \
+ | i370 | i*86 | i860 | i960 | ia16 | ia64 \
+ | ip2k | iq2000 \
+ | k1om \
++ | kvx \
+ | le32 | le64 \
+ | lm32 \
+ | loongarch32 | loongarch64 \
+@@ -1213,36 +1216,13 @@
+ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
+ | m88110 | m88k | maxq | mb | mcore | mep | metag \
+ | microblaze | microblazeel \
+- | mips | mipsbe | mipseb | mipsel | mipsle \
+- | mips16 \
+- | mips64 | mips64eb | mips64el \
+- | mips64octeon | mips64octeonel \
+- | mips64orion | mips64orionel \
+- | mips64r5900 | mips64r5900el \
+- | mips64vr | mips64vrel \
+- | mips64vr4100 | mips64vr4100el \
+- | mips64vr4300 | mips64vr4300el \
+- | mips64vr5000 | mips64vr5000el \
+- | mips64vr5900 | mips64vr5900el \
+- | mipsisa32 | mipsisa32el \
+- | mipsisa32r2 | mipsisa32r2el \
+- | mipsisa32r3 | mipsisa32r3el \
+- | mipsisa32r5 | mipsisa32r5el \
+- | mipsisa32r6 | mipsisa32r6el \
+- | mipsisa64 | mipsisa64el \
+- | mipsisa64r2 | mipsisa64r2el \
+- | mipsisa64r3 | mipsisa64r3el \
+- | mipsisa64r5 | mipsisa64r5el \
+- | mipsisa64r6 | mipsisa64r6el \
+- | mipsisa64sb1 | mipsisa64sb1el \
+- | mipsisa64sr71k | mipsisa64sr71kel \
+- | mipsr5900 | mipsr5900el \
+- | mipstx39 | mipstx39el \
++ | mips* \
+ | mmix \
+ | mn10200 | mn10300 \
+ | moxie \
+ | mt \
+ | msp430 \
++ | nanomips* \
+ | nds32 | nds32le | nds32be \
+ | nfp \
+ | nios | nios2 | nios2eb | nios2el \
+@@ -1274,6 +1254,7 @@
+ | ubicom32 \
+ | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
+ | vax \
++ | vc4 \
+ | visium \
+ | w65 \
+ | wasm32 | wasm64 \
+@@ -1285,7 +1266,7 @@
+ ;;
+
+ *)
+- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
++ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2
+ exit 1
+ ;;
+ esac
+@@ -1306,11 +1287,12 @@
+
+ # Decode manufacturer-specific aliases for certain operating systems.
+
+-if test x$basic_os != x
++if test x"$basic_os" != x
+ then
+
+ # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
+ # set os.
++obj=
+ case $basic_os in
+ gnu/linux*)
+ kernel=linux
+@@ -1510,10 +1492,16 @@
+ os=eabi
+ ;;
+ *)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ esac
+ ;;
++ aout* | coff* | elf* | pe*)
++ # These are machine code file formats, not OSes
++ obj=$os
++ os=
++ ;;
+ *)
+ # No normalization, but not necessarily accepted, that comes below.
+ ;;
+@@ -1532,12 +1520,15 @@
+ # system, and we'll never get to this point.
+
+ kernel=
++obj=
+ case $cpu-$vendor in
+ score-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ spu-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ *-acorn)
+ os=riscix1.2
+@@ -1547,28 +1538,35 @@
+ os=gnu
+ ;;
+ arm*-semi)
+- os=aout
++ os=
++ obj=aout
+ ;;
+ c4x-* | tic4x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ c8051-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ clipper-intergraph)
+ os=clix
+ ;;
+ hexagon-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ tic54x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ tic55x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ tic6x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+@@ -1590,19 +1588,24 @@
+ os=sunos3
+ ;;
+ m68*-cisco)
+- os=aout
++ os=
++ obj=aout
+ ;;
+ mep-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ mips*-cisco)
+- os=elf
++ os=
++ obj=elf
+ ;;
+- mips*-*)
+- os=elf
++ mips*-*|nanomips*-*)
++ os=
++ obj=elf
+ ;;
+ or32-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=sysv3
+@@ -1611,7 +1614,8 @@
+ os=sunos4.1.1
+ ;;
+ pru-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ *-be)
+ os=beos
+@@ -1692,10 +1696,12 @@
+ os=uxpv
+ ;;
+ *-rom68k)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-*bug)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-apple)
+ os=macos
+@@ -1713,10 +1719,11 @@
+
+ fi
+
+-# Now, validate our (potentially fixed-up) OS.
++# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ).
++
+ case $os in
+ # Sometimes we do "kernel-libc", so those need to count as OSes.
+- musl* | newlib* | relibc* | uclibc*)
++ llvm* | musl* | newlib* | relibc* | uclibc*)
+ ;;
+ # Likewise for "kernel-abi"
+ eabi* | gnueabi*)
+@@ -1724,6 +1731,9 @@
+ # VxWorks passes extra cpu info in the 4th filed.
+ simlinux | simwindows | spe)
+ ;;
++ # See `case $cpu-$os` validation below
++ ghcjs)
++ ;;
+ # Now accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST end in a * to match a version number.
+@@ -1732,7 +1742,7 @@
+ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
+ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
+ | hiux* | abug | nacl* | netware* | windows* \
+- | os9* | macos* | osx* | ios* \
++ | os9* | macos* | osx* | ios* | tvos* | watchos* \
+ | mpw* | magic* | mmixware* | mon960* | lnews* \
+ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
+ | aos* | aros* | cloudabi* | sortix* | twizzler* \
+@@ -1741,11 +1751,11 @@
+ | mirbsd* | netbsd* | dicos* | openedition* | ose* \
+ | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
+ | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
+- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
+- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
++ | bosx* | nextstep* | cxux* | oabi* \
++ | ptx* | ecoff* | winnt* | domain* | vsta* \
+ | udi* | lites* | ieee* | go32* | aux* | hcos* \
+ | chorusrdb* | cegcc* | glidix* | serenity* \
+- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
++ | cygwin* | msys* | moss* | proelf* | rtems* \
+ | midipix* | mingw32* | mingw64* | mint* \
+ | uxpv* | beos* | mpeix* | udk* | moxiebox* \
+ | interix* | uwin* | mks* | rhapsody* | darwin* \
+@@ -1758,62 +1768,116 @@
+ | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
+ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
+ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
+- | fiwix* | mlibc* )
++ | fiwix* | mlibc* | cos* | mbr* | ironclad* )
+ ;;
+ # This one is extra strict with allowed versions
+ sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
+ # Don't forget version if it is 3.2v4 or newer.
+ ;;
++ # This refers to builds using the UEFI calling convention
++ # (which depends on the architecture) and PE file format.
++ # Note that this is both a different calling convention and
++ # different file format than that of GNU-EFI
++ # (x86_64-w64-mingw32).
++ uefi)
++ ;;
+ none)
+ ;;
+- kernel* )
++ kernel* | msvc* )
+ # Restricted further below
+ ;;
++ '')
++ if test x"$obj" = x
++ then
++ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2
++ fi
++ ;;
+ *)
+- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
++ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2
++ exit 1
++ ;;
++esac
++
++case $obj in
++ aout* | coff* | elf* | pe*)
++ ;;
++ '')
++ # empty is fine
++ ;;
++ *)
++ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2
++ exit 1
++ ;;
++esac
++
++# Here we handle the constraint that a (synthetic) cpu and os are
++# valid only in combination with each other and nowhere else.
++case $cpu-$os in
++ # The "javascript-unknown-ghcjs" triple is used by GHC; we
++ # accept it here in order to tolerate that, but reject any
++ # variations.
++ javascript-ghcjs)
++ ;;
++ javascript-* | *-ghcjs)
++ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2
+ exit 1
+ ;;
+ esac
+
+ # As a final step for OS-related things, validate the OS-kernel combination
+ # (given a valid OS), if there is a kernel.
+-case $kernel-$os in
+- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
+- | linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* )
++case $kernel-$os-$obj in
++ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \
++ | linux-mlibc*- | linux-musl*- | linux-newlib*- \
++ | linux-relibc*- | linux-uclibc*- )
+ ;;
+- uclinux-uclibc* )
++ uclinux-uclibc*- )
+ ;;
+- managarm-mlibc* | managarm-kernel* )
++ managarm-mlibc*- | managarm-kernel*- )
+ ;;
+- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* )
++ windows*-msvc*-)
++ ;;
++ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \
++ | -uclibc*- )
+ # These are just libc implementations, not actual OSes, and thus
+ # require a kernel.
+- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
++ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2
+ exit 1
+ ;;
+- -kernel* )
+- echo "Invalid configuration \`$1': \`$os' needs explicit kernel." 1>&2
++ -kernel*- )
++ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2
+ exit 1
+ ;;
+- *-kernel* )
+- echo "Invalid configuration \`$1': \`$kernel' does not support \`$os'." 1>&2
++ *-kernel*- )
++ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2
+ exit 1
+ ;;
+- kfreebsd*-gnu* | kopensolaris*-gnu*)
++ *-msvc*- )
++ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2
++ exit 1
+ ;;
+- vxworks-simlinux | vxworks-simwindows | vxworks-spe)
++ kfreebsd*-gnu*- | kopensolaris*-gnu*-)
+ ;;
+- nto-qnx*)
++ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-)
+ ;;
+- os2-emx)
++ nto-qnx*-)
++ ;;
++ os2-emx-)
++ ;;
++ *-eabi*- | *-gnueabi*-)
+ ;;
+- *-eabi* | *-gnueabi*)
++ none--*)
++ # None (no kernel, i.e. freestanding / bare metal),
++ # can be paired with an machine code file format
+ ;;
+- -*)
++ -*-)
+ # Blank kernel with real OS is always fine.
+ ;;
+- *-*)
+- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
++ --*)
++ # Blank kernel and OS with real machine code file format is always fine.
++ ;;
++ *-*-*)
++ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2
+ exit 1
+ ;;
+ esac
+@@ -1896,7 +1960,7 @@
+ ;;
+ esac
+
+-echo "$cpu-$vendor-${kernel:+$kernel-}$os"
++echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}"
+ exit
+
+ # Local variables:
+--- contrib/unbound/configure.orig
++++ contrib/unbound/configure
+@@ -1,6 +1,6 @@
+ #! /bin/sh
+ # Guess values for system-dependent variables and create Makefiles.
+-# Generated by GNU Autoconf 2.69 for unbound 1.17.1.
++# Generated by GNU Autoconf 2.69 for unbound 1.19.1.
+ #
+ # Report bugs to <unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues>.
+ #
+@@ -591,8 +591,8 @@
+ # Identity of this package.
+ PACKAGE_NAME='unbound'
+ PACKAGE_TARNAME='unbound'
+-PACKAGE_VERSION='1.17.1'
+-PACKAGE_STRING='unbound 1.17.1'
++PACKAGE_VERSION='1.19.1'
++PACKAGE_STRING='unbound 1.19.1'
+ PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
+ PACKAGE_URL=''
+
+@@ -1477,7 +1477,7 @@
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+-\`configure' configures unbound 1.17.1 to adapt to many kinds of systems.
++\`configure' configures unbound 1.19.1 to adapt to many kinds of systems.
+
+ Usage: $0 [OPTION]... [VAR=VALUE]...
+
+@@ -1543,7 +1543,7 @@
+
+ if test -n "$ac_init_help"; then
+ case $ac_init_help in
+- short | recursive ) echo "Configuration of unbound 1.17.1:";;
++ short | recursive ) echo "Configuration of unbound 1.19.1:";;
+ esac
+ cat <<\_ACEOF
+
+@@ -1785,7 +1785,7 @@
+ test -n "$ac_init_help" && exit $ac_status
+ if $ac_init_version; then
+ cat <<\_ACEOF
+-unbound configure 1.17.1
++unbound configure 1.19.1
+ generated by GNU Autoconf 2.69
+
+ Copyright (C) 2012 Free Software Foundation, Inc.
+@@ -2494,7 +2494,7 @@
+ This file contains any messages produced by compilers while
+ running configure, to aid debugging if configure makes a mistake.
+
+-It was created by unbound $as_me 1.17.1, which was
++It was created by unbound $as_me 1.19.1, which was
+ generated by GNU Autoconf 2.69. Invocation command line was
+
+ $ $0 $@
+@@ -2844,13 +2844,13 @@
+
+ UNBOUND_VERSION_MAJOR=1
+
+-UNBOUND_VERSION_MINOR=17
++UNBOUND_VERSION_MINOR=19
+
+ UNBOUND_VERSION_MICRO=1
+
+
+ LIBUNBOUND_CURRENT=9
+-LIBUNBOUND_REVISION=21
++LIBUNBOUND_REVISION=24
+ LIBUNBOUND_AGE=1
+ # 1.0.0 had 0:12:0
+ # 1.0.1 had 0:13:0
+@@ -2939,6 +2939,9 @@
+ # 1.16.3 had 9:19:1
+ # 1.17.0 had 9:20:1
+ # 1.17.1 had 9:21:1
++# 1.18.0 had 9:22:1
++# 1.19.0 had 9:23:1
++# 1.19.1 had 9:24:1
+
+ # Current -- the number of the binary API that we're implementing
+ # Revision -- which iteration of the implementation of the binary
+@@ -4603,450 +4606,186 @@
+
+ default_cflags=yes
+ fi
+-ac_ext=c
+-ac_cpp='$CPP $CPPFLAGS'
+-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+-ac_compiler_gnu=$ac_cv_c_compiler_gnu
+-if test -n "$ac_tool_prefix"; then
+- # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+-set dummy ${ac_tool_prefix}gcc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_CC="${ac_tool_prefix}gcc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+-fi
+-if test -z "$ac_cv_prog_CC"; then
+- ac_ct_CC=$CC
+- # Extract the first word of "gcc", so it can be a program name with args.
+-set dummy gcc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_ac_ct_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$ac_ct_CC"; then
+- ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_ac_ct_CC="gcc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-ac_ct_CC=$ac_cv_prog_ac_ct_CC
+-if test -n "$ac_ct_CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+-$as_echo "$ac_ct_CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+- if test "x$ac_ct_CC" = x; then
+- CC=""
+- else
+- case $cross_compiling:$ac_tool_warned in
+-yes:)
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+-ac_tool_warned=yes ;;
+-esac
+- CC=$ac_ct_CC
+- fi
+-else
+- CC="$ac_cv_prog_CC"
+-fi
+-
+-if test -z "$CC"; then
+- if test -n "$ac_tool_prefix"; then
+- # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+-set dummy ${ac_tool_prefix}cc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_CC="${ac_tool_prefix}cc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+- fi
+-fi
+-if test -z "$CC"; then
+- # Extract the first word of "cc", so it can be a program name with args.
+-set dummy cc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+- ac_prog_rejected=no
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+- ac_prog_rejected=yes
+- continue
+- fi
+- ac_cv_prog_CC="cc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-if test $ac_prog_rejected = yes; then
+- # We found a bogon in the path, so make sure we never use it.
+- set dummy $ac_cv_prog_CC
+- shift
+- if test $# != 0; then
+- # We chose a different compiler from the bogus one.
+- # However, it has the same basename, so the bogon will be chosen
+- # first if we set CC to just the basename; use the full file name.
+- shift
+- ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+- fi
+-fi
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+-fi
+-if test -z "$CC"; then
+- if test -n "$ac_tool_prefix"; then
+- for ac_prog in cl.exe
+- do
+- # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+-set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+- test -n "$CC" && break
+- done
+-fi
+-if test -z "$CC"; then
+- ac_ct_CC=$CC
+- for ac_prog in cl.exe
+-do
+- # Extract the first word of "$ac_prog", so it can be a program name with args.
+-set dummy $ac_prog; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_ac_ct_CC+:} false; then :
++ case $ac_cv_prog_cc_stdc in #(
++ no) :
++ ac_cv_prog_cc_c99=no; ac_cv_prog_cc_c89=no ;; #(
++ *) :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5
++$as_echo_n "checking for $CC option to accept ISO C99... " >&6; }
++if ${ac_cv_prog_cc_c99+:} false; then :
+ $as_echo_n "(cached) " >&6
+ else
+- if test -n "$ac_ct_CC"; then
+- ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_ac_ct_CC="$ac_prog"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
++ ac_cv_prog_cc_c99=no
++ac_save_CC=$CC
++cat confdefs.h - <<_ACEOF >conftest.$ac_ext
++/* end confdefs.h. */
++#include <stdarg.h>
++#include <stdbool.h>
++#include <stdlib.h>
++#include <wchar.h>
++#include <stdio.h>
+
+-fi
+-fi
+-ac_ct_CC=$ac_cv_prog_ac_ct_CC
+-if test -n "$ac_ct_CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+-$as_echo "$ac_ct_CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
++// Check varargs macros. These examples are taken from C99 6.10.3.5.
++#define debug(...) fprintf (stderr, __VA_ARGS__)
++#define showlist(...) puts (#__VA_ARGS__)
++#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
++static void
++test_varargs_macros (void)
++{
++ int x = 1234;
++ int y = 5678;
++ debug ("Flag");
++ debug ("X = %d\n", x);
++ showlist (The first, second, and third items.);
++ report (x>y, "x is %d but y is %d", x, y);
++}
+
++// Check long long types.
++#define BIG64 18446744073709551615ull
++#define BIG32 4294967295ul
++#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
++#if !BIG_OK
++ your preprocessor is broken;
++#endif
++#if BIG_OK
++#else
++ your preprocessor is broken;
++#endif
++static long long int bignum = -9223372036854775807LL;
++static unsigned long long int ubignum = BIG64;
+
+- test -n "$ac_ct_CC" && break
+-done
++struct incomplete_array
++{
++ int datasize;
++ double data[];
++};
+
+- if test "x$ac_ct_CC" = x; then
+- CC=""
+- else
+- case $cross_compiling:$ac_tool_warned in
+-yes:)
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+-ac_tool_warned=yes ;;
+-esac
+- CC=$ac_ct_CC
+- fi
+-fi
++struct named_init {
++ int number;
++ const wchar_t *name;
++ double average;
++};
+
+-fi
++typedef const char *ccp;
+
++static inline int
++test_restrict (ccp restrict text)
++{
++ // See if C++-style comments work.
++ // Iterate through items via the restricted pointer.
++ // Also check for declarations in for loops.
++ for (unsigned int i = 0; *(text+i) != '\0'; ++i)
++ continue;
++ return 0;
++}
+
+-test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "no acceptable C compiler found in \$PATH
+-See \`config.log' for more details" "$LINENO" 5; }
++// Check varargs and va_copy.
++static void
++test_varargs (const char *format, ...)
++{
++ va_list args;
++ va_start (args, format);
++ va_list args_copy;
++ va_copy (args_copy, args);
+
+-# Provide some information about the compiler.
+-$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+-set X $ac_compile
+-ac_compiler=$2
+-for ac_option in --version -v -V -qversion; do
+- { { ac_try="$ac_compiler $ac_option >&5"
+-case "(($ac_try" in
+- *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+- *) ac_try_echo=$ac_try;;
+-esac
+-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+-$as_echo "$ac_try_echo"; } >&5
+- (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+- ac_status=$?
+- if test -s conftest.err; then
+- sed '10a\
+-... rest of stderr output deleted ...
+- 10q' conftest.err >conftest.er1
+- cat conftest.er1 >&5
+- fi
+- rm -f conftest.er1 conftest.err
+- $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+- test $ac_status = 0; }
+-done
++ const char *str;
++ int number;
++ float fnumber;
+
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+-$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+-if ${ac_cv_c_compiler_gnu+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ while (*format)
++ {
++ switch (*format++)
++ {
++ case 's': // string
++ str = va_arg (args_copy, const char *);
++ break;
++ case 'd': // int
++ number = va_arg (args_copy, int);
++ break;
++ case 'f': // float
++ fnumber = va_arg (args_copy, double);
++ break;
++ default:
++ break;
++ }
++ }
++ va_end (args_copy);
++ va_end (args);
++}
+
+ int
+ main ()
+ {
+-#ifndef __GNUC__
+- choke me
+-#endif
+
+- ;
+- return 0;
+-}
+-_ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
+- ac_compiler_gnu=yes
+-else
+- ac_compiler_gnu=no
+-fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+-ac_cv_c_compiler_gnu=$ac_compiler_gnu
++ // Check bool.
++ _Bool success = false;
+
+-fi
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+-$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+-if test $ac_compiler_gnu = yes; then
+- GCC=yes
+-else
+- GCC=
+-fi
+-ac_test_CFLAGS=${CFLAGS+set}
+-ac_save_CFLAGS=$CFLAGS
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+-$as_echo_n "checking whether $CC accepts -g... " >&6; }
+-if ${ac_cv_prog_cc_g+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- ac_save_c_werror_flag=$ac_c_werror_flag
+- ac_c_werror_flag=yes
+- ac_cv_prog_cc_g=no
+- CFLAGS="-g"
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ // Check restrict.
++ if (test_restrict ("String literal") == 0)
++ success = true;
++ char *restrict newvar = "Another string";
+
+-int
+-main ()
+-{
++ // Check varargs.
++ test_varargs ("s, d' f .", "string", 65, 34.234);
++ test_varargs_macros ();
+
+- ;
+- return 0;
+-}
+-_ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
+- ac_cv_prog_cc_g=yes
+-else
+- CFLAGS=""
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ // Check flexible array members.
++ struct incomplete_array *ia =
++ malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
++ ia->datasize = 10;
++ for (int i = 0; i < ia->datasize; ++i)
++ ia->data[i] = i * 1.234;
+
+-int
+-main ()
+-{
++ // Check named initializers.
++ struct named_init ni = {
++ .number = 34,
++ .name = L"Test wide string",
++ .average = 543.34343,
++ };
+
+- ;
+- return 0;
+-}
+-_ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
++ ni.number = 58;
+
+-else
+- ac_c_werror_flag=$ac_save_c_werror_flag
+- CFLAGS="-g"
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ int dynamic_array[ni.number];
++ dynamic_array[ni.number - 1] = 543;
+
+-int
+-main ()
+-{
++ // work around unused variable warnings
++ return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x'
++ || dynamic_array[ni.number - 1] != 543);
+
+ ;
+ return 0;
+ }
+ _ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
+- ac_cv_prog_cc_g=yes
+-fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+-fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
++for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99
++do
++ CC="$ac_save_CC $ac_arg"
++ if ac_fn_c_try_compile "$LINENO"; then :
++ ac_cv_prog_cc_c99=$ac_arg
+ fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+- ac_c_werror_flag=$ac_save_c_werror_flag
++rm -f core conftest.err conftest.$ac_objext
++ test "x$ac_cv_prog_cc_c99" != "xno" && break
++done
++rm -f conftest.$ac_ext
++CC=$ac_save_CC
++
+ fi
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+-$as_echo "$ac_cv_prog_cc_g" >&6; }
+-if test "$ac_test_CFLAGS" = set; then
+- CFLAGS=$ac_save_CFLAGS
+-elif test $ac_cv_prog_cc_g = yes; then
+- if test "$GCC" = yes; then
+- CFLAGS="-g -O2"
+- else
+- CFLAGS="-g"
+- fi
++# AC_CACHE_VAL
++case "x$ac_cv_prog_cc_c99" in
++ x)
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
++$as_echo "none needed" >&6; } ;;
++ xno)
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
++$as_echo "unsupported" >&6; } ;;
++ *)
++ CC="$CC $ac_cv_prog_cc_c99"
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
++$as_echo "$ac_cv_prog_cc_c99" >&6; } ;;
++esac
++if test "x$ac_cv_prog_cc_c99" != xno; then :
++ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99
+ else
+- if test "$GCC" = yes; then
+- CFLAGS="-O2"
+- else
+- CFLAGS=
+- fi
+-fi
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+ $as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+ if ${ac_cv_prog_cc_c89+:} false; then :
+ $as_echo_n "(cached) " >&6
+@@ -5133,19 +4872,36 @@
+ $as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+ esac
+ if test "x$ac_cv_prog_cc_c89" != xno; then :
++ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89
++else
++ ac_cv_prog_cc_stdc=no
++fi
+
++fi
++ ;;
++esac
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO Standard C" >&5
++$as_echo_n "checking for $CC option to accept ISO Standard C... " >&6; }
++ if ${ac_cv_prog_cc_stdc+:} false; then :
++ $as_echo_n "(cached) " >&6
+ fi
+
+-ac_ext=c
+-ac_cpp='$CPP $CPPFLAGS'
+-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+-ac_compiler_gnu=$ac_cv_c_compiler_gnu
++ case $ac_cv_prog_cc_stdc in #(
++ no) :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
++$as_echo "unsupported" >&6; } ;; #(
++ '') :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
++$as_echo "none needed" >&6; } ;; #(
++ *) :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_stdc" >&5
++$as_echo "$ac_cv_prog_cc_stdc" >&6; } ;;
++esac
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking $CC dependency flag" >&5
+ $as_echo_n "checking $CC dependency flag... " >&6; }
+-echo 'void f(){}' >conftest.c
++echo 'void f(void){}' >conftest.c
+ if test "`$CC -MM conftest.c 2>&1`" = "conftest.o: conftest.c"; then
+ DEPFLAG="-MM"
+ else
+@@ -5327,7 +5083,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -5348,7 +5104,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5418,7 +5174,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -5439,7 +5195,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5498,12 +5254,12 @@
+ echo '
+ #include <stdbool.h>
+ #include <ctype.h>
+-int test() {
++int test(void) {
+ int a = 0;
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5562,13 +5318,13 @@
+ echo '
+ #include <ctype.h>
+
+-int test() {
++int test(void) {
+ int a;
+ a = isascii(32);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5627,13 +5383,13 @@
+ echo '
+ #include <netinet/in.h>
+
+-int test() {
++int test(void) {
+ struct in6_pktinfo inf;
+ int a = (int)sizeof(inf);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5695,13 +5451,13 @@
+ echo '
+ #include <unistd.h>
+
+-int test() {
++int test(void) {
+ int a = setresgid(0,0,0);
+ a = setresuid(0,0,0);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5764,7 +5520,7 @@
+ #endif
+ #include <netdb.h>
+
+-int test() {
++int test(void) {
+ int a = 0;
+ char *t;
+ time_t time = 0;
+@@ -5777,7 +5533,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5846,7 +5602,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -5859,7 +5615,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -6610,6 +6366,11 @@
+ fi
+
+ fi
++if test "$LEX" = "" -o "$LEX" = ":"; then
++ if test ! -f util/configlexer.c; then
++ as_fn_error $? "no lex and no util/configlexer.c: need flex and bison to compile from source repository." "$LINENO" 5
++ fi
++fi
+ for ac_prog in 'bison -y' byacc
+ do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+@@ -6653,6 +6414,11 @@
+ done
+ test -n "$YACC" || YACC="yacc"
+
++if test "$YACC" = "" -o "$YACC" = ":"; then
++ if test ! -f util/configparser.c; then
++ as_fn_error $? "no yacc and no util/configparser.c: need flex and bison to compile from source repository." "$LINENO" 5
++ fi
++fi
+ # Extract the first word of "doxygen", so it can be a program name with args.
+ set dummy doxygen; ac_word=$2
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+@@ -14877,6 +14643,21 @@
+ done
+
+
++# Check for Linux timestamping headers
++for ac_header in linux/net_tstamp.h
++do :
++ ac_fn_c_check_header_compile "$LINENO" "linux/net_tstamp.h" "ac_cv_header_linux_net_tstamp_h" "$ac_includes_default
++"
++if test "x$ac_cv_header_linux_net_tstamp_h" = xyes; then :
++ cat >>confdefs.h <<_ACEOF
++#define HAVE_LINUX_NET_TSTAMP_H 1
++_ACEOF
++
++fi
++
++done
++
++
+ # check for types.
+ # Using own tests for int64* because autoconf builtin only give 32bit.
+ ac_fn_c_check_type "$LINENO" "int8_t" "ac_cv_type_int8_t" "$ac_includes_default"
+@@ -15954,12 +15735,12 @@
+
+ echo '
+ #include <stdio.h>
+-int test() {
++int test(void) {
+ int a = fseeko(stdin, 0, 0);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -16221,10 +16002,7 @@
+ $as_echo_n "checking whether strptime works... " >&6; }
+ if test c${cross_compiling} = cno; then
+ if test "$cross_compiling" = yes; then :
+- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "cannot run test program while cross compiling
+-See \`config.log' for more details" "$LINENO" 5; }
++ eval "ac_cv_c_strptime_works=maybe"
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ /* end confdefs.h. */
+@@ -17541,39 +17319,68 @@
+ PYTHON_VERSION=`$PYTHON -c "import sys; \
+ print(sys.version.split()[0])"`
+ fi
++ # calculate the version number components.
++
++ v="$PYTHON_VERSION"
++ PYTHON_VERSION_MAJOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MAJOR"; then PYTHON_VERSION_MAJOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_MINOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MINOR"; then PYTHON_VERSION_MINOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_PATCH=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_PATCH"; then PYTHON_VERSION_PATCH="0"; fi
++
++
++ # For some systems, sysconfig exists, but has the wrong paths,
++ # on Debian 10, for python 2.7 and 3.7. So, we check the version,
++ # and for older versions try distutils.sysconfig first. For newer
++ # versions>=3.10, where distutils.sysconfig is deprecated, use
++ # sysconfig first and then attempt the other one.
++ py_distutils_first="no"
++ if test $PYTHON_VERSION_MAJOR -lt 3; then
++ py_distutils_first="yes"
++ fi
++ if test $PYTHON_VERSION_MAJOR -eq 3 -a $PYTHON_VERSION_MINOR -lt 10; then
++ py_distutils_first="yes"
++ fi
+
+- # Check if you have sysconfig
+- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the sysconfig Python module" >&5
+-$as_echo_n "checking for the sysconfig Python module... " >&6; }
+- if ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`; then
++ # Check if you have the first module
++ if test "$py_distutils_first" = "yes"; then m="distutils"; else m="sysconfig"; fi
++ sysconfig_module=""
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the $m Python module" >&5
++$as_echo_n "checking for the $m Python module... " >&6; }
++ if ac_modulecheck_result1=`$PYTHON -c "import $m" 2>&1`; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+ $as_echo "yes" >&6; }
+- sysconfig_module="sysconfig"
+- # if yes, use sysconfig, because distutils is deprecated.
++ sysconfig_module="$m"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+ $as_echo "no" >&6; }
+- # if no, try to use distutils
+-
+- #
+- # Check if you have distutils, else fail
+- #
+- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the distutils Python package" >&5
+-$as_echo_n "checking for the distutils Python package... " >&6; }
+- if ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`; then
++ fi
++
++ # if not found, try the other one.
++ if test -z "$sysconfig_module"; then
++ if test "$py_distutils_first" = "yes"; then m2="sysconfig"; else m2="distutils"; fi
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the $m2 Python module" >&5
++$as_echo_n "checking for the $m2 Python module... " >&6; }
++ if ac_modulecheck_result2=`$PYTHON -c "import $m2" 2>&1`; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+ $as_echo "yes" >&6; }
++ sysconfig_module="$m2"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+ $as_echo "no" >&6; }
+- as_fn_error $? "cannot import Python module \"distutils\".
+- Please check your Python installation. The error was:
+- $ac_distutils_result" "$LINENO" 5
++ as_fn_error $? "cannot import Python module \"$m\", or \"$m2\".
++ Please check your Python installation. The errors are:
++ $m
++ $ac_modulecheck_result1
++ $m2
++ $ac_modulecheck_result2" "$LINENO" 5
+ PYTHON_VERSION=""
+ fi
+-
+- sysconfig_module="distutils.sysconfig"
+ fi
++ if test "$sysconfig_module" = "distutils"; then sysconfig_module="distutils.sysconfig"; fi
+
+ #
+ # Check for Python include path
+@@ -17705,7 +17512,14 @@
+ #
+
+ if test ! -z "$PYTHON_VERSION"; then
+- if test `$PYTHON -c "print('$PYTHON_VERSION' >= '2.4.0')"` = "False"; then
++ badversion="no"
++ if test "$PYTHON_VERSION_MAJOR" -lt 2; then
++ badversion="yes"
++ fi
++ if test "$PYTHON_VERSION_MAJOR" -eq 2 -a "$PYTHON_VERSION_MINOR" -lt 4; then
++ badversion="yes"
++ fi
++ if test "$badversion" = "yes"; then
+ as_fn_error $? "Python version >= 2.4.0 is required" "$LINENO" 5
+ fi
+
+@@ -18977,10 +18791,7 @@
+ CFLAGS="$CFLAGS -Wl,-rpath,$ssldir_lib"
+ fi
+ if test "$cross_compiling" = yes; then :
+- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "cannot run test program while cross compiling
+-See \`config.log' for more details" "$LINENO" 5; }
++ eval "ac_cv_c_gost_works=maybe"
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ /* end confdefs.h. */
+@@ -20174,7 +19985,7 @@
+ #ifdef __cplusplus
+ }
+ #endif
+-int main() {
++int main(void) {
+ ;
+ return 0;
+ }
+@@ -20448,7 +20259,7 @@
+ #include <stdlib.h>
+ #include <unistd.h>
+ ' >conftest.c
+-echo 'void f(){ (void)daemon(0, 0); }' >>conftest.c
++echo 'void f(void){ (void)daemon(0, 0); }' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS -c conftest.c 2>&1 | grep -e deprecated -e unavailable`"; then
+ eval "cv_cc_deprecated_$cache=no"
+ else
+@@ -20854,10 +20665,8 @@
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for correct snprintf return value" >&5
+ $as_echo_n "checking for correct snprintf return value... " >&6; }
+ if test "$cross_compiling" = yes; then :
+- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "cannot run test program while cross compiling
+-See \`config.log' for more details" "$LINENO" 5; }
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: maybe" >&5
++$as_echo "maybe" >&6; }
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ /* end confdefs.h. */
+@@ -21532,7 +21341,7 @@
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+ else
+- as_fn_error $? "The protobuf-c library was not found. Please install protobuf-c!" "$LINENO" 5
++ as_fn_error $? "The protobuf-c library was not found. Please install the development libraries for protobuf-c!" "$LINENO" 5
+ fi
+
+
+@@ -22086,7 +21895,7 @@
+
+
+
+-version=1.17.1
++version=1.19.1
+
+ date=`date +'%b %e, %Y'`
+
+@@ -22605,7 +22414,7 @@
+ # report actual input values of CONFIG_FILES etc. instead of their
+ # values after options handling.
+ ac_log="
+-This file was extended by unbound $as_me 1.17.1, which was
++This file was extended by unbound $as_me 1.19.1, which was
+ generated by GNU Autoconf 2.69. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+@@ -22671,7 +22480,7 @@
+ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ ac_cs_version="\\
+-unbound config.status 1.17.1
++unbound config.status 1.19.1
+ configured by $0, generated by GNU Autoconf 2.69,
+ with options \\"\$ac_cs_config\\"
+
+--- contrib/unbound/configure.ac.orig
++++ contrib/unbound/configure.ac
+@@ -10,7 +10,7 @@
+
+ # must be numbers. ac_defun because of later processing
+ m4_define([VERSION_MAJOR],[1])
+-m4_define([VERSION_MINOR],[17])
++m4_define([VERSION_MINOR],[19])
+ m4_define([VERSION_MICRO],[1])
+ AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound])
+ AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR])
+@@ -18,7 +18,7 @@
+ AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO])
+
+ LIBUNBOUND_CURRENT=9
+-LIBUNBOUND_REVISION=21
++LIBUNBOUND_REVISION=24
+ LIBUNBOUND_AGE=1
+ # 1.0.0 had 0:12:0
+ # 1.0.1 had 0:13:0
+@@ -107,6 +107,9 @@
+ # 1.16.3 had 9:19:1
+ # 1.17.0 had 9:20:1
+ # 1.17.1 had 9:21:1
++# 1.18.0 had 9:22:1
++# 1.19.0 had 9:23:1
++# 1.19.1 had 9:24:1
+
+ # Current -- the number of the binary API that we're implementing
+ # Revision -- which iteration of the implementation of the binary
+@@ -156,7 +159,7 @@
+
+ # are we on MinGW?
+ if uname -s 2>&1 | grep MINGW >/dev/null; then on_mingw="yes"
+-else
++else
+ if echo $host | grep mingw >/dev/null; then on_mingw="yes"
+ else on_mingw="no"; fi
+ fi
+@@ -185,9 +188,9 @@
+ AC_SUBST(ub_conf_dir)
+
+ # Determine run, chroot directory and pidfile locations
+-AC_ARG_WITH(run-dir,
+- AS_HELP_STRING([--with-run-dir=path],[set default directory to chdir to (by default dir part of cfg file)]),
+- UNBOUND_RUN_DIR="$withval",
++AC_ARG_WITH(run-dir,
++ AS_HELP_STRING([--with-run-dir=path],[set default directory to chdir to (by default dir part of cfg file)]),
++ UNBOUND_RUN_DIR="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_RUN_DIR=`dirname "$ub_conf_file"`
+ else
+@@ -198,9 +201,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_RUN_DIR, hdr_run)
+ AC_DEFINE_UNQUOTED(RUN_DIR, ["$hdr_run"], [Directory to chdir to])
+
+-AC_ARG_WITH(chroot-dir,
+- AS_HELP_STRING([--with-chroot-dir=path],[set default directory to chroot to (by default same as run-dir)]),
+- UNBOUND_CHROOT_DIR="$withval",
++AC_ARG_WITH(chroot-dir,
++ AS_HELP_STRING([--with-chroot-dir=path],[set default directory to chroot to (by default same as run-dir)]),
++ UNBOUND_CHROOT_DIR="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_CHROOT_DIR="$UNBOUND_RUN_DIR"
+ else
+@@ -218,9 +221,9 @@
+ AC_SUBST(UNBOUND_SHARE_DIR)
+ AC_DEFINE_UNQUOTED(SHARE_DIR, ["$UNBOUND_SHARE_DIR"], [Shared data])
+
+-AC_ARG_WITH(pidfile,
+- AS_HELP_STRING([--with-pidfile=filename],[set default pathname to unbound pidfile (default run-dir/unbound.pid)]),
+- UNBOUND_PIDFILE="$withval",
++AC_ARG_WITH(pidfile,
++ AS_HELP_STRING([--with-pidfile=filename],[set default pathname to unbound pidfile (default run-dir/unbound.pid)]),
++ UNBOUND_PIDFILE="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_PIDFILE="$UNBOUND_RUN_DIR/unbound.pid"
+ else
+@@ -231,9 +234,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_PIDFILE, hdr_pid)
+ AC_DEFINE_UNQUOTED(PIDFILE, ["$hdr_pid"], [default pidfile location])
+
+-AC_ARG_WITH(rootkey-file,
+- AS_HELP_STRING([--with-rootkey-file=filename],[set default pathname to root key file (default run-dir/root.key). This file is read and written.]),
+- UNBOUND_ROOTKEY_FILE="$withval",
++AC_ARG_WITH(rootkey-file,
++ AS_HELP_STRING([--with-rootkey-file=filename],[set default pathname to root key file (default run-dir/root.key). This file is read and written.]),
++ UNBOUND_ROOTKEY_FILE="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_ROOTKEY_FILE="$UNBOUND_RUN_DIR/root.key"
+ else
+@@ -244,9 +247,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_ROOTKEY_FILE, hdr_rkey)
+ AC_DEFINE_UNQUOTED(ROOT_ANCHOR_FILE, ["$hdr_rkey"], [default rootkey location])
+
+-AC_ARG_WITH(rootcert-file,
+- AS_HELP_STRING([--with-rootcert-file=filename],[set default pathname to root update certificate file (default run-dir/icannbundle.pem). This file need not exist if you are content with the builtin.]),
+- UNBOUND_ROOTCERT_FILE="$withval",
++AC_ARG_WITH(rootcert-file,
++ AS_HELP_STRING([--with-rootcert-file=filename],[set default pathname to root update certificate file (default run-dir/icannbundle.pem). This file need not exist if you are content with the builtin.]),
++ UNBOUND_ROOTCERT_FILE="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_ROOTCERT_FILE="$UNBOUND_RUN_DIR/icannbundle.pem"
+ else
+@@ -257,9 +260,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_ROOTCERT_FILE, hdr_rpem)
+ AC_DEFINE_UNQUOTED(ROOT_CERT_FILE, ["$hdr_rpem"], [default rootcert location])
+
+-AC_ARG_WITH(username,
+- AS_HELP_STRING([--with-username=user],[set default user that unbound changes to (default user is unbound)]),
+- UNBOUND_USERNAME="$withval",
++AC_ARG_WITH(username,
++ AS_HELP_STRING([--with-username=user],[set default user that unbound changes to (default user is unbound)]),
++ UNBOUND_USERNAME="$withval",
+ UNBOUND_USERNAME="unbound")
+ AC_SUBST(UNBOUND_USERNAME)
+ AC_DEFINE_UNQUOTED(UB_USERNAME, ["$UNBOUND_USERNAME"], [default username])
+@@ -278,14 +281,14 @@
+ ACX_CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"])
+ default_cflags=yes
+ fi
+-AC_PROG_CC
++m4_version_prereq([2.70], [AC_PROG_CC], [AC_PROG_CC_STDC])
+ ACX_DEPFLAG
+ ACX_DETERMINE_EXT_FLAGS_UNBOUND
+
+ # debug mode flags warnings
+ AC_ARG_ENABLE(checking, AS_HELP_STRING([--enable-checking],[Enable warnings, asserts, makefile-dependencies]))
+ AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[same as enable-checking]))
+-if test "$enable_debug" = "yes"; then debug_enabled="$enable_debug";
++if test "$enable_debug" = "yes"; then debug_enabled="$enable_debug";
+ else debug_enabled="$enable_checking"; fi
+ AC_SUBST(debug_enabled)
+ case "$debug_enabled" in
+@@ -388,7 +391,17 @@
+ if test "$LEX" != "" -a "$LEX" != ":"; then
+ ACX_YYLEX_OPTION
+ fi
++if test "$LEX" = "" -o "$LEX" = ":"; then
++ if test ! -f util/configlexer.c; then
++ AC_MSG_ERROR([no lex and no util/configlexer.c: need flex and bison to compile from source repository.])
++ fi
++fi
+ AC_PROG_YACC
++if test "$YACC" = "" -o "$YACC" = ":"; then
++ if test ! -f util/configparser.c; then
++ AC_MSG_ERROR([no yacc and no util/configparser.c: need flex and bison to compile from source repository.])
++ fi
++fi
+ AC_CHECK_PROG(doxygen, doxygen, doxygen)
+ AC_CHECK_TOOL(STRIP, strip)
+ ACX_LIBTOOL_C_ONLY
+@@ -453,6 +466,9 @@
+ #endif
+ ])
+
++# Check for Linux timestamping headers
++AC_CHECK_HEADERS([linux/net_tstamp.h],,, [AC_INCLUDES_DEFAULT])
++
+ # check for types.
+ # Using own tests for int64* because autoconf builtin only give 32bit.
+ AC_CHECK_TYPE(int8_t, signed char)
+@@ -511,7 +527,8 @@
+ if (!res) return 2;
+ res = strptime("20070207111842", "%Y%m%d%H%M%S", &tm);
+ if (!res) return 1; return 0; }
+-]])] , [eval "ac_cv_c_strptime_works=yes"], [eval "ac_cv_c_strptime_works=no"])
++]])] , [eval "ac_cv_c_strptime_works=yes"], [eval "ac_cv_c_strptime_works=no"],
++[eval "ac_cv_c_strptime_works=maybe"])
+ else
+ eval "ac_cv_c_strptime_works=maybe"
+ fi
+@@ -548,11 +565,11 @@
+ # Include systemd.m4 - end
+
+ # set memory allocation checking if requested
+-AC_ARG_ENABLE(alloc-checks, AS_HELP_STRING([--enable-alloc-checks],[ enable to memory allocation statistics, for debug purposes ]),
++AC_ARG_ENABLE(alloc-checks, AS_HELP_STRING([--enable-alloc-checks],[ enable to memory allocation statistics, for debug purposes ]),
+ , )
+-AC_ARG_ENABLE(alloc-lite, AS_HELP_STRING([--enable-alloc-lite],[ enable for lightweight alloc assertions, for debug purposes ]),
++AC_ARG_ENABLE(alloc-lite, AS_HELP_STRING([--enable-alloc-lite],[ enable for lightweight alloc assertions, for debug purposes ]),
+ , )
+-AC_ARG_ENABLE(alloc-nonregional, AS_HELP_STRING([--enable-alloc-nonregional],[ enable nonregional allocs, slow but exposes regional allocations to other memory purifiers, for debug purposes ]),
++AC_ARG_ENABLE(alloc-nonregional, AS_HELP_STRING([--enable-alloc-nonregional],[ enable nonregional allocs, slow but exposes regional allocations to other memory purifiers, for debug purposes ]),
+ , )
+ if test x_$enable_alloc_nonregional = x_yes; then
+ AC_DEFINE(UNBOUND_ALLOC_NONREGIONAL, 1, [use malloc not regions, for debug use])
+@@ -585,7 +602,7 @@
+ ])],
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_WINDOWS_THREADS, 1, [Using Windows threads])
+-,
++,
+ AC_MSG_RESULT(no)
+ )
+
+@@ -596,7 +613,7 @@
+ # check this first, so that the pthread lib does not get linked in via
+ # libssl or libpython, and thus distorts the tests, and we end up using
+ # the non-threadsafe C libraries.
+-AC_ARG_WITH(pthreads, AS_HELP_STRING([--with-pthreads],[use pthreads library, or --without-pthreads to disable threading support.]),
++AC_ARG_WITH(pthreads, AS_HELP_STRING([--with-pthreads],[use pthreads library, or --without-pthreads to disable threading support.]),
+ [ ],[ withval="yes" ])
+ ub_have_pthreads=no
+ if test x_$withval != x_no; then
+@@ -623,7 +640,7 @@
+ # first compile
+ echo "$CC $CFLAGS -c conftest.c -o conftest.o" >&AS_MESSAGE_LOG_FD
+ $CC $CFLAGS -c conftest.c -o conftest.o 2>&AS_MESSAGE_LOG_FD >&AS_MESSAGE_LOG_FD
+- if test $? = 0; then
++ if test $? = 0; then
+ # then link
+ echo "$CC $CFLAGS -Werror $LDFLAGS $LIBS -o conftest contest.o" >&AS_MESSAGE_LOG_FD
+ $CC $CFLAGS -Werror $LDFLAGS $LIBS -o conftest conftest.o 2>&AS_MESSAGE_LOG_FD >&AS_MESSAGE_LOG_FD
+@@ -644,7 +661,7 @@
+ ])
+ fi
+
+-# check solaris thread library
++# check solaris thread library
+ AC_ARG_WITH(solaris-threads, AS_HELP_STRING([--with-solaris-threads],[use solaris native thread library.]), [ ],[ withval="no" ])
+ ub_have_sol_threads=no
+ if test x_$withval != x_no; then
+@@ -658,8 +675,8 @@
+ ACX_CHECK_COMPILER_FLAG(mt, [CFLAGS="$CFLAGS -mt"],
+ [CFLAGS="$CFLAGS -D_REENTRANT"])
+ ub_have_sol_threads=yes
+- ] , [
+- AC_MSG_ERROR([no solaris threads found.])
++ ] , [
++ AC_MSG_ERROR([no solaris threads found.])
+ ])
+ fi
+ fi
+@@ -734,7 +751,14 @@
+ ac_save_LIBS="$LIBS" dnl otherwise AC_PYTHON_DEVEL thrashes $LIBS
+ AC_PYTHON_DEVEL
+ if test ! -z "$PYTHON_VERSION"; then
+- if test `$PYTHON -c "print('$PYTHON_VERSION' >= '2.4.0')"` = "False"; then
++ badversion="no"
++ if test "$PYTHON_VERSION_MAJOR" -lt 2; then
++ badversion="yes"
++ fi
++ if test "$PYTHON_VERSION_MAJOR" -eq 2 -a "$PYTHON_VERSION_MINOR" -lt 4; then
++ badversion="yes"
++ fi
++ if test "$badversion" = "yes"; then
+ AC_MSG_ERROR([Python version >= 2.4.0 is required])
+ fi
+
+@@ -1085,7 +1109,7 @@
+ EVP_PKEY_asn1_get0_info(&gost_id, NULL, NULL, NULL, NULL, meth);
+ return gost_id;
+ }
+-int main(void) {
++int main(void) {
+ EVP_MD_CTX* ctx;
+ const EVP_MD* md;
+ unsigned char digest[64]; /* its a 256-bit digest, so uses 32 bytes */
+@@ -1116,7 +1140,8 @@
+ return 6;
+ return 0;
+ }
+-]])] , [eval "ac_cv_c_gost_works=yes"], [eval "ac_cv_c_gost_works=no"])
++]])] , [eval "ac_cv_c_gost_works=yes"], [eval "ac_cv_c_gost_works=no"],
++[eval "ac_cv_c_gost_works=maybe"])
+ CFLAGS="$BAKCFLAGS"
+ else
+ eval "ac_cv_c_gost_works=maybe"
+@@ -1529,7 +1554,7 @@
+ fi
+
+ # set lock checking if requested
+-AC_ARG_ENABLE(lock_checks, AS_HELP_STRING([--enable-lock-checks],[ enable to check lock and unlock calls, for debug purposes ]),
++AC_ARG_ENABLE(lock_checks, AS_HELP_STRING([--enable-lock-checks],[ enable to check lock and unlock calls, for debug purposes ]),
+ , )
+ if test x_$enable_lock_checks = x_yes; then
+ AC_DEFINE(ENABLE_LOCK_CHECKS, 1, [Define if you want to use debug lock checking (slow).])
+@@ -1693,7 +1718,7 @@
+ AC_MSG_RESULT(no)
+ AC_DEFINE([SNPRINTF_RET_BROKEN], [], [define if (v)snprintf does not return length needed, (but length used)])
+ AC_LIBOBJ(snprintf)
+- ])
++ ], [AC_MSG_RESULT(maybe)])
+ fi
+ fi
+ AC_REPLACE_FUNCS(strlcat)
+@@ -1923,7 +1948,7 @@
+ esac
+
+ if echo "$host" | $GREP -i -e linux >/dev/null; then
+- AC_ARG_ENABLE(linux-ip-local-port-range, AC_HELP_STRING([--enable-linux-ip-local-port-range], [Define this to enable use of /proc/sys/net/ipv4/ip_local_port_range as a default outgoing port range. This is only for the libunbound on Linux and does not affect unbound resolving daemon itself. This may severely limit the number of available outgoing ports and thus decrease randomness. Define this only when the target system restricts (e.g. some of SELinux enabled distributions) the use of non-ephemeral ports.]))
++ AC_ARG_ENABLE(linux-ip-local-port-range, AS_HELP_STRING([--enable-linux-ip-local-port-range], [Define this to enable use of /proc/sys/net/ipv4/ip_local_port_range as a default outgoing port range. This is only for the libunbound on Linux and does not affect unbound resolving daemon itself. This may severely limit the number of available outgoing ports and thus decrease randomness. Define this only when the target system restricts (e.g. some of SELinux enabled distributions) the use of non-ephemeral ports.]))
+ case "$enable_linux_ip_local_port_range" in
+ yes)
+ AC_DEFINE([USE_LINUX_IP_LOCAL_PORT_RANGE], [1], [Define this to enable use of /proc/sys/net/ipv4/ip_local_port_range as a default outgoing port range. This is only for the libunbound on Linux and does not affect unbound resolving daemon itself. This may severely limit the number of available outgoing ports and thus decrease randomness. Define this only when the target system restricts (e.g. some of SELinux enabled distributions) the use of non-ephemeral ports.])
+@@ -1980,11 +2005,11 @@
+ fi
+ ])
+ if test $ALLTARGET = "alltargets"; then
+- if test $USE_NSS = "yes"; then
+- AC_MSG_ERROR([--with-nss can only be used in combination with --with-libunbound-only.])
++ if test $USE_NSS = "yes"; then
++ AC_MSG_ERROR([--with-nss can only be used in combination with --with-libunbound-only.])
+ fi
+ if test $USE_NETTLE = "yes"; then
+- AC_MSG_ERROR([--with-nettle can only be used in combination with --with-libunbound-only.])
++ AC_MSG_ERROR([--with-nettle can only be used in combination with --with-libunbound-only.])
+ fi
+ fi
+
+@@ -1995,7 +2020,7 @@
+ if test -n "$LATE_LDFLAGS"; then
+ LDFLAGS="$LATE_LDFLAGS $LDFLAGS"
+ fi
+-# remove start spaces
++# remove start spaces
+ LDFLAGS=`echo "$LDFLAGS"|sed -e 's/^ *//'`
+ LIBS=`echo "$LIBS"|sed -e 's/^ *//'`
+
+--- contrib/unbound/contrib/Dockerfile.tests.orig
++++ contrib/unbound/contrib/Dockerfile.tests
+@@ -1,10 +1,8 @@
+ FROM gcc:latest
+ WORKDIR /usr/src/unbound
+-RUN apt-get update
+ # install semantic parser & lexical analyzer
+-RUN apt-get install -y bison flex
+ # install packages used in tests
+-RUN apt-get install -y ldnsutils dnsutils xxd splint doxygen netcat
++RUN apt-get update && apt-get install -y bison flex ldnsutils dnsutils xxd splint doxygen netcat-openbsd
+ # accept short rsa keys, which are used in tests
+ RUN sed -i 's/SECLEVEL=2/SECLEVEL=1/g' /usr/lib/ssl/openssl.cnf
+
+--- contrib/unbound/contrib/README.orig
++++ contrib/unbound/contrib/README
+@@ -55,3 +55,6 @@
+ contributed by Andreas Schulze.
+ * metrics.awk: awk script that can convert unbound-control stats to
+ Prometheus metrics format output.
++* unbound.init_yocto: An init script to start and stop the server. Put it
++ in /etc/init.d/unbound to use it. It is for the Yocto Project, in
++ embedded systems, contributed by beni-sandu.
+--- contrib/unbound/contrib/aaaa-filter-iterator.patch.orig
++++ contrib/unbound/contrib/aaaa-filter-iterator.patch
+@@ -105,9 +105,9 @@
+ --- a/iterator/iter_utils.c
+ +++ b/iterator/iter_utils.c
+ @@ -177,6 +177,7 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+- iter_env->supports_ipv6 = cfg->do_ip6;
+- iter_env->supports_ipv4 = cfg->do_ip4;
+ iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
++ iter_env->max_sent_count = cfg->max_sent_count;
++ iter_env->max_query_restarts = cfg->max_query_restarts;
+ + iter_env->aaaa_filter = cfg->aaaa_filter;
+ return 1;
+ }
+--- /dev/null
++++ contrib/unbound/contrib/unbound.init_yocto
+@@ -0,0 +1,139 @@
++#!/bin/sh
++#
++# unbound This shell script takes care of starting and stopping
++# unbound (DNS server).
++#
++# chkconfig: - 14 86
++# description: unbound is a Domain Name Server (DNS) \
++# that is used to resolve host names to IP addresses.
++
++### BEGIN INIT INFO
++# Provides: $named unbound
++# Required-Start: $network $local_fs
++# Required-Stop: $network $local_fs
++# Should-Start: $syslog
++# Should-Stop: $syslog
++# Short-Description: unbound recursive Domain Name Server.
++# Description: unbound is a Domain Name Server (DNS)
++# that is used to resolve host names to IP addresses.
++### END INIT INFO
++
++# Source function library.
++. /etc/init.d/functions
++
++exec="/usr/sbin/unbound"
++prog="unbound"
++config="/etc/unbound/unbound.conf"
++pidfile="/var/unbound/unbound.pid"
++rootdir="/var/unbound"
++
++[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
++
++lockfile=/var/lock/subsys/$prog
++
++start() {
++ [ -x $exec ] || exit 5
++ [ -f $config ] || exit 6
++ echo -n $"Starting $prog: "
++
++ # setup root jail
++ if [ -s /etc/localtime ]; then
++ [ -d ${rootdir}/etc ] || mkdir -p ${rootdir}/etc ;
++ if [ ! -e ${rootdir}/etc/localtime ] || ! /usr/bin/cmp -s /etc/localtime ${rootdir}/etc/localtime; then
++ cp -fp /etc/localtime ${rootdir}/etc/localtime
++ fi;
++ fi;
++ if [ -s /etc/resolv.conf ]; then
++ [ -d ${rootdir}/etc ] || mkdir -p ${rootdir}/etc ;
++ if [ ! -e ${rootdir}/etc/resolv.conf ] || ! /usr/bin/cmp -s /etc/resolv.conf ${rootdir}/etc/resolv.conf; then
++ cp -fp /etc/resolv.conf ${rootdir}/etc/resolv.conf
++ fi;
++ fi;
++ if ! egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/log' /proc/mounts; then
++ [ -d ${rootdir}/dev ] || mkdir -p ${rootdir}/dev ;
++ [ -e ${rootdir}/dev/log ] || touch ${rootdir}/dev/log
++ mount --bind -n /dev/log ${rootdir}/dev/log >/dev/null 2>&1;
++ fi;
++ if ! egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/random' /proc/mounts; then
++ [ -d ${rootdir}/dev ] || mkdir -p ${rootdir}/dev ;
++ [ -e ${rootdir}/dev/random ] || touch ${rootdir}/dev/random
++ mount --bind -n /dev/random ${rootdir}/dev/random >/dev/null 2>&1;
++ fi;
++
++ # if not running, start it up here
++ daemonize $exec
++ retval=$?
++ echo
++ [ $retval -eq 0 ] && touch $lockfile
++ return $retval
++}
++
++stop() {
++ echo -n $"Stopping $prog: "
++ # stop it here, often "killproc $prog"
++ killproc $prog
++ retval=$?
++ echo
++ [ $retval -eq 0 ] && rm -f $lockfile
++ if egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/log' /proc/mounts; then
++ umount ${rootdir}/dev/log >/dev/null 2>&1
++ fi;
++ if egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/random' /proc/mounts; then
++ umount ${rootdir}/dev/random >/dev/null 2>&1
++ fi;
++ return $retval
++}
++
++restart() {
++ stop
++ start
++}
++
++reload() {
++ kill -HUP `cat $pidfile`
++}
++
++force_reload() {
++ restart
++}
++
++rh_status() {
++ # run checks to determine if the service is running or use generic status
++ status $prog
++}
++
++rh_status_q() {
++ rh_status -p $pidfile >/dev/null 2>&1
++}
++
++case "$1" in
++ start)
++ rh_status_q && exit 0
++ $1
++ ;;
++ stop)
++ rh_status_q || exit 0
++ $1
++ ;;
++ restart)
++ $1
++ ;;
++ reload)
++ rh_status_q || exit 7
++ $1
++ ;;
++ force-reload)
++ force_reload
++ ;;
++ status)
++ rh_status
++ ;;
++ condrestart|try-restart)
++ rh_status_q || exit 0
++ restart
++ ;;
++ *)
++ echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
++ exit 2
++esac
++exit $?
+--- contrib/unbound/daemon/acl_list.c.orig
++++ contrib/unbound/daemon/acl_list.c
+@@ -109,6 +109,8 @@
+ *control = acl_allow_snoop;
+ else if(strcmp(str, "allow_setrd") == 0)
+ *control = acl_allow_setrd;
++ else if (strcmp(str, "allow_cookie") == 0)
++ *control = acl_allow_cookie;
+ else {
+ log_err("access control type %s unknown", str);
+ return 0;
+--- contrib/unbound/daemon/acl_list.h.orig
++++ contrib/unbound/daemon/acl_list.h
+@@ -64,8 +64,12 @@
+ acl_allow,
+ /** allow full access for all queries, recursion and cache snooping */
+ acl_allow_snoop,
+- /** allow full access for recursion queries and set RD flag regardless of request */
+- acl_allow_setrd
++ /** allow full access for recursion queries and set RD flag regardless
++ * of request */
++ acl_allow_setrd,
++ /** allow full access for recursion (+RD) queries if valid cookie
++ * present or stateful transport */
++ acl_allow_cookie
+ };
+
+ /**
+--- contrib/unbound/daemon/cachedump.c.orig
++++ contrib/unbound/daemon/cachedump.c
+@@ -166,8 +166,7 @@
+
+ /** dump message entry */
+ static int
+-dump_msg(RES* ssl, struct query_info* k, struct reply_info* d,
+- time_t now)
++dump_msg(RES* ssl, struct query_info* k, struct reply_info* d, time_t now)
+ {
+ size_t i;
+ char* nm, *tp, *cl;
+@@ -192,13 +191,15 @@
+ }
+
+ /* meta line */
+- if(!ssl_printf(ssl, "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u\n",
++ if(!ssl_printf(ssl, "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n",
+ nm, cl, tp,
+ (int)d->flags, (int)d->qdcount,
+ (long long)(d->ttl-now), (int)d->security,
+- (unsigned)d->an_numrrsets,
++ (unsigned)d->an_numrrsets,
+ (unsigned)d->ns_numrrsets,
+- (unsigned)d->ar_numrrsets)) {
++ (unsigned)d->ar_numrrsets,
++ (int)d->reason_bogus,
++ d->reason_bogus_str?d->reason_bogus_str:"")) {
+ free(nm);
+ free(tp);
+ free(cl);
+@@ -633,6 +634,9 @@
+ long long ttl;
+ size_t i;
+ int go_on = 1;
++ int ede;
++ int consumed = 0;
++ char* ede_str = NULL;
+
+ regional_free_all(region);
+
+@@ -647,11 +651,16 @@
+ }
+
+ /* read remainder of line */
+- if(sscanf(s, " %u %u " ARG_LL "d %u %u %u %u", &flags, &qdcount, &ttl,
+- &security, &an, &ns, &ar) != 7) {
++ /* note the last space before any possible EDE text */
++ if(sscanf(s, " %u %u " ARG_LL "d %u %u %u %u %d %n", &flags, &qdcount, &ttl,
++ &security, &an, &ns, &ar, &ede, &consumed) != 8) {
+ log_warn("error cannot parse numbers: %s", s);
+ return 0;
+ }
++ /* there may be EDE text after the numbers */
++ if(consumed > 0 && (size_t)consumed < strlen(s))
++ ede_str = s + consumed;
++ memset(&rep, 0, sizeof(rep));
+ rep.flags = (uint16_t)flags;
+ rep.qdcount = (uint16_t)qdcount;
+ rep.ttl = (time_t)ttl;
+@@ -666,6 +675,8 @@
+ rep.ns_numrrsets = (size_t)ns;
+ rep.ar_numrrsets = (size_t)ar;
+ rep.rrset_count = (size_t)an+(size_t)ns+(size_t)ar;
++ rep.reason_bogus = (sldns_ede_code)ede;
++ rep.reason_bogus_str = ede_str?(char*)regional_strdup(region, ede_str):NULL;
+ rep.rrsets = (struct ub_packed_rrset_key**)regional_alloc_zero(
+ region, sizeof(struct ub_packed_rrset_key*)*rep.rrset_count);
+
+@@ -860,7 +871,8 @@
+ /* go up? */
+ if(iter_dp_is_useless(&qinfo, BIT_RD, dp,
+ (worker->env.cfg->do_ip4 && worker->back->num_ip4 != 0),
+- (worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0))) {
++ (worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0),
++ worker->env.cfg->do_nat64)) {
+ print_dp_main(ssl, dp, msg);
+ print_dp_details(ssl, worker, dp);
+ if(!ssl_printf(ssl, "cache delegation was "
+--- contrib/unbound/daemon/remote.c.orig
++++ contrib/unbound/daemon/remote.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -38,7 +38,7 @@
+ *
+ * This file contains the remote control functionality for the daemon.
+ * The remote control can be performed using either the commandline
+- * unbound-control tool, or a TLS capable web browser.
++ * unbound-control tool, or a TLS capable web browser.
+ * The channel is secured using TLSv1, and certificates.
+ * Both the server and the client(control tool) have their own keys.
+ */
+@@ -87,6 +87,7 @@
+ #include "sldns/parseutil.h"
+ #include "sldns/wire2str.h"
+ #include "sldns/sbuffer.h"
++#include "util/timeval_func.h"
+
+ #ifdef HAVE_SYS_TYPES_H
+ # include <sys/types.h>
+@@ -106,47 +107,6 @@
+ /** what to put on statistics lines between var and value, ": " or "=" */
+ #define SQ "="
+
+-/** subtract timers and the values do not overflow or become negative */
+-static void
+-timeval_subtract(struct timeval* d, const struct timeval* end,
+- const struct timeval* start)
+-{
+-#ifndef S_SPLINT_S
+- time_t end_usec = end->tv_usec;
+- d->tv_sec = end->tv_sec - start->tv_sec;
+- if(end_usec < start->tv_usec) {
+- end_usec += 1000000;
+- d->tv_sec--;
+- }
+- d->tv_usec = end_usec - start->tv_usec;
+-#endif
+-}
+-
+-/** divide sum of timers to get average */
+-static void
+-timeval_divide(struct timeval* avg, const struct timeval* sum, long long d)
+-{
+-#ifndef S_SPLINT_S
+- size_t leftover;
+- if(d <= 0) {
+- avg->tv_sec = 0;
+- avg->tv_usec = 0;
+- return;
+- }
+- avg->tv_sec = sum->tv_sec / d;
+- avg->tv_usec = sum->tv_usec / d;
+- /* handle fraction from seconds divide */
+- leftover = sum->tv_sec - avg->tv_sec*d;
+- if(leftover <= 0)
+- leftover = 0;
+- avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
+- if(avg->tv_sec < 0)
+- avg->tv_sec = 0;
+- if(avg->tv_usec < 0)
+- avg->tv_usec = 0;
+-#endif
+-}
+-
+ static int
+ remote_setup_ctx(struct daemon_remote* rc, struct config_file* cfg)
+ {
+@@ -201,7 +161,7 @@
+ struct daemon_remote*
+ daemon_remote_create(struct config_file* cfg)
+ {
+- struct daemon_remote* rc = (struct daemon_remote*)calloc(1,
++ struct daemon_remote* rc = (struct daemon_remote*)calloc(1,
+ sizeof(*rc));
+ if(!rc) {
+ log_err("out of memory in daemon_remote_create");
+@@ -410,7 +370,7 @@
+ n->next = rc->accept_list;
+ rc->accept_list = n;
+ /* open commpt */
+- n->com = comm_point_create_raw(rc->worker->base, fd, 0,
++ n->com = comm_point_create_raw(rc->worker->base, fd, 0,
+ &remote_accept_callback, rc);
+ if(!n->com)
+ return 0;
+@@ -419,7 +379,7 @@
+ return 1;
+ }
+
+-int daemon_remote_open_accept(struct daemon_remote* rc,
++int daemon_remote_open_accept(struct daemon_remote* rc,
+ struct listen_port* ports, struct worker* worker)
+ {
+ struct listen_port* p;
+@@ -437,7 +397,7 @@
+ {
+ struct listen_list* p;
+ for(p=rc->accept_list; p; p=p->next) {
+- comm_point_stop_listening(p->com);
++ comm_point_stop_listening(p->com);
+ }
+ }
+
+@@ -445,11 +405,11 @@
+ {
+ struct listen_list* p;
+ for(p=rc->accept_list; p; p=p->next) {
+- comm_point_start_listening(p->com, -1, -1);
++ comm_point_start_listening(p->com, -1, -1);
+ }
+ }
+
+-int remote_accept_callback(struct comm_point* c, void* arg, int err,
++int remote_accept_callback(struct comm_point* c, void* arg, int err,
+ struct comm_reply* ATTR_UNUSED(rep))
+ {
+ struct daemon_remote* rc = (struct daemon_remote*)arg;
+@@ -481,7 +441,7 @@
+ }
+ n->fd = newfd;
+ /* start in reading state */
+- n->c = comm_point_create_raw(rc->worker->base, newfd, 0,
++ n->c = comm_point_create_raw(rc->worker->base, newfd, 0,
+ &remote_control_callback, n);
+ if(!n->c) {
+ log_err("out of memory");
+@@ -521,7 +481,7 @@
+ rc->busy_list = n;
+ rc->active ++;
+
+- /* perform the first nonblocking read already, for windows,
++ /* perform the first nonblocking read already, for windows,
+ * so it can return wouldblock. could be faster too. */
+ (void)remote_control_callback(n->c, n, NETEVENT_NOERROR, NULL);
+ return 0;
+@@ -558,17 +518,18 @@
+ ssl_print_text(RES* res, const char* text)
+ {
+ int r;
+- if(!res)
++ if(!res)
+ return 0;
+ if(res->ssl) {
+ ERR_clear_error();
+ if((r=SSL_write(res->ssl, text, (int)strlen(text))) <= 0) {
+- if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
++ int r2;
++ if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN) {
+ verbose(VERB_QUERY, "warning, in SSL_write, peer "
+ "closed connection");
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", r2);
+ return 0;
+ }
+ } else {
+@@ -619,11 +580,12 @@
+ if(res->ssl) {
+ ERR_clear_error();
+ if((r=SSL_read(res->ssl, buf+len, 1)) <= 0) {
+- if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
++ int r2;
++ if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN) {
+ buf[len] = 0;
+ return 1;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", r2);
+ return 0;
+ }
+ } else {
+@@ -636,7 +598,7 @@
+ }
+ if(errno == EINTR || errno == EAGAIN)
+ continue;
+- log_err("could not recv: %s",
++ if(rr < 0) log_err("could not recv: %s",
+ sock_strerror(errno));
+ return 0;
+ }
+@@ -660,7 +622,7 @@
+ skipwhite(char* str)
+ {
+ /* EOS \0 is not a space */
+- while( isspace((unsigned char)*str) )
++ while( isspace((unsigned char)*str) )
+ str++;
+ return str;
+ }
+@@ -708,20 +670,30 @@
+ print_stats(RES* ssl, const char* nm, struct ub_stats_info* s)
+ {
+ struct timeval sumwait, avg;
+- if(!ssl_printf(ssl, "%s.num.queries"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.queries"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries)) return 0;
+ if(!ssl_printf(ssl, "%s.num.queries_ip_ratelimited"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries_ip_ratelimited)) return 0;
+- if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm,
+- (unsigned long)(s->svr.num_queries
++ if(!ssl_printf(ssl, "%s.num.queries_cookie_valid"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_cookie_valid)) return 0;
++ if(!ssl_printf(ssl, "%s.num.queries_cookie_client"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_cookie_client)) return 0;
++ if(!ssl_printf(ssl, "%s.num.queries_cookie_invalid"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_cookie_invalid)) return 0;
++ if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm,
++ (unsigned long)(s->svr.num_queries
+ - s->svr.num_queries_missed_cache))) return 0;
+- if(!ssl_printf(ssl, "%s.num.cachemiss"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.cachemiss"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries_missed_cache)) return 0;
+- if(!ssl_printf(ssl, "%s.num.prefetch"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.prefetch"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries_prefetch)) return 0;
++ if(!ssl_printf(ssl, "%s.num.queries_timed_out"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_timed_out)) return 0;
++ if(!ssl_printf(ssl, "%s.query.queue_time_us.max"SQ"%lu\n", nm,
++ (unsigned long)s->svr.max_query_time_us)) return 0;
+ if(!ssl_printf(ssl, "%s.num.expired"SQ"%lu\n", nm,
+ (unsigned long)s->svr.ans_expired)) return 0;
+- if(!ssl_printf(ssl, "%s.num.recursivereplies"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.recursivereplies"SQ"%lu\n", nm,
+ (unsigned long)s->mesh_replies_sent)) return 0;
+ #ifdef USE_DNSCRYPT
+ if(!ssl_printf(ssl, "%s.num.dnscrypt.crypted"SQ"%lu\n", nm,
+@@ -755,7 +727,7 @@
+ timeval_divide(&avg, &sumwait, s->mesh_replies_sent);
+ if(!ssl_printf(ssl, "%s.recursion.time.avg"SQ ARG_LL "d.%6.6d\n", nm,
+ (long long)avg.tv_sec, (int)avg.tv_usec)) return 0;
+- if(!ssl_printf(ssl, "%s.recursion.time.median"SQ"%g\n", nm,
++ if(!ssl_printf(ssl, "%s.recursion.time.median"SQ"%g\n", nm,
+ s->mesh_time_median)) return 0;
+ if(!ssl_printf(ssl, "%s.tcpusage"SQ"%lu\n", nm,
+ (unsigned long)s->svr.tcp_accept_usage)) return 0;
+@@ -780,7 +752,7 @@
+ /* more than a Gb */
+ size_t front = x / (size_t)1000000;
+ size_t back = x % (size_t)1000000;
+- return ssl_printf(ssl, "%s%u%6.6u\n", desc,
++ return ssl_printf(ssl, "%s%u%6.6u\n", desc,
+ (unsigned)front, (unsigned)back);
+ } else {
+ return ssl_printf(ssl, "%s%lu\n", desc, (unsigned long)x);
+@@ -880,11 +852,11 @@
+ timeval_subtract(&dt, &now, &worker->daemon->time_last_stat);
+ if(reset)
+ worker->daemon->time_last_stat = now;
+- if(!ssl_printf(ssl, "time.now"SQ ARG_LL "d.%6.6d\n",
++ if(!ssl_printf(ssl, "time.now"SQ ARG_LL "d.%6.6d\n",
+ (long long)now.tv_sec, (unsigned)now.tv_usec)) return 0;
+- if(!ssl_printf(ssl, "time.up"SQ ARG_LL "d.%6.6d\n",
++ if(!ssl_printf(ssl, "time.up"SQ ARG_LL "d.%6.6d\n",
+ (long long)up.tv_sec, (unsigned)up.tv_usec)) return 0;
+- if(!ssl_printf(ssl, "time.elapsed"SQ ARG_LL "d.%6.6d\n",
++ if(!ssl_printf(ssl, "time.elapsed"SQ ARG_LL "d.%6.6d\n",
+ (long long)dt.tv_sec, (unsigned)dt.tv_usec)) return 0;
+ return 1;
+ }
+@@ -902,7 +874,7 @@
+ }
+ timehist_import(hist, s->svr.hist, NUM_BUCKETS_HIST);
+ for(i=0; i<hist->num; i++) {
+- if(!ssl_printf(ssl,
++ if(!ssl_printf(ssl,
+ "histogram.%6.6d.%6.6d.to.%6.6d.%6.6d=%lu\n",
+ (int)hist->buckets[i].lower.tv_sec,
+ (int)hist->buckets[i].lower.tv_usec,
+@@ -945,11 +917,11 @@
+ } else {
+ snprintf(nm, sizeof(nm), "TYPE%d", i);
+ }
+- if(!ssl_printf(ssl, "num.query.type.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.type.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.qtype[i])) return 0;
+ }
+ if(!inhibit_zero || s->svr.qtype_big) {
+- if(!ssl_printf(ssl, "num.query.type.other"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.type.other"SQ"%lu\n",
+ (unsigned long)s->svr.qtype_big)) return 0;
+ }
+ /* CLASS */
+@@ -962,11 +934,11 @@
+ } else {
+ snprintf(nm, sizeof(nm), "CLASS%d", i);
+ }
+- if(!ssl_printf(ssl, "num.query.class.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.class.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.qclass[i])) return 0;
+ }
+ if(!inhibit_zero || s->svr.qclass_big) {
+- if(!ssl_printf(ssl, "num.query.class.other"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.class.other"SQ"%lu\n",
+ (unsigned long)s->svr.qclass_big)) return 0;
+ }
+ /* OPCODE */
+@@ -979,44 +951,44 @@
+ } else {
+ snprintf(nm, sizeof(nm), "OPCODE%d", i);
+ }
+- if(!ssl_printf(ssl, "num.query.opcode.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.opcode.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.qopcode[i])) return 0;
+ }
+ /* transport */
+- if(!ssl_printf(ssl, "num.query.tcp"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tcp"SQ"%lu\n",
+ (unsigned long)s->svr.qtcp)) return 0;
+- if(!ssl_printf(ssl, "num.query.tcpout"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tcpout"SQ"%lu\n",
+ (unsigned long)s->svr.qtcp_outgoing)) return 0;
+ if(!ssl_printf(ssl, "num.query.udpout"SQ"%lu\n",
+ (unsigned long)s->svr.qudp_outgoing)) return 0;
+- if(!ssl_printf(ssl, "num.query.tls"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tls"SQ"%lu\n",
+ (unsigned long)s->svr.qtls)) return 0;
+- if(!ssl_printf(ssl, "num.query.tls.resume"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tls.resume"SQ"%lu\n",
+ (unsigned long)s->svr.qtls_resume)) return 0;
+- if(!ssl_printf(ssl, "num.query.ipv6"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.ipv6"SQ"%lu\n",
+ (unsigned long)s->svr.qipv6)) return 0;
+ if(!ssl_printf(ssl, "num.query.https"SQ"%lu\n",
+ (unsigned long)s->svr.qhttps)) return 0;
+ /* flags */
+- if(!ssl_printf(ssl, "num.query.flags.QR"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.QR"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_QR)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.AA"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.AA"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_AA)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.TC"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.TC"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_TC)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.RD"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.RD"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_RD)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.RA"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.RA"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_RA)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.Z"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.Z"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_Z)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.AD"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.AD"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_AD)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.CD"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.CD"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_CD)) return 0;
+- if(!ssl_printf(ssl, "num.query.edns.present"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.edns.present"SQ"%lu\n",
+ (unsigned long)s->svr.qEDNS)) return 0;
+- if(!ssl_printf(ssl, "num.query.edns.DO"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.edns.DO"SQ"%lu\n",
+ (unsigned long)s->svr.qEDNS_DO)) return 0;
+
+ /* RCODE */
+@@ -1030,31 +1002,31 @@
+ } else {
+ snprintf(nm, sizeof(nm), "RCODE%d", i);
+ }
+- if(!ssl_printf(ssl, "num.answer.rcode.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.rcode.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.ans_rcode[i])) return 0;
+ }
+ if(!inhibit_zero || s->svr.ans_rcode_nodata) {
+- if(!ssl_printf(ssl, "num.answer.rcode.nodata"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.rcode.nodata"SQ"%lu\n",
+ (unsigned long)s->svr.ans_rcode_nodata)) return 0;
+ }
+ /* iteration */
+- if(!ssl_printf(ssl, "num.query.ratelimited"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.ratelimited"SQ"%lu\n",
+ (unsigned long)s->svr.queries_ratelimited)) return 0;
+ /* validation */
+- if(!ssl_printf(ssl, "num.answer.secure"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.secure"SQ"%lu\n",
+ (unsigned long)s->svr.ans_secure)) return 0;
+- if(!ssl_printf(ssl, "num.answer.bogus"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.bogus"SQ"%lu\n",
+ (unsigned long)s->svr.ans_bogus)) return 0;
+- if(!ssl_printf(ssl, "num.rrset.bogus"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.rrset.bogus"SQ"%lu\n",
+ (unsigned long)s->svr.rrset_bogus)) return 0;
+- if(!ssl_printf(ssl, "num.query.aggressive.NOERROR"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.aggressive.NOERROR"SQ"%lu\n",
+ (unsigned long)s->svr.num_neg_cache_noerror)) return 0;
+- if(!ssl_printf(ssl, "num.query.aggressive.NXDOMAIN"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.aggressive.NXDOMAIN"SQ"%lu\n",
+ (unsigned long)s->svr.num_neg_cache_nxdomain)) return 0;
+ /* threat detection */
+- if(!ssl_printf(ssl, "unwanted.queries"SQ"%lu\n",
++ if(!ssl_printf(ssl, "unwanted.queries"SQ"%lu\n",
+ (unsigned long)s->svr.unwanted_queries)) return 0;
+- if(!ssl_printf(ssl, "unwanted.replies"SQ"%lu\n",
++ if(!ssl_printf(ssl, "unwanted.replies"SQ"%lu\n",
+ (unsigned long)s->svr.unwanted_replies)) return 0;
+ /* cache counts */
+ if(!ssl_printf(ssl, "msg.cache.count"SQ"%u\n",
+@@ -1065,6 +1037,11 @@
+ (unsigned)s->svr.infra_cache_count)) return 0;
+ if(!ssl_printf(ssl, "key.cache.count"SQ"%u\n",
+ (unsigned)s->svr.key_cache_count)) return 0;
++ /* max collisions */
++ if(!ssl_printf(ssl, "msg.cache.max_collisions"SQ"%u\n",
++ (unsigned)s->svr.msg_cache_max_collisions)) return 0;
++ if(!ssl_printf(ssl, "rrset.cache.max_collisions"SQ"%u\n",
++ (unsigned)s->svr.rrset_cache_max_collisions)) return 0;
+ /* applied RPZ actions */
+ for(i=0; i<UB_STATS_RPZ_ACTION_NUM; i++) {
+ if(i == RPZ_NO_OVERRIDE_ACTION)
+@@ -1095,6 +1072,10 @@
+ if(!ssl_printf(ssl, "num.query.subnet_cache"SQ"%lu\n",
+ (unsigned long)s->svr.num_query_subnet_cache)) return 0;
+ #endif /* CLIENT_SUBNET */
++#ifdef USE_CACHEDB
++ if(!ssl_printf(ssl, "num.query.cachedb"SQ"%lu\n",
++ (unsigned long)s->svr.num_query_cachedb)) return 0;
++#endif /* USE_CACHEDB */
+ return 1;
+ }
+
+@@ -1119,7 +1100,7 @@
+ }
+ /* print the thread statistics */
+ total.mesh_time_median /= (double)daemon->num;
+- if(!print_stats(ssl, "total", &total))
++ if(!print_stats(ssl, "total", &total))
+ return;
+ if(!print_uptime(ssl, worker, reset))
+ return;
+@@ -1208,7 +1189,7 @@
+ return 0;
+ }
+ lock_rw_wrlock(&zones->lock);
+- if((z=local_zones_find(zones, nm, nmlen,
++ if((z=local_zones_find(zones, nm, nmlen,
+ nmlabs, LDNS_RR_CLASS_IN))) {
+ /* already present in tree */
+ lock_rw_wrlock(&z->lock);
+@@ -1218,7 +1199,7 @@
+ lock_rw_unlock(&zones->lock);
+ return 1;
+ }
+- if(!local_zones_add_zone(zones, nm, nmlen,
++ if(!local_zones_add_zone(zones, nm, nmlen,
+ nmlabs, LDNS_RR_CLASS_IN, t)) {
+ lock_rw_unlock(&zones->lock);
+ ssl_printf(ssl, "error out of memory\n");
+@@ -1244,8 +1225,8 @@
+ char buf[2048];
+ int num = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ if(!perform_zone_add(ssl, zones, buf)) {
+ if(!ssl_printf(ssl, "error for input line: %s\n", buf))
+ return;
+@@ -1267,7 +1248,7 @@
+ if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs))
+ return 0;
+ lock_rw_wrlock(&zones->lock);
+- if((z=local_zones_find(zones, nm, nmlen,
++ if((z=local_zones_find(zones, nm, nmlen,
+ nmlabs, LDNS_RR_CLASS_IN))) {
+ /* present in tree */
+ local_zones_del_zone(zones, z);
+@@ -1293,8 +1274,8 @@
+ char buf[2048];
+ int num = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ if(!perform_zone_remove(ssl, zones, buf)) {
+ if(!ssl_printf(ssl, "error for input line: %s\n", buf))
+ return;
+@@ -1357,8 +1338,8 @@
+ char buf[2048];
+ int num = 0, line = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ line++;
+ if(perform_data_add(ssl, zones, buf, line))
+ num++;
+@@ -1397,8 +1378,8 @@
+ char buf[2048];
+ int num = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ if(!perform_data_remove(ssl, zones, buf)) {
+ if(!ssl_printf(ssl, "error for input line: %s\n", buf))
+ return;
+@@ -1608,8 +1589,11 @@
+ if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs))
+ return;
+ t = sldns_get_rr_type_by_name(arg2);
++ if(t == 0 && strcmp(arg2, "TYPE0") != 0) {
++ return;
++ }
+ do_cache_remove(worker, nm, nmlen, t, LDNS_RR_CLASS_IN);
+-
++
+ free(nm);
+ send_ok(ssl);
+ }
+@@ -1719,7 +1703,7 @@
+ struct del_info* inf = (struct del_info*)arg;
+ struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key;
+ if(dname_subdomain_c(k->rk.dname, inf->name)) {
+- struct packed_rrset_data* d =
++ struct packed_rrset_data* d =
+ (struct packed_rrset_data*)e->data;
+ if(d->ttl > inf->expired) {
+ d->ttl = inf->expired;
+@@ -1783,21 +1767,21 @@
+ inf.num_rrsets = 0;
+ inf.num_msgs = 0;
+ inf.num_keys = 0;
+- slabhash_traverse(&worker->env.rrset_cache->table, 1,
++ slabhash_traverse(&worker->env.rrset_cache->table, 1,
+ &zone_del_rrset, &inf);
+
+ slabhash_traverse(worker->env.msg_cache, 1, &zone_del_msg, &inf);
+
+ /* and validator cache */
+ if(worker->env.key_cache) {
+- slabhash_traverse(worker->env.key_cache->slab, 1,
++ slabhash_traverse(worker->env.key_cache->slab, 1,
+ &zone_del_kcache, &inf);
+ }
+
+ free(nm);
+
+ (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages "
+- "and %lu key entries\n", (unsigned long)inf.num_rrsets,
++ "and %lu key entries\n", (unsigned long)inf.num_rrsets,
+ (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys);
+ }
+
+@@ -1852,19 +1836,19 @@
+ inf.num_rrsets = 0;
+ inf.num_msgs = 0;
+ inf.num_keys = 0;
+- slabhash_traverse(&worker->env.rrset_cache->table, 1,
++ slabhash_traverse(&worker->env.rrset_cache->table, 1,
+ &bogus_del_rrset, &inf);
+
+ slabhash_traverse(worker->env.msg_cache, 1, &bogus_del_msg, &inf);
+
+ /* and validator cache */
+ if(worker->env.key_cache) {
+- slabhash_traverse(worker->env.key_cache->slab, 1,
++ slabhash_traverse(worker->env.key_cache->slab, 1,
+ &bogus_del_kcache, &inf);
+ }
+
+ (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages "
+- "and %lu key entries\n", (unsigned long)inf.num_rrsets,
++ "and %lu key entries\n", (unsigned long)inf.num_rrsets,
+ (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys);
+ }
+
+@@ -1927,19 +1911,19 @@
+ inf.num_rrsets = 0;
+ inf.num_msgs = 0;
+ inf.num_keys = 0;
+- slabhash_traverse(&worker->env.rrset_cache->table, 1,
++ slabhash_traverse(&worker->env.rrset_cache->table, 1,
+ &negative_del_rrset, &inf);
+
+ slabhash_traverse(worker->env.msg_cache, 1, &negative_del_msg, &inf);
+
+ /* and validator cache */
+ if(worker->env.key_cache) {
+- slabhash_traverse(worker->env.key_cache->slab, 1,
++ slabhash_traverse(worker->env.key_cache->slab, 1,
+ &negative_del_kcache, &inf);
+ }
+
+ (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages "
+- "and %lu key entries\n", (unsigned long)inf.num_rrsets,
++ "and %lu key entries\n", (unsigned long)inf.num_rrsets,
+ (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys);
+ }
+
+@@ -1964,7 +1948,7 @@
+ do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_NAPTR, LDNS_RR_CLASS_IN);
+ do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_SVCB, LDNS_RR_CLASS_IN);
+ do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_HTTPS, LDNS_RR_CLASS_IN);
+-
++
+ free(nm);
+ send_ok(ssl);
+ }
+@@ -2334,7 +2318,7 @@
+ uptime = (time_t)time(NULL) - (time_t)worker->daemon->time_boot.tv_sec;
+ if(!ssl_printf(ssl, "uptime: " ARG_LL "d seconds\n", (long long)uptime))
+ return;
+- if(!ssl_printf(ssl, "options:%s%s%s%s\n" ,
++ if(!ssl_printf(ssl, "options:%s%s%s%s\n" ,
+ (worker->daemon->reuseport?" reuseport":""),
+ (worker->daemon->rc->accept_list?" control":""),
+ (worker->daemon->rc->accept_list && worker->daemon->rc->use_cert?"(ssl)":""),
+@@ -2348,7 +2332,7 @@
+
+ /** get age for the mesh state */
+ static void
+-get_mesh_age(struct mesh_state* m, char* buf, size_t len,
++get_mesh_age(struct mesh_state* m, char* buf, size_t len,
+ struct module_env* env)
+ {
+ if(m->reply_list) {
+@@ -2367,7 +2351,7 @@
+
+ /** get status of a mesh state */
+ static void
+-get_mesh_status(struct mesh_area* mesh, struct mesh_state* m,
++get_mesh_status(struct mesh_area* mesh, struct mesh_state* m,
+ char* buf, size_t len)
+ {
+ enum module_ext_state s = m->s.ext_state[m->s.curmod];
+@@ -2389,7 +2373,7 @@
+ snprintf(buf, len, " ");
+ l = strlen(buf);
+ buf += l; len -= l;
+- addr_to_str(&e->qsent->addr, e->qsent->addrlen,
++ addr_to_str(&e->qsent->addr, e->qsent->addrlen,
+ buf, len);
+ l = strlen(buf);
+ buf += l; len -= l;
+@@ -2442,7 +2426,7 @@
+ dname_str(m->s.qinfo.qname, buf);
+ get_mesh_age(m, timebuf, sizeof(timebuf), &worker->env);
+ get_mesh_status(mesh, m, statbuf, sizeof(statbuf));
+- if(!ssl_printf(ssl, "%3d %4s %2s %s %s %s\n",
++ if(!ssl_printf(ssl, "%3d %4s %2s %s %s %s\n",
+ num, (t?t:"TYPE??"), (c?c:"CLASS??"), buf, timebuf,
+ statbuf)) {
+ free(t);
+@@ -2632,7 +2616,7 @@
+ free(nm);
+ send_ok(ssl);
+ }
+-
++
+ /** do the set_option command */
+ static void
+ do_set_option(RES* ssl, struct worker* worker, char* arg)
+@@ -2770,7 +2754,7 @@
+ RBTREE_FOR(z, struct local_zone*, &zones->ztree) {
+ lock_rw_rdlock(&z->lock);
+ dname_str(z->name, buf);
+- if(!ssl_printf(ssl, "%s %s\n", buf,
++ if(!ssl_printf(ssl, "%s %s\n", buf,
+ local_zone_type2str(z->type))) {
+ /* failure to print */
+ lock_rw_unlock(&z->lock);
+@@ -2999,7 +2983,7 @@
+ distribute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd)
+ {
+ int i;
+- if(!cmd || !ssl)
++ if(!cmd || !ssl)
+ return;
+ /* skip i=0 which is me */
+ for(i=1; i<rc->worker->daemon->num; i++) {
+@@ -3022,7 +3006,7 @@
+
+ /** execute a remote control command */
+ static void
+-execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
++execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
+ struct worker* worker)
+ {
+ char* p = skipwhite(cmd);
+@@ -3206,7 +3190,7 @@
+ }
+ }
+
+-void
++void
+ daemon_remote_exec(struct worker* worker)
+ {
+ /* read the cmd string */
+@@ -3240,9 +3224,10 @@
+ if(res->ssl) {
+ ERR_clear_error();
+ if((r=SSL_read(res->ssl, magic, (int)sizeof(magic)-1)) <= 0) {
+- if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN)
++ int r2;
++ if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN)
+ return;
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", r2);
+ return;
+ }
+ } else {
+@@ -3309,13 +3294,13 @@
+ log_err("remote control connection closed prematurely");
+ log_addr(VERB_OPS, "failed connection from",
+ &s->c->repinfo.remote_addr, s->c->repinfo.remote_addrlen);
+- log_crypto_err("remote control failed ssl");
++ log_crypto_err_io("remote control failed ssl", r2);
+ clean_point(rc, s);
+ }
+ return 0;
+ }
+
+-int remote_control_callback(struct comm_point* c, void* arg, int err,
++int remote_control_callback(struct comm_point* c, void* arg, int err,
+ struct comm_reply* ATTR_UNUSED(rep))
+ {
+ RES res;
+@@ -3323,7 +3308,7 @@
+ struct daemon_remote* rc = s->rc;
+ int r;
+ if(err != NETEVENT_NOERROR) {
+- if(err==NETEVENT_TIMEOUT)
++ if(err==NETEVENT_TIMEOUT)
+ log_err("remote control timed out");
+ clean_point(rc, s);
+ return 0;
+--- contrib/unbound/daemon/remote.h.orig
++++ contrib/unbound/daemon/remote.h
+@@ -46,7 +46,7 @@
+ #ifndef DAEMON_REMOTE_H
+ #define DAEMON_REMOTE_H
+ #ifdef HAVE_OPENSSL_SSL_H
+-#include "openssl/ssl.h"
++#include <openssl/ssl.h>
+ #endif
+ struct config_file;
+ struct listen_list;
+--- contrib/unbound/daemon/stats.c.orig
++++ contrib/unbound/daemon/stats.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -116,8 +116,8 @@
+ log_info("server stats for thread %d: %u queries, "
+ "%u answers from cache, %u recursions, %u prefetch, %u rejected by "
+ "ip ratelimiting",
+- threadnum, (unsigned)stats->num_queries,
+- (unsigned)(stats->num_queries -
++ threadnum, (unsigned)stats->num_queries,
++ (unsigned)(stats->num_queries -
+ stats->num_queries_missed_cache),
+ (unsigned)stats->num_queries_missed_cache,
+ (unsigned)stats->num_queries_prefetch,
+@@ -279,7 +279,7 @@
+ s->svr.ans_rcode[i] += (long long)worker->env.mesh->ans_rcode[i];
+ for(i=0; i<UB_STATS_RPZ_ACTION_NUM; i++)
+ s->svr.rpz_action[i] += (long long)worker->env.mesh->rpz_action[i];
+- timehist_export(worker->env.mesh->histogram, s->svr.hist,
++ timehist_export(worker->env.mesh->histogram, s->svr.hist,
+ NUM_BUCKETS_HIST);
+ /* values from outside network */
+ s->svr.unwanted_replies = (long long)worker->back->unwanted_replies;
+@@ -293,8 +293,10 @@
+ s->svr.queries_ratelimited = (long long)get_queries_ratelimit(worker, reset);
+
+ /* get cache sizes */
+- s->svr.msg_cache_count = (long long)count_slabhash_entries(worker->env.msg_cache);
+- s->svr.rrset_cache_count = (long long)count_slabhash_entries(&worker->env.rrset_cache->table);
++ get_slabhash_stats(worker->env.msg_cache,
++ &s->svr.msg_cache_count, &s->svr.msg_cache_max_collisions);
++ get_slabhash_stats(&worker->env.rrset_cache->table,
++ &s->svr.rrset_cache_count, &s->svr.rrset_cache_max_collisions);
+ s->svr.infra_cache_count = (long long)count_slabhash_entries(worker->env.infra_cache->hosts);
+ if(worker->env.key_cache)
+ s->svr.key_cache_count = (long long)count_slabhash_entries(worker->env.key_cache->slab);
+@@ -354,6 +356,11 @@
+ s->svr.num_query_subnet = 0;
+ s->svr.num_query_subnet_cache = 0;
+ #endif
++#ifdef USE_CACHEDB
++ s->svr.num_query_cachedb = (long long)worker->env.mesh->ans_cachedb;
++#else
++ s->svr.num_query_cachedb = 0;
++#endif
+
+ /* get tcp accept usage */
+ s->svr.tcp_accept_usage = 0;
+@@ -419,7 +426,7 @@
+ struct ub_stats_info s;
+ server_stats_compile(worker, &s, reset);
+ verbose(VERB_ALGO, "write stats replymsg");
+- if(!tube_write_msg(worker->daemon->workers[0]->cmd,
++ if(!tube_write_msg(worker->daemon->workers[0]->cmd,
+ (uint8_t*)&s, sizeof(s), 0))
+ fatal_exit("could not write stat values over cmd channel");
+ }
+@@ -428,8 +435,14 @@
+ {
+ total->svr.num_queries += a->svr.num_queries;
+ total->svr.num_queries_ip_ratelimited += a->svr.num_queries_ip_ratelimited;
++ total->svr.num_queries_cookie_valid += a->svr.num_queries_cookie_valid;
++ total->svr.num_queries_cookie_client += a->svr.num_queries_cookie_client;
++ total->svr.num_queries_cookie_invalid += a->svr.num_queries_cookie_invalid;
+ total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache;
+ total->svr.num_queries_prefetch += a->svr.num_queries_prefetch;
++ total->svr.num_queries_timed_out += a->svr.num_queries_timed_out;
++ if (total->svr.max_query_time_us < a->svr.max_query_time_us)
++ total->svr.max_query_time_us = a->svr.max_query_time_us;
+ total->svr.sum_query_list_size += a->svr.sum_query_list_size;
+ total->svr.ans_expired += a->svr.ans_expired;
+ #ifdef USE_DNSCRYPT
+@@ -471,6 +484,9 @@
+ total->svr.unwanted_replies += a->svr.unwanted_replies;
+ total->svr.unwanted_queries += a->svr.unwanted_queries;
+ total->svr.tcp_accept_usage += a->svr.tcp_accept_usage;
++#ifdef USE_CACHEDB
++ total->svr.num_query_cachedb += a->svr.num_query_cachedb;
++#endif
+ for(i=0; i<UB_STATS_QTYPE_NUM; i++)
+ total->svr.qtype[i] += a->svr.qtype[i];
+ for(i=0; i<UB_STATS_QCLASS_NUM; i++)
+@@ -514,7 +530,7 @@
+ if(c->ssl != NULL) {
+ stats->qtls++;
+ #ifdef HAVE_SSL
+- if(SSL_session_reused(c->ssl))
++ if(SSL_session_reused(c->ssl))
+ stats->qtls_resume++;
+ #endif
+ if(c->type == comm_http)
+@@ -555,3 +571,16 @@
+ stats->ans_rcode_nodata ++;
+ }
+ }
++
++void server_stats_downstream_cookie(struct ub_server_stats* stats,
++ struct edns_data* edns)
++{
++ if(!(edns->edns_present && edns->cookie_present)) return;
++ if(edns->cookie_valid) {
++ stats->num_queries_cookie_valid++;
++ } else if(edns->cookie_client) {
++ stats->num_queries_cookie_client++;
++ } else {
++ stats->num_queries_cookie_invalid++;
++ }
++}
+--- contrib/unbound/daemon/stats.h.orig
++++ contrib/unbound/daemon/stats.h
+@@ -126,4 +126,11 @@
+ */
+ void server_stats_insrcode(struct ub_server_stats* stats, struct sldns_buffer* buf);
+
++/**
++ * Add DNS Cookie stats for this query
++ * @param stats: the stats
++ * @param edns: edns record
++ */
++void server_stats_downstream_cookie(struct ub_server_stats* stats,
++ struct edns_data* edns);
+ #endif /* DAEMON_STATS_H */
+--- contrib/unbound/daemon/worker.c.orig
++++ contrib/unbound/daemon/worker.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -66,8 +66,10 @@
+ #include "util/data/msgencode.h"
+ #include "util/data/dname.h"
+ #include "util/fptr_wlist.h"
++#include "util/proxy_protocol.h"
+ #include "util/tube.h"
+ #include "util/edns.h"
++#include "util/timeval_func.h"
+ #include "iterator/iter_fwd.h"
+ #include "iterator/iter_hints.h"
+ #include "iterator/iter_utils.h"
+@@ -112,7 +114,7 @@
+
+ /** Report on memory usage by this thread and global */
+ static void
+-worker_mem_report(struct worker* ATTR_UNUSED(worker),
++worker_mem_report(struct worker* ATTR_UNUSED(worker),
+ struct serviced_query* ATTR_UNUSED(cur_serv))
+ {
+ #ifdef UNBOUND_ALLOC_STATS
+@@ -125,7 +127,7 @@
+ #ifdef CLIENT_SUBNET
+ size_t subnet = 0;
+ #endif /* CLIENT_SUBNET */
+- if(verbosity < VERB_ALGO)
++ if(verbosity < VERB_ALGO)
+ return;
+ front = listen_get_mem(worker->front);
+ back = outnet_get_mem(worker->back);
+@@ -154,10 +156,10 @@
+ (&worker->env, i);
+ }
+ me = sizeof(*worker) + sizeof(*worker->base) + sizeof(*worker->comsig)
+- + comm_point_get_mem(worker->cmd_com)
+- + sizeof(worker->rndstate)
+- + regional_get_mem(worker->scratchpad)
+- + sizeof(*worker->env.scratch_buffer)
++ + comm_point_get_mem(worker->cmd_com)
++ + sizeof(worker->rndstate)
++ + regional_get_mem(worker->scratchpad)
++ + sizeof(*worker->env.scratch_buffer)
+ + sldns_buffer_capacity(worker->env.scratch_buffer)
+ + forwards_get_mem(worker->env.fwds)
+ + hints_get_mem(worker->env.hints);
+@@ -172,7 +174,7 @@
+ log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u "
+ "rrset=%u infra=%u iter=%u val=%u subnet=%u anchors=%u "
+ "alloccache=%u globalalloccache=%u me=%u",
+- (unsigned)total, (unsigned)front, (unsigned)back,
++ (unsigned)total, (unsigned)front, (unsigned)back,
+ (unsigned)mesh, (unsigned)msg, (unsigned)rrset, (unsigned)infra,
+ (unsigned)iter, (unsigned)val,
+ (unsigned)subnet, (unsigned)anch, (unsigned)ac,
+@@ -181,13 +183,13 @@
+ log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u "
+ "rrset=%u infra=%u iter=%u val=%u anchors=%u "
+ "alloccache=%u globalalloccache=%u me=%u",
+- (unsigned)total, (unsigned)front, (unsigned)back,
+- (unsigned)mesh, (unsigned)msg, (unsigned)rrset,
++ (unsigned)total, (unsigned)front, (unsigned)back,
++ (unsigned)mesh, (unsigned)msg, (unsigned)rrset,
+ (unsigned)infra, (unsigned)iter, (unsigned)val, (unsigned)anch,
+ (unsigned)ac, (unsigned)superac, (unsigned)me);
+ #endif /* CLIENT_SUBNET */
+ log_info("Total heap memory estimate: %u total-alloc: %u "
+- "total-free: %u", (unsigned)total,
++ "total-free: %u", (unsigned)total,
+ (unsigned)unbound_mem_alloc, (unsigned)unbound_mem_freed);
+ #else /* no UNBOUND_ALLOC_STATS */
+ size_t val = 0;
+@@ -227,7 +229,7 @@
+ #endif /* UNBOUND_ALLOC_STATS */
+ }
+
+-void
++void
+ worker_send_cmd(struct worker* worker, enum worker_commands cmd)
+ {
+ uint32_t c = (uint32_t)htonl(cmd);
+@@ -236,8 +238,8 @@
+ }
+ }
+
+-int
+-worker_handle_service_reply(struct comm_point* c, void* arg, int error,
++int
++worker_handle_service_reply(struct comm_point* c, void* arg, int error,
+ struct comm_reply* reply_info)
+ {
+ struct outbound_entry* e = (struct outbound_entry*)arg;
+@@ -252,13 +254,13 @@
+ }
+ /* sanity check. */
+ if(!LDNS_QR_WIRE(sldns_buffer_begin(c->buffer))
+- || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) !=
++ || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) !=
+ LDNS_PACKET_QUERY
+ || LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) > 1) {
+ /* error becomes timeout for the module as if this reply
+ * never arrived. */
+ verbose(VERB_ALGO, "worker: bad reply handled as timeout");
+- mesh_report_reply(worker->env.mesh, e, reply_info,
++ mesh_report_reply(worker->env.mesh, e, reply_info,
+ NETEVENT_TIMEOUT);
+ worker_mem_report(worker, sq);
+ return 0;
+@@ -288,64 +290,86 @@
+ return err;
+ }
+
++/**
++ * Structure holding the result of the worker_check_request function.
++ * Based on configuration it could be called up to four times; ideally should
++ * be called once.
++ */
++struct check_request_result {
++ int checked;
++ int value;
++};
+ /** check request sanity.
+ * @param pkt: the wire packet to examine for sanity.
+ * @param worker: parameters for checking.
+- * @return error code, 0 OK, or -1 discard.
++ * @param out: struct to update with the result.
+ */
+-static int
+-worker_check_request(sldns_buffer* pkt, struct worker* worker)
++static void
++worker_check_request(sldns_buffer* pkt, struct worker* worker,
++ struct check_request_result* out)
+ {
++ if(out->checked) return;
++ out->checked = 1;
+ if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) {
+ verbose(VERB_QUERY, "request too short, discarded");
+- return -1;
++ out->value = -1;
++ return;
+ }
+- if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE &&
++ if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE &&
+ worker->daemon->cfg->harden_large_queries) {
+ verbose(VERB_QUERY, "request too large, discarded");
+- return -1;
++ out->value = -1;
++ return;
+ }
+ if(LDNS_QR_WIRE(sldns_buffer_begin(pkt))) {
+ verbose(VERB_QUERY, "request has QR bit on, discarded");
+- return -1;
++ out->value = -1;
++ return;
+ }
+ if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) {
+ LDNS_TC_CLR(sldns_buffer_begin(pkt));
+ verbose(VERB_QUERY, "request bad, has TC bit on");
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+ if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY &&
+ LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_NOTIFY) {
+- verbose(VERB_QUERY, "request unknown opcode %d",
++ verbose(VERB_QUERY, "request unknown opcode %d",
+ LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_NOTIMPL);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_NOTIMPL);
++ return;
+ }
+ if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) {
+- verbose(VERB_QUERY, "request wrong nr qd=%d",
++ verbose(VERB_QUERY, "request wrong nr qd=%d",
+ LDNS_QDCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+- if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0 &&
++ if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0 &&
+ (LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 1 ||
+ LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_NOTIFY)) {
+- verbose(VERB_QUERY, "request wrong nr an=%d",
++ verbose(VERB_QUERY, "request wrong nr an=%d",
+ LDNS_ANCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+ if(LDNS_NSCOUNT(sldns_buffer_begin(pkt)) != 0) {
+- verbose(VERB_QUERY, "request wrong nr ns=%d",
++ verbose(VERB_QUERY, "request wrong nr ns=%d",
+ LDNS_NSCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+ if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) > 1) {
+- verbose(VERB_QUERY, "request wrong nr ar=%d",
++ verbose(VERB_QUERY, "request wrong nr ar=%d",
+ LDNS_ARCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+- return 0;
++ out->value = 0;
++ return;
+ }
+
+-void
++void
+ worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg,
+ size_t len, int error, void* arg)
+ {
+@@ -388,7 +412,7 @@
+
+ /** check if a delegation is secure */
+ static enum sec_status
+-check_delegation_secure(struct reply_info *rep)
++check_delegation_secure(struct reply_info *rep)
+ {
+ /* return smallest security status */
+ size_t i;
+@@ -424,10 +448,10 @@
+ s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data)
+ ->security;
+ if(s != sec_status_secure) {
+- memmove(rep->rrsets+i, rep->rrsets+i+1,
+- sizeof(struct ub_packed_rrset_key*)*
++ memmove(rep->rrsets+i, rep->rrsets+i+1,
++ sizeof(struct ub_packed_rrset_key*)*
+ (rep->rrset_count - i - 1));
+- rep->ar_numrrsets--;
++ rep->ar_numrrsets--;
+ rep->rrset_count--;
+ i--;
+ }
+@@ -437,27 +461,28 @@
+ /** answer nonrecursive query from the cache */
+ static int
+ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
+- uint16_t id, uint16_t flags, struct comm_reply* repinfo,
++ uint16_t id, uint16_t flags, struct comm_reply* repinfo,
+ struct edns_data* edns)
+ {
+ /* for a nonrecursive query return either:
+ * o an error (servfail; we try to avoid this)
+ * o a delegation (closest we have; this routine tries that)
+- * o the answer (checked by answer_from_cache)
++ * o the answer (checked by answer_from_cache)
+ *
+- * So, grab a delegation from the rrset cache.
++ * So, grab a delegation from the rrset cache.
+ * Then check if it needs validation, if so, this routine fails,
+ * so that iterator can prime and validator can verify rrsets.
+ */
+ uint16_t udpsize = edns->udp_size;
+ int secure = 0;
+ time_t timenow = *worker->env.now;
+- int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd)
++ int has_cd_bit = (flags&BIT_CD);
++ int must_validate = (!has_cd_bit || worker->env.cfg->ignore_cd)
+ && worker->env.need_to_validate;
+ struct dns_msg *msg = NULL;
+ struct delegpt *dp;
+
+- dp = dns_cache_find_delegation(&worker->env, qinfo->qname,
++ dp = dns_cache_find_delegation(&worker->env, qinfo->qname,
+ qinfo->qname_len, qinfo->qtype, qinfo->qclass,
+ worker->scratchpad, &msg, timenow, 0, NULL, 0);
+ if(!dp) { /* no delegation, need to reprime */
+@@ -470,7 +495,7 @@
+ if(must_validate) {
+ switch(check_delegation_secure(msg->rep)) {
+ case sec_status_unchecked:
+- /* some rrsets have not been verified yet, go and
++ /* some rrsets have not been verified yet, go and
+ * let validator do that */
+ return 0;
+ case sec_status_bogus:
+@@ -484,13 +509,14 @@
+ msg->rep, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ return 0;
+- /* TODO store the reason for the bogus reply in cache
+- * and implement in here instead of the hardcoded EDE */
+- if (worker->env.cfg->ede) {
+- EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+- worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
++ /* Attach the cached EDE (RFC8914) */
++ if(worker->env.cfg->ede &&
++ msg->rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, msg->rep->reason_bogus,
++ msg->rep->reason_bogus_str);
+ }
+- error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
++ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ &msg->qinfo, id, flags, edns);
+ if(worker->stats.extended) {
+ worker->stats.ans_bogus++;
+@@ -517,11 +543,23 @@
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
++ if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
++ edns->edns_present = 0;
+ if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, msg->rep,
+ (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ return 0;
+ msg->rep->flags |= BIT_QR|BIT_RA;
++ /* Attach the cached EDE (RFC8914) if CD bit is set and the answer is
++ * bogus. */
++ if(worker->env.cfg->ede && has_cd_bit &&
++ (check_delegation_secure(msg->rep) == sec_status_bogus ||
++ check_delegation_secure(msg->rep) == sec_status_secure_sentinel_fail) &&
++ msg->rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, msg->rep->reason_bogus,
++ msg->rep->reason_bogus_str);
++ }
+ if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags,
+ repinfo->c->buffer, 0, 1, worker->scratchpad,
+ udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) {
+@@ -529,7 +567,7 @@
+ LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ edns->opt_list_inplace_cb_out = NULL;
+- error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
++ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ &msg->qinfo, id, flags, edns);
+ }
+ if(worker->stats.extended) {
+@@ -565,9 +603,10 @@
+
+ /* xxx_deny actions mean dropping the reply, unless the original reply
+ * was redirected to response-ip data. */
+- if((actinfo.action == respip_deny ||
++ if(actinfo.action == respip_always_deny ||
++ ((actinfo.action == respip_deny ||
+ actinfo.action == respip_inform_deny) &&
+- *encode_repp == rep)
++ *encode_repp == rep))
+ *encode_repp = NULL;
+
+ /* If address info is returned, it means the action should be an
+@@ -611,7 +650,8 @@
+ uint16_t udpsize = edns->udp_size;
+ struct reply_info* encode_rep = rep;
+ struct reply_info* partial_rep = *partial_repp;
+- int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd)
++ int has_cd_bit = (flags&BIT_CD);
++ int must_validate = (!has_cd_bit || worker->env.cfg->ignore_cd)
+ && worker->env.need_to_validate;
+ *partial_repp = NULL; /* avoid accidental further pass */
+
+@@ -665,15 +705,17 @@
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
++ if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
++ edns->edns_present = 0;
+ if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, rep,
+ LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ goto bail_out;
+- /* TODO store the reason for the bogus reply in cache
+- * and implement in here instead of the hardcoded EDE */
+- if (worker->env.cfg->ede) {
+- EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+- worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
++ /* Attach the cached EDE (RFC8914) */
++ if(worker->env.cfg->ede && rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, rep->reason_bogus,
++ rep->reason_bogus_str);
+ }
+ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ qinfo, id, flags, edns);
+@@ -705,10 +747,8 @@
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
+- if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, rep,
+- (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
+- worker->env.now_tv))
+- goto bail_out;
++ if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
++ edns->edns_present = 0;
+ *alias_rrset = NULL; /* avoid confusion if caller set it to non-NULL */
+ if((worker->daemon->use_response_ip || worker->daemon->use_rpz) &&
+ !partial_rep && !apply_respip_action(worker, qinfo, cinfo, rep,
+@@ -738,11 +778,24 @@
+ goto bail_out;
+ }
+ } else {
+- if (*is_expired_answer == 1 &&
++ if(*is_expired_answer == 1 &&
+ worker->env.cfg->ede_serve_expired && worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+ worker->scratchpad, LDNS_EDE_STALE_ANSWER, "");
+ }
++ /* Attach the cached EDE (RFC8914) if CD bit is set and the
++ * answer is bogus. */
++ if(*is_secure_answer == 0 &&
++ worker->env.cfg->ede && has_cd_bit &&
++ encode_rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, encode_rep->reason_bogus,
++ encode_rep->reason_bogus_str);
++ }
++ if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, encode_rep,
++ (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
++ worker->env.now_tv))
++ goto bail_out;
+ if(!reply_info_answer_encode(qinfo, encode_rep, id, flags,
+ repinfo->c->buffer, timenow, 1, worker->scratchpad,
+ udpsize, edns, (int)(edns->bits & EDNS_DO),
+@@ -763,7 +816,7 @@
+ return 1;
+
+ bail_out:
+- rrset_array_unlock_touch(worker->env.rrset_cache,
++ rrset_array_unlock_touch(worker->env.rrset_cache,
+ worker->scratchpad, rep->ref, rep->rrset_count);
+ return 0;
+ }
+@@ -793,7 +846,8 @@
+ if(modstack_find(&worker->env.mesh->mods, "subnetcache") != -1
+ && worker->env.unique_mesh) {
+ mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+- PREFETCH_EXPIRY_ADD, rpz_passthru, repinfo, opt_list);
++ PREFETCH_EXPIRY_ADD, rpz_passthru,
++ &repinfo->client_addr, opt_list);
+ return;
+ }
+ #endif
+@@ -947,12 +1001,12 @@
+ struct config_file* cfg = w->env.cfg;
+ if(qinfo->qtype != LDNS_RR_TYPE_ANY && qinfo->qtype != LDNS_RR_TYPE_TXT)
+ return 0;
+- if(query_dname_compare(qinfo->qname,
++ if(query_dname_compare(qinfo->qname,
+ (uint8_t*)"\002id\006server") == 0 ||
+- query_dname_compare(qinfo->qname,
++ query_dname_compare(qinfo->qname,
+ (uint8_t*)"\010hostname\004bind") == 0)
+ {
+- if(cfg->hide_identity)
++ if(cfg->hide_identity)
+ return 0;
+ if(cfg->identity==NULL || cfg->identity[0]==0) {
+ char buf[MAXHOSTNAMELEN+1];
+@@ -967,12 +1021,12 @@
+ else chaos_replyonestr(pkt, cfg->identity, edns, w, repinfo);
+ return 1;
+ }
+- if(query_dname_compare(qinfo->qname,
++ if(query_dname_compare(qinfo->qname,
+ (uint8_t*)"\007version\006server") == 0 ||
+- query_dname_compare(qinfo->qname,
++ query_dname_compare(qinfo->qname,
+ (uint8_t*)"\007version\004bind") == 0)
+ {
+- if(cfg->hide_version)
++ if(cfg->hide_version)
+ return 0;
+ if(cfg->version==NULL || cfg->version[0]==0)
+ chaos_replyonestr(pkt, PACKAGE_STRING, edns, w, repinfo);
+@@ -1056,7 +1110,8 @@
+ deny_refuse(struct comm_point* c, enum acl_access acl,
+ enum acl_access deny, enum acl_access refuse,
+ struct worker* worker, struct comm_reply* repinfo,
+- struct acl_addr* acladdr, int ede)
++ struct acl_addr* acladdr, int ede,
++ struct check_request_result* check_result)
+ {
+ if(acl == deny) {
+ if(verbosity >= VERB_ALGO) {
+@@ -1079,9 +1134,16 @@
+
+ if(worker->stats.extended)
+ worker->stats.unwanted_queries++;
+- if(worker_check_request(c->buffer, worker) == -1) {
++ worker_check_request(c->buffer, worker, check_result);
++ if(check_result->value != 0) {
++ if(check_result->value != -1) {
++ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ check_result->value);
++ return 1;
++ }
+ comm_point_drop_reply(repinfo);
+- return 0; /* discard this */
++ return 0;
+ }
+ /* worker_check_request() above guarantees that the buffer contains at
+ * least a header and that qdcount == 1
+@@ -1131,7 +1193,7 @@
+ return 1;
+ }
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_REFUSED);
+
+ sldns_buffer_skip(c->buffer, (ssize_t)sizeof(uint16_t)); /* skip qtype */
+@@ -1146,7 +1208,7 @@
+ /* Skip through the RR records */
+ if(LDNS_ANCOUNT(sldns_buffer_begin(c->buffer)) != 0 ||
+ LDNS_NSCOUNT(sldns_buffer_begin(c->buffer)) != 0) {
+- if(!skip_pkt_rrs(c->buffer,
++ if(!skip_pkt_rrs(c->buffer,
+ ((int)LDNS_ANCOUNT(sldns_buffer_begin(c->buffer)))+
+ ((int)LDNS_NSCOUNT(sldns_buffer_begin(c->buffer))))) {
+ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+@@ -1235,7 +1297,8 @@
+ static int
+ deny_refuse_all(struct comm_point* c, enum acl_access* acl,
+ struct worker* worker, struct comm_reply* repinfo,
+- struct acl_addr** acladdr, int ede, int check_proxy)
++ struct acl_addr** acladdr, int ede, int check_proxy,
++ struct check_request_result* check_result)
+ {
+ if(check_proxy) {
+ *acladdr = acl_addr_lookup(worker->daemon->acl,
+@@ -1250,16 +1313,51 @@
+ }
+ *acl = acl_get_control(*acladdr);
+ return deny_refuse(c, *acl, acl_deny, acl_refuse, worker, repinfo,
+- *acladdr, ede);
++ *acladdr, ede, check_result);
+ }
+
+ static int
+ deny_refuse_non_local(struct comm_point* c, enum acl_access acl,
+ struct worker* worker, struct comm_reply* repinfo,
+- struct acl_addr* acladdr, int ede)
++ struct acl_addr* acladdr, int ede,
++ struct check_request_result* check_result)
+ {
+ return deny_refuse(c, acl, acl_deny_non_local, acl_refuse_non_local,
+- worker, repinfo, acladdr, ede);
++ worker, repinfo, acladdr, ede, check_result);
++}
++
++/* Returns 1 if the ip rate limit check can happen before EDNS parsing,
++ * else 0 */
++static int
++pre_edns_ip_ratelimit_check(enum acl_access acl)
++{
++ if(acl == acl_allow_cookie) return 0;
++ return 1;
++}
++
++/* Check if the query is blocked by source IP rate limiting.
++ * Returns 1 if it passes the check, 0 otherwise. */
++static int
++check_ip_ratelimit(struct worker* worker, struct sockaddr_storage* addr,
++ socklen_t addrlen, int has_cookie, sldns_buffer* pkt)
++{
++ if(!infra_ip_ratelimit_inc(worker->env.infra_cache, addr, addrlen,
++ *worker->env.now, has_cookie,
++ worker->env.cfg->ip_ratelimit_backoff, pkt)) {
++ /* See if we can pass through with slip factor */
++ if(!has_cookie && worker->env.cfg->ip_ratelimit_factor != 0 &&
++ ub_random_max(worker->env.rnd,
++ worker->env.cfg->ip_ratelimit_factor) == 0) {
++ char addrbuf[128];
++ addr_to_str(addr, addrlen, addrbuf, sizeof(addrbuf));
++ verbose(VERB_QUERY, "ip_ratelimit allowed through for "
++ "ip address %s because of slip in "
++ "ip_ratelimit_factor", addrbuf);
++ return 1;
++ }
++ return 0;
++ }
++ return 1;
+ }
+
+ int
+@@ -1275,11 +1373,13 @@
+ struct edns_option* original_edns_list = NULL;
+ enum acl_access acl;
+ struct acl_addr* acladdr;
++ int pre_edns_ip_ratelimit = 1;
+ int rc = 0;
+ int need_drop = 0;
+ int is_expired_answer = 0;
+ int is_secure_answer = 0;
+ int rpz_passthru = 0;
++ long long wait_queue_time = 0;
+ /* We might have to chase a CNAME chain internally, in which case
+ * we'll have up to two replies and combine them to build a complete
+ * answer. These variables control this case. */
+@@ -1288,6 +1388,8 @@
+ struct query_info* lookup_qinfo = &qinfo;
+ struct query_info qinfo_tmp; /* placeholder for lookup_qinfo */
+ struct respip_client_info* cinfo = NULL, cinfo_tmp;
++ struct timeval wait_time;
++ struct check_request_result check_result = {0,0};
+ memset(&qinfo, 0, sizeof(qinfo));
+
+ if((error != NETEVENT_NOERROR && error != NETEVENT_DONE)|| !repinfo) {
+@@ -1295,6 +1397,20 @@
+ verbose(VERB_ALGO, "handle request called with err=%d", error);
+ return 0;
+ }
++
++ if (worker->env.cfg->sock_queue_timeout && timeval_isset(&c->recv_tv)) {
++ timeval_subtract(&wait_time, worker->env.now_tv, &c->recv_tv);
++ wait_queue_time = wait_time.tv_sec * 1000000 + wait_time.tv_usec;
++ if (worker->stats.max_query_time_us < wait_queue_time)
++ worker->stats.max_query_time_us = wait_queue_time;
++ if(wait_queue_time >
++ (long long)(worker->env.cfg->sock_queue_timeout * 1000000)) {
++ /* count and drop queries that were sitting in the socket queue too long */
++ worker->stats.num_queries_timed_out++;
++ return 0;
++ }
++ }
++
+ #ifdef USE_DNSCRYPT
+ repinfo->max_udp_size = worker->daemon->cfg->max_udp_size;
+ if(!dnsc_handle_curved_request(worker->daemon->dnscenv, repinfo)) {
+@@ -1304,7 +1420,8 @@
+ if(c->dnscrypt && !repinfo->is_dnscrypted) {
+ char buf[LDNS_MAX_DOMAINLEN+1];
+ /* Check if this is unencrypted and asking for certs */
+- if(worker_check_request(c->buffer, worker) != 0) {
++ worker_check_request(c->buffer, worker, &check_result);
++ if(check_result.value != 0) {
+ verbose(VERB_ALGO,
+ "dnscrypt: worker check request: bad query.");
+ log_addr(VERB_CLIENT,"from",&repinfo->client_addr,
+@@ -1346,31 +1463,34 @@
+ if(worker->dtenv.log_client_query_messages) {
+ log_addr(VERB_ALGO, "request from client", &repinfo->client_addr, repinfo->client_addrlen);
+ log_addr(VERB_ALGO, "to local addr", (void*)repinfo->c->socket->addr->ai_addr, repinfo->c->socket->addr->ai_addrlen);
+- dt_msg_send_client_query(&worker->dtenv, &repinfo->client_addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer);
++ dt_msg_send_client_query(&worker->dtenv, &repinfo->client_addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer,
++ ((worker->env.cfg->sock_queue_timeout && timeval_isset(&c->recv_tv))?&c->recv_tv:NULL));
+ }
+ #endif
+ /* Check deny/refuse ACLs */
+ if(repinfo->is_proxied) {
+ if((ret=deny_refuse_all(c, &acl, worker, repinfo, &acladdr,
+- worker->env.cfg->ede, 1)) != -1) {
++ worker->env.cfg->ede, 1, &check_result)) != -1) {
+ if(ret == 1)
+ goto send_reply;
+ return ret;
+ }
+ }
+ if((ret=deny_refuse_all(c, &acl, worker, repinfo, &acladdr,
+- worker->env.cfg->ede, 0)) != -1) {
++ worker->env.cfg->ede, 0, &check_result)) != -1) {
+ if(ret == 1)
+ goto send_reply;
+ return ret;
+ }
+
+- if((ret=worker_check_request(c->buffer, worker)) != 0) {
++ worker_check_request(c->buffer, worker, &check_result);
++ if(check_result.value != 0) {
+ verbose(VERB_ALGO, "worker check request: bad query.");
+ log_addr(VERB_CLIENT,"from",&repinfo->client_addr, repinfo->client_addrlen);
+- if(ret != -1) {
++ if(check_result.value != -1) {
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ check_result.value);
+ return 1;
+ }
+ comm_point_drop_reply(repinfo);
+@@ -1378,33 +1498,21 @@
+ }
+
+ worker->stats.num_queries++;
+-
+- /* check if this query should be dropped based on source ip rate limiting
+- * NOTE: we always check the repinfo->client_address. IP ratelimiting is
+- * implicitly disabled for proxies. */
+- if(!infra_ip_ratelimit_inc(worker->env.infra_cache,
+- &repinfo->client_addr, repinfo->client_addrlen,
+- *worker->env.now,
+- worker->env.cfg->ip_ratelimit_backoff, c->buffer)) {
+- /* See if we are passed through with slip factor */
+- if(worker->env.cfg->ip_ratelimit_factor != 0 &&
+- ub_random_max(worker->env.rnd,
+- worker->env.cfg->ip_ratelimit_factor) == 0) {
+- char addrbuf[128];
+- addr_to_str(&repinfo->client_addr,
+- repinfo->client_addrlen, addrbuf,
+- sizeof(addrbuf));
+- verbose(VERB_QUERY, "ip_ratelimit allowed through for "
+- "ip address %s because of slip in "
+- "ip_ratelimit_factor", addrbuf);
+- } else {
++ pre_edns_ip_ratelimit = pre_edns_ip_ratelimit_check(acl);
++
++ /* If the IP rate limiting check needs extra EDNS information (e.g.,
++ * DNS Cookies) postpone the check until after EDNS is parsed. */
++ if(pre_edns_ip_ratelimit) {
++ /* NOTE: we always check the repinfo->client_address.
++ * IP ratelimiting is implicitly disabled for proxies. */
++ if(!check_ip_ratelimit(worker, &repinfo->client_addr,
++ repinfo->client_addrlen, 0, c->buffer)) {
+ worker->stats.num_queries_ip_ratelimited++;
+ comm_point_drop_reply(repinfo);
+ return 0;
+ }
+ }
+
+- /* see if query is in the cache */
+ if(!query_info_parse(&qinfo, c->buffer)) {
+ verbose(VERB_ALGO, "worker parse request: formerror.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+@@ -1416,7 +1524,7 @@
+ }
+ sldns_buffer_rewind(c->buffer);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ goto send_reply;
+ }
+@@ -1425,21 +1533,21 @@
+ addr_to_str(&repinfo->client_addr, repinfo->client_addrlen, ip, sizeof(ip));
+ log_query_in(ip, qinfo.qname, qinfo.qtype, qinfo.qclass);
+ }
+- if(qinfo.qtype == LDNS_RR_TYPE_AXFR ||
++ if(qinfo.qtype == LDNS_RR_TYPE_AXFR ||
+ qinfo.qtype == LDNS_RR_TYPE_IXFR) {
+ verbose(VERB_ALGO, "worker request: refused zone transfer.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+ repinfo->client_addrlen);
+ sldns_buffer_rewind(c->buffer);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_REFUSED);
+ if(worker->stats.extended) {
+ worker->stats.qtype[qinfo.qtype]++;
+ }
+ goto send_reply;
+ }
+- if(qinfo.qtype == LDNS_RR_TYPE_OPT ||
++ if(qinfo.qtype == LDNS_RR_TYPE_OPT ||
+ qinfo.qtype == LDNS_RR_TYPE_TSIG ||
+ qinfo.qtype == LDNS_RR_TYPE_TKEY ||
+ qinfo.qtype == LDNS_RR_TYPE_MAILA ||
+@@ -1454,23 +1562,23 @@
+ }
+ sldns_buffer_rewind(c->buffer);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ if(worker->stats.extended) {
+ worker->stats.qtype[qinfo.qtype]++;
+ }
+ goto send_reply;
+ }
+- if((ret=parse_edns_from_query_pkt(c->buffer, &edns, worker->env.cfg, c,
+- worker->scratchpad)) != 0) {
++ if((ret=parse_edns_from_query_pkt(
++ c->buffer, &edns, worker->env.cfg, c, repinfo,
++ (worker->env.now ? *worker->env.now : time(NULL)),
++ worker->scratchpad)) != 0) {
+ struct edns_data reply_edns;
+ verbose(VERB_ALGO, "worker parse edns: formerror.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+ repinfo->client_addrlen);
+ memset(&reply_edns, 0, sizeof(reply_edns));
+ reply_edns.edns_present = 1;
+- reply_edns.udp_size = EDNS_ADVERTISED_SIZE;
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
+ error_encode(c->buffer, ret, &qinfo,
+ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+ sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns);
+@@ -1479,23 +1587,15 @@
+ }
+ if(edns.edns_present) {
+ if(edns.edns_version != 0) {
+- edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4);
+- edns.edns_version = EDNS_ADVERTISED_VERSION;
+- edns.udp_size = EDNS_ADVERTISED_SIZE;
+- edns.bits &= EDNS_DO;
+ edns.opt_list_in = NULL;
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+- edns.padding_block_size = 0;
+ verbose(VERB_ALGO, "query with bad edns version.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+ repinfo->client_addrlen);
+- error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo,
++ extended_error_encode(c->buffer, EDNS_RCODE_BADVERS, &qinfo,
+ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+- sldns_buffer_read_u16_at(c->buffer, 2), NULL);
+- if(sldns_buffer_capacity(c->buffer) >=
+- sldns_buffer_limit(c->buffer)+calc_edns_field_size(&edns))
+- attach_edns_record(c->buffer, &edns);
++ sldns_buffer_read_u16_at(c->buffer, 2), 0, &edns);
+ regional_free_all(worker->scratchpad);
+ goto send_reply;
+ }
+@@ -1508,6 +1608,62 @@
+ edns.udp_size = NORMAL_UDP_SIZE;
+ }
+ }
++
++ /* Get stats for cookies */
++ server_stats_downstream_cookie(&worker->stats, &edns);
++
++ /* If the IP rate limiting check was postponed, check now. */
++ if(!pre_edns_ip_ratelimit) {
++ /* NOTE: we always check the repinfo->client_address.
++ * IP ratelimiting is implicitly disabled for proxies. */
++ if(!check_ip_ratelimit(worker, &repinfo->client_addr,
++ repinfo->client_addrlen, edns.cookie_valid,
++ c->buffer)) {
++ worker->stats.num_queries_ip_ratelimited++;
++ comm_point_drop_reply(repinfo);
++ return 0;
++ }
++ }
++
++ /* "if, else if" sequence below deals with downstream DNS Cookies */
++ if(acl != acl_allow_cookie)
++ ; /* pass; No cookie downstream processing whatsoever */
++
++ else if(edns.cookie_valid)
++ ; /* pass; Valid cookie is good! */
++
++ else if(c->type != comm_udp)
++ ; /* pass; Stateful transport */
++
++ else if(edns.cookie_present) {
++ /* Cookie present, but not valid: Cookie was bad! */
++ extended_error_encode(c->buffer,
++ LDNS_EXT_RCODE_BADCOOKIE, &qinfo,
++ *(uint16_t*)(void *)
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_read_u16_at(c->buffer, 2),
++ 0, &edns);
++ regional_free_all(worker->scratchpad);
++ goto send_reply;
++ } else {
++ /* Cookie required, but no cookie present on UDP */
++ verbose(VERB_ALGO, "worker request: "
++ "need cookie or stateful transport");
++ log_addr(VERB_ALGO, "from",&repinfo->remote_addr
++ , repinfo->remote_addrlen);
++ EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
++ worker->scratchpad, LDNS_EDE_OTHER,
++ "DNS Cookie needed for UDP replies");
++ error_encode(c->buffer,
++ (LDNS_RCODE_REFUSED|BIT_TC), &qinfo,
++ *(uint16_t*)(void *)
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_read_u16_at(c->buffer, 2),
++ &edns);
++ regional_free_all(worker->scratchpad);
++ goto send_reply;
++ }
++
+ if(edns.udp_size > worker->daemon->cfg->max_udp_size &&
+ c->type == comm_udp) {
+ verbose(VERB_QUERY,
+@@ -1523,10 +1679,10 @@
+ repinfo->client_addrlen);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+ LDNS_TC_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_SERVFAIL);
+ sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
+- sldns_buffer_write_at(c->buffer, 4,
++ sldns_buffer_write_at(c->buffer, 4,
+ (uint8_t*)"\0\0\0\0\0\0\0\0", 8);
+ sldns_buffer_flip(c->buffer);
+ regional_free_all(worker->scratchpad);
+@@ -1593,7 +1749,7 @@
+ /* We've looked in our local zones. If the answer isn't there, we
+ * might need to bail out based on ACLs now. */
+ if((ret=deny_refuse_non_local(c, acl, worker, repinfo, acladdr,
+- worker->env.cfg->ede)) != -1)
++ worker->env.cfg->ede, &check_result)) != -1)
+ {
+ regional_free_all(worker->scratchpad);
+ if(ret == 1)
+@@ -1612,7 +1768,7 @@
+ * ACLs allow the snooping. */
+ if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) &&
+ acl != acl_allow_snoop ) {
+- if (worker->env.cfg->ede) {
++ if(worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
+ worker->scratchpad, LDNS_EDE_NOT_AUTHORITATIVE, "");
+ }
+@@ -1745,8 +1901,8 @@
+
+ if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) {
+ if(answer_norec_from_cache(worker, &qinfo,
+- *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+- sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
++ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
++ sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
+ &edns)) {
+ regional_free_all(worker->scratchpad);
+ goto send_reply;
+@@ -1825,10 +1981,10 @@
+ return rc;
+ }
+
+-void
++void
+ worker_sighandler(int sig, void* arg)
+ {
+- /* note that log, print, syscalls here give race conditions.
++ /* note that log, print, syscalls here give race conditions.
+ * And cause hangups if the log-lock is held by the application. */
+ struct worker* worker = (struct worker*)arg;
+ switch(sig) {
+@@ -1903,13 +2059,13 @@
+ comm_timer_set(worker->env.probe_timer, &tv);
+ }
+
+-struct worker*
++struct worker*
+ worker_create(struct daemon* daemon, int id, int* ports, int n)
+ {
+ unsigned int seed;
+- struct worker* worker = (struct worker*)calloc(1,
++ struct worker* worker = (struct worker*)calloc(1,
+ sizeof(struct worker));
+- if(!worker)
++ if(!worker)
+ return NULL;
+ worker->numports = n;
+ worker->ports = (int*)memdup(ports, sizeof(int)*n);
+@@ -1937,7 +2093,7 @@
+ }
+
+ int
+-worker_init(struct worker* worker, struct config_file *cfg,
++worker_init(struct worker* worker, struct config_file *cfg,
+ struct listen_port* ports, int do_sigs)
+ {
+ #ifdef USE_DNSTAP
+@@ -1970,9 +2126,9 @@
+ #endif
+ ub_thread_sig_unblock(SIGTERM);
+ #ifndef LIBEVENT_SIGNAL_PROBLEM
+- worker->comsig = comm_signal_create(worker->base,
++ worker->comsig = comm_signal_create(worker->base,
+ worker_sighandler, worker);
+- if(!worker->comsig
++ if(!worker->comsig
+ #ifdef SIGHUP
+ || !comm_signal_bind(worker->comsig, SIGHUP)
+ #endif
+@@ -1989,7 +2145,7 @@
+ return 0;
+ }
+ #endif /* LIBEVENT_SIGNAL_PROBLEM */
+- if(!daemon_remote_open_accept(worker->daemon->rc,
++ if(!daemon_remote_open_accept(worker->daemon->rc,
+ worker->daemon->rc_ports, worker)) {
+ worker_delete(worker);
+ return 0;
+@@ -2023,8 +2179,8 @@
+ return 0;
+ }
+ worker->back = outside_network_create(worker->base,
+- cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports,
+- cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6,
++ cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports,
++ cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6,
+ cfg->do_tcp?cfg->outgoing_num_tcp:0, cfg->ip_dscp,
+ worker->daemon->env->infra_cache, worker->rndstate,
+ cfg->use_caps_bits_for_id, worker->ports, worker->numports,
+@@ -2049,13 +2205,13 @@
+ worker_delete(worker);
+ return 0;
+ }
+- worker->stat_timer = comm_timer_create(worker->base,
++ worker->stat_timer = comm_timer_create(worker->base,
+ worker_stat_timer_cb, worker);
+ if(!worker->stat_timer) {
+ log_err("could not create statistics timer");
+ }
+
+- /* we use the msg_buffer_size as a good estimate for what the
++ /* we use the msg_buffer_size as a good estimate for what the
+ * user wants for memory usage sizes */
+ worker->scratchpad = regional_create_custom(cfg->msg_buffer_size);
+ if(!worker->scratchpad) {
+@@ -2164,23 +2320,24 @@
+ worker_mem_report(worker, NULL);
+ /* if statistics enabled start timer */
+ if(worker->env.cfg->stat_interval > 0) {
+- verbose(VERB_ALGO, "set statistics interval %d secs",
++ verbose(VERB_ALGO, "set statistics interval %d secs",
+ worker->env.cfg->stat_interval);
+ worker_restart_timer(worker);
+ }
++ pp_init(&sldns_write_uint16, &sldns_write_uint32);
+ return 1;
+ }
+
+-void
++void
+ worker_work(struct worker* worker)
+ {
+ comm_base_dispatch(worker->base);
+ }
+
+-void
++void
+ worker_delete(struct worker* worker)
+ {
+- if(!worker)
++ if(!worker)
+ return;
+ if(worker->env.mesh && verbosity >= VERB_OPS) {
+ server_stats_log(&worker->stats, worker, worker->thread_num);
+@@ -2232,7 +2389,7 @@
+ struct worker* worker = q->env->worker;
+ struct outbound_entry* e = (struct outbound_entry*)regional_alloc(
+ q->region, sizeof(*e));
+- if(!e)
++ if(!e)
+ return NULL;
+ e->qstate = q;
+ e->qsent = outnet_serviced_query(worker->back, qinfo, flags, dnssec,
+@@ -2246,7 +2403,7 @@
+ return e;
+ }
+
+-void
++void
+ worker_alloc_cleanup(void* arg)
+ {
+ struct worker* worker = (struct worker*)arg;
+@@ -2294,7 +2451,7 @@
+ return 0;
+ }
+
+-int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
++int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(reply_info))
+ {
+--- contrib/unbound/dns64/dns64.c.orig
++++ contrib/unbound/dns64/dns64.c
+@@ -59,7 +59,7 @@
+ ******************************************************************************/
+
+ /**
+- * This is the default DNS64 prefix that is used whent he dns64 module is listed
++ * This is the default DNS64 prefix that is used when the dns64 module is listed
+ * in module-config but when the dns64-prefix variable is not present.
+ */
+ static const char DEFAULT_DNS64_PREFIX[] = "64:ff9b::/96";
+@@ -573,28 +573,29 @@
+ handle_event_pass(struct module_qstate* qstate, int id)
+ {
+ struct dns64_qstate* iq = (struct dns64_qstate*)qstate->minfo[id];
+- if (iq && iq->state == DNS64_NEW_QUERY
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
+- && qstate->qinfo.qname_len == 74
+- && !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa"))
+- /* Handle PTR queries for IPv6 addresses. */
+- return handle_ipv6_ptr(qstate, id);
+-
+- if (qstate->env->cfg->dns64_synthall &&
+- iq && iq->state == DNS64_NEW_QUERY
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA)
+- return generate_type_A_query(qstate, id);
++ int synth_all_cfg = qstate->env->cfg->dns64_synthall;
++ int synth_qname = 0;
++
++ if(iq && iq->state == DNS64_NEW_QUERY
++ && qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
++ && qstate->qinfo.qname_len == 74
++ && !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa")) {
++ /* Handle PTR queries for IPv6 addresses. */
++ return handle_ipv6_ptr(qstate, id);
++ }
+
+- if(dns64_always_synth_for_qname(qstate, id) &&
+- iq && iq->state == DNS64_NEW_QUERY
+- && !(qstate->query_flags & BIT_CD)
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) {
+- verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
++ if(iq && iq->state == DNS64_NEW_QUERY &&
++ qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
++ (synth_all_cfg ||
++ (synth_qname=(dns64_always_synth_for_qname(qstate, id)
++ && !(qstate->query_flags & BIT_CD))))) {
++ if(synth_qname)
++ verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
+ return generate_type_A_query(qstate, id);
+ }
+
+ /* We are finished when our sub-query is finished. */
+- if (iq && iq->state == DNS64_SUBQUERY_FINISHED)
++ if(iq && iq->state == DNS64_SUBQUERY_FINISHED)
+ return module_finished;
+
+ /* Otherwise, pass request to next module. */
+@@ -627,32 +628,37 @@
+ * synthesize in (sec 5.1.2 of RFC6147).
+ * - A successful AAAA query with an answer.
+ */
+- if((!iq || iq->state != DNS64_INTERNAL_QUERY)
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA
+- && !(qstate->query_flags & BIT_CD)
+- && !(qstate->return_msg &&
+- qstate->return_msg->rep &&
+- reply_find_answer_rrset(&qstate->qinfo,
+- qstate->return_msg->rep)))
+- /* not internal, type AAAA, not CD, and no answer RRset,
+- * So, this is a AAAA noerror/nodata answer */
+- return generate_type_A_query(qstate, id);
+
+- if((!iq || iq->state != DNS64_INTERNAL_QUERY)
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA
+- && !(qstate->query_flags & BIT_CD)
+- && dns64_always_synth_for_qname(qstate, id)) {
+- /* if it is not internal, AAAA, not CD and listed domain,
+- * generate from A record and ignore AAAA */
+- verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
++ /* When an AAAA query completes check if we want to perform DNS64
++ * synthesis. We skip queries with DNSSEC enabled (!CD) and
++ * ones generated by us to retrive the A/PTR record to use for
++ * synth. */
++ int could_synth =
++ qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
++ (!iq || iq->state != DNS64_INTERNAL_QUERY) &&
++ !(qstate->query_flags & BIT_CD);
++ int has_data = /* whether query returned non-empty rrset */
++ qstate->return_msg &&
++ qstate->return_msg->rep &&
++ reply_find_answer_rrset(&qstate->qinfo, qstate->return_msg->rep);
++ int synth_qname = 0;
++
++ if(could_synth &&
++ (!has_data ||
++ (synth_qname=dns64_always_synth_for_qname(qstate, id)))) {
++ if(synth_qname)
++ verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
+ return generate_type_A_query(qstate, id);
+ }
+
+ /* Store the response in cache. */
+- if ( (!iq || !iq->started_no_cache_store) &&
+- qstate->return_msg && qstate->return_msg->rep &&
+- !dns_cache_store(qstate->env, &qstate->qinfo, qstate->return_msg->rep,
+- 0, 0, 0, NULL, qstate->query_flags, qstate->qstarttime))
++ if( (!iq || !iq->started_no_cache_store) &&
++ qstate->return_msg &&
++ qstate->return_msg->rep &&
++ !dns_cache_store(
++ qstate->env, &qstate->qinfo, qstate->return_msg->rep,
++ 0, 0, 0, NULL,
++ qstate->query_flags, qstate->qstarttime))
+ log_err("out of memory");
+
+ /* do nothing */
+@@ -841,7 +847,7 @@
+ cp = construct_reply_info_base(super->region, rep->flags, rep->qdcount,
+ rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl,
+ rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets,
+- rep->rrset_count, rep->security);
++ rep->rrset_count, rep->security, LDNS_EDE_NONE);
+ if(!cp)
+ return;
+
+@@ -969,10 +975,19 @@
+ }
+ super_dq->state = DNS64_SUBQUERY_FINISHED;
+
+- /* If there is no successful answer, we're done. */
+- if (qstate->return_rcode != LDNS_RCODE_NOERROR
+- || !qstate->return_msg
+- || !qstate->return_msg->rep) {
++ /* If there is no successful answer, we're done.
++ * Guarantee that we have at least a NOERROR reply further on. */
++ if(qstate->return_rcode != LDNS_RCODE_NOERROR
++ || !qstate->return_msg
++ || !qstate->return_msg->rep) {
++ return;
++ }
++
++ /* When no A record is found for synthesis fall back to AAAA again. */
++ if(qstate->qinfo.qtype == LDNS_RR_TYPE_A &&
++ !reply_find_answer_rrset(&qstate->qinfo,
++ qstate->return_msg->rep)) {
++ super_dq->state = DNS64_INTERNAL_QUERY;
+ return;
+ }
+
+--- contrib/unbound/dnstap/dnstap.c.orig
++++ contrib/unbound/dnstap/dnstap.c
+@@ -388,12 +388,15 @@
+ struct sockaddr_storage *qsock,
+ struct sockaddr_storage *rsock,
+ enum comm_point_type cptype,
+- sldns_buffer *qmsg)
++ sldns_buffer *qmsg,
++ struct timeval* tstamp)
+ {
+ struct dt_msg dm;
+ struct timeval qtime;
+
+- gettimeofday(&qtime, NULL);
++ if(tstamp)
++ memcpy(&qtime, tstamp, sizeof(qtime));
++ else gettimeofday(&qtime, NULL);
+
+ /* type */
+ dt_msg_init(env, &dm, DNSTAP__MESSAGE__TYPE__CLIENT_QUERY);
+--- contrib/unbound/dnstap/dnstap.h.orig
++++ contrib/unbound/dnstap/dnstap.h
+@@ -126,13 +126,15 @@
+ * @param rsock: local (service) address/port.
+ * @param cptype: comm_udp or comm_tcp.
+ * @param qmsg: query message.
++ * @param tstamp: timestamp or NULL if none provided.
+ */
+ void
+ dt_msg_send_client_query(struct dt_env *env,
+ struct sockaddr_storage *qsock,
+ struct sockaddr_storage *rsock,
+ enum comm_point_type cptype,
+- struct sldns_buffer *qmsg);
++ struct sldns_buffer *qmsg,
++ struct timeval* tstamp);
+
+ /**
+ * Create and send a new dnstap "Message" event of type CLIENT_RESPONSE.
+--- contrib/unbound/dnstap/dnstap.m4.orig
++++ contrib/unbound/dnstap/dnstap.m4
+@@ -41,7 +41,7 @@
+ fi
+ ])
+ AC_SEARCH_LIBS([protobuf_c_message_pack], [protobuf-c], [],
+- AC_MSG_ERROR([The protobuf-c library was not found. Please install protobuf-c!]))
++ AC_MSG_ERROR([The protobuf-c library was not found. Please install the development libraries for protobuf-c!]))
+ $2
+ else
+ $3
+--- contrib/unbound/dnstap/dtstream.c.orig
++++ contrib/unbound/dnstap/dtstream.c
+@@ -788,7 +788,7 @@
+ }
+ return -1;
+ }
+- log_crypto_err("dnstap io, could not SSL_write");
++ log_crypto_err_io("dnstap io, could not SSL_write", want);
+ return -1;
+ }
+ return r;
+@@ -1029,7 +1029,7 @@
+ "other side");
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ verbose(VERB_DETAIL, "dnstap io: output closed by the "
+ "other side");
+ return 0;
+@@ -1431,8 +1431,8 @@
+ } else {
+ unsigned long err = ERR_get_error();
+ if(!squelch_err_ssl_handshake(err)) {
+- log_crypto_err_code("dnstap io, ssl handshake failed",
+- err);
++ log_crypto_err_io_code("dnstap io, ssl handshake failed",
++ want, err);
+ verbose(VERB_OPS, "dnstap io, ssl handshake failed "
+ "from %s", dtio->ip_str);
+ }
+--- contrib/unbound/dnstap/unbound-dnstap-socket.c.orig
++++ contrib/unbound/dnstap/unbound-dnstap-socket.c
+@@ -61,6 +61,7 @@
+ #include "services/listen_dnsport.h"
+ #include "sldns/sbuffer.h"
+ #include "sldns/wire2str.h"
++#include "sldns/pkthdr.h"
+ #ifdef USE_DNSTAP
+ #include <protobuf-c/protobuf-c.h>
+ #include "dnstap/dnstap.pb-c.h"
+@@ -448,6 +449,7 @@
+ char buf[300];
+ /* header, name, type, class minimum to get the query tuple */
+ if(message.len < 12 + 1 + 4 + 4) return NULL;
++ if(LDNS_QDCOUNT(message.data) < 1) return NULL;
+ if(sldns_wire2str_rrquestion_buf(message.data+12, message.len-12,
+ buf, sizeof(buf)) != 0) {
+ /* remove trailing newline, tabs to spaces */
+@@ -502,7 +504,7 @@
+ time_t time_t_sec;
+ memset(&tv, 0, sizeof(tv));
+ if(has_time_sec) tv.tv_sec = time_sec;
+- if(has_time_nsec) tv.tv_usec = time_nsec;
++ if(has_time_nsec) tv.tv_usec = time_nsec/1000;
+
+ buf[0]=0;
+ time_t_sec = tv.tv_sec;
+@@ -706,7 +708,7 @@
+ (data->id?data->id:""));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ if(verbosity) log_info("dnstap client stream closed from %s",
+ (data->id?data->id:""));
+ return 0;
+@@ -758,10 +760,11 @@
+ fd_set_block(data->fd);
+ if(data->ssl) {
+ if((r=SSL_write(data->ssl, acceptframe, len)) <= 0) {
+- if(SSL_get_error(data->ssl, r) == SSL_ERROR_ZERO_RETURN)
++ int r2;
++ if((r2=SSL_get_error(data->ssl, r)) == SSL_ERROR_ZERO_RETURN)
+ log_err("SSL_write, peer closed connection");
+ else
+- log_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", r2);
+ fd_set_nonblock(data->fd);
+ free(acceptframe);
+ return 0;
+@@ -789,7 +792,7 @@
+
+ /** reply with FINISH control frame to bidirectional client,
+ * returns 0 on error */
+-static int reply_with_finish(int fd)
++static int reply_with_finish(struct tap_data* data)
+ {
+ #ifdef USE_DNSTAP
+ size_t len = 0;
+@@ -799,21 +802,35 @@
+ return 0;
+ }
+
+- fd_set_block(fd);
+- if(send(fd, finishframe, len, 0) == -1) {
+- log_err("send failed: %s", sock_strerror(errno));
+- fd_set_nonblock(fd);
+- free(finishframe);
+- return 0;
++ fd_set_block(data->fd);
++ if(data->ssl) {
++ int r;
++ if((r=SSL_write(data->ssl, finishframe, len)) <= 0) {
++ int r2;
++ if((r2=SSL_get_error(data->ssl, r)) == SSL_ERROR_ZERO_RETURN)
++ log_err("SSL_write, peer closed connection");
++ else
++ log_crypto_err_io("could not SSL_write", r2);
++ fd_set_nonblock(data->fd);
++ free(finishframe);
++ return 0;
++ }
++ } else {
++ if(send(data->fd, finishframe, len, 0) == -1) {
++ log_err("send failed: %s", sock_strerror(errno));
++ fd_set_nonblock(data->fd);
++ free(finishframe);
++ return 0;
++ }
+ }
+ if(verbosity) log_info("sent control frame(finish)");
+
+- fd_set_nonblock(fd);
++ fd_set_nonblock(data->fd);
+ free(finishframe);
+ return 1;
+ #else
+ log_err("no dnstap compiled, no reply");
+- (void)fd;
++ (void)data;
+ return 0;
+ #endif
+ }
+@@ -933,7 +950,7 @@
+ #endif /* HAVE_SSL */
+
+ /** callback for dnstap listener */
+-void dtio_tap_callback(int fd, short ATTR_UNUSED(bits), void* arg)
++void dtio_tap_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), void* arg)
+ {
+ struct tap_data* data = (struct tap_data*)arg;
+ if(verbosity>=3) log_info("tap callback");
+@@ -1016,7 +1033,7 @@
+ }
+ } else if(data->len >= 4 && sldns_read_uint32(data->frame) ==
+ FSTRM_CONTROL_FRAME_STOP && data->is_bidirectional) {
+- if(!reply_with_finish(fd)) {
++ if(!reply_with_finish(data)) {
+ tap_data_free(data);
+ return;
+ }
+--- contrib/unbound/doc/Changelog.orig
++++ contrib/unbound/doc/Changelog
+@@ -1,3 +1,476 @@
++2 November 2023: Wouter
++ - Set version number to 1.19.0.
++ - Tag for 1.19.0rc1 release.
++
++1 November 2023: George
++ - Mention flex and bison in README.md when building from repository
++ source.
++
++1 November 2023: Wouter
++ - Fix SSL compile failure for definition in log_crypto_err_io_code_arg.
++ - Fix SSL compile failure for other missing definitions in
++ log_crypto_err_io_code_arg.
++ - Fix compilation without openssl, remove unused function warning.
++
++31 October 2023: George
++ - Fix #941: dnscrypt doesn't work after upgrade to 1.18 with
++ suggestion by dukeartem to also fix the udp_ancil with dnscrypt.
++
++30 October 2023: George
++ - Merge #930 from Stuart Henderson: add void to
++ log_ident_revert_to_default declaration.
++
++30 October 2023: Wouter
++ - autoconf.
++
++24 October 2023: George
++ - Clearer configure text for missing protobuf-c development libraries.
++
++20 October 2023: Wouter
++ - Merge #951: Cachedb no store. The cachedb-no-store: yes option is
++ used to stop cachedb from writing messages to the backend storage.
++ It reads messages when data is available from the backend. The
++ default is no.
++
++19 October 2023: Wouter
++ - Fix to print detailed errors when an SSL IO routine fails via
++ SSL_get_error.
++
++18 October 2023: George
++ - Mailing list patches from Daniel Gröber for DNS64 fallback to plain
++ AAAA when no A record exists for synthesis, and minor DNS64 code
++ refactoring for better readability.
++ - Fixes for the DNS64 patches.
++ - Update the dns64_lookup.rpl test for the DNS64 fallback patch.
++ - Merge #955 from buevsan: fix ipset wrong behavior.
++ - Update testdata/ipset.tdir test for ipset fix.
++
++17 October 2023: Wouter
++ - Fix #954: Inconsistent RPZ handling for A record returned along with
++ CNAME.
++
++16 October 2023: George
++ - Expose the script filename in the Python module environment 'mod_env'
++ instead of the config_file structure which includes the linked list
++ of scripts in a multi Python module setup; fixes #79.
++ - Expose the configured listening and outgoing interfaces, if any, as
++ a list of strings in the Python 'config_file' class instead of the
++ current Swig object proxy; fixes #79.
++ - For multi Python module setups, clean previously parsed module
++ functions in __main__'s dictionary, if any, so that only current
++ module functions are registered.
++
++13 October 2023: George
++ - Better fix for infinite loop when reading multiple lines of input on
++ a broken remote control socket, by treating a zero byte line the
++ same as transmission end. Addesses #947 and #948.
++
++12 October 2023: Wouter
++ - Merge #944: Disable EDNS DO.
++ Disable the EDNS DO flag in upstream requests. This can be helpful
++ for devices that cannot handle DNSSEC information. But it should not
++ be enabled otherwise, because that would stop DNSSEC validation. The
++ DNSSEC validation would not work for Unbound itself, and also not
++ for downstream users. Default is no. The option
++ is disable-edns-do: no
++
++11 October 2023: George
++ - Fix #850: [FR] Ability to use specific database in Redis, with new
++ redis-logical-db configuration option.
++
++11 October 2023: Wouter
++ - Fix #949: "could not create control compt".
++ - Fix that cachedb does not warn when serve-expired is disabled about
++ use of serve-expired-reply-ttl and serve-expired-client-timeout.
++ - Fix for #949: Fix pythonmod/ubmodule-tst.py for Python 3.x.
++
++10 October 2023: George
++ - Fix infinite loop when reading multiple lines of input on a broken
++ remote control socket. Addesses #947 and #948.
++
++9 October 2023: Wouter
++ - Fix edns subnet so that queries with a source prefix of zero cause
++ the recursor send no edns subnet option to the upstream.
++ - Fix that printout of EDNS options shows the EDNS cookie option by
++ name.
++
++4 October 2023: Wouter
++ - Fix #946: Forwarder returns servfail on upstream response noerror no
++ data.
++
++3 October 2023: George
++ - Merge #881: Generalise the proxy protocol code.
++
++2 October 2023: George
++ - Fix misplaced comment.
++
++22 September 2023: Wouter
++ - Fix #942: 1.18.0 libunbound DNS regression when built without
++ OpenSSL.
++
++18 September 2023: Wouter
++ - Fix rpz tcp-only action with rpz triggers nsdname and nsip.
++
++15 September 2023: Wouter
++ - Merge #936: Check for c99 with autoconf versions prior to 2.70.
++ - Fix to remove two c99 notations.
++
++14 September 2023: Wouter
++ - Fix authority zone answers for obscured DNAMEs and delegations.
++
++8 September 2023: Wouter
++ - Fix send of udp retries when ENOBUFS is returned. It stops looping
++ and also waits for the condition to go away. Reported by Florian
++ Obser.
++
++7 September 2023: Wouter
++ - Fix to scrub resource records of type A and AAAA that have an
++ inappropriate size. They are removed from responses.
++ - Fix to move msgparse_rrset_remove_rr code to util/msgparse.c.
++ - Fix to add EDE text when RRs have been removed due to length.
++ - Fix to set ede match in unit test for rr length removal.
++ - Fix to print EDE text in readable form in output logs.
++
++6 September 2023: Wouter
++ - Merge #931: Prevent warnings from -Wmissing-prototypes.
++
++31 August 2023: Wouter
++ - Fix autoconf 2.69 warnings in configure.
++ - Fix #927: unbound 1.18.0 make test error. Fix make test without SHA1.
++
++30 August 2023: Wouter
++ - Fix for WKS call to getservbyname that creates allocation on exit
++ in unit test by testing numbers first and testing from the services
++ list later.
++
++28 August 2023: Wouter
++ - Fix for version generation race condition that ignored changes.
++
++25 August 2023: Wouter
++ - Fix compile error on NetBSD in util/netevent.h.
++
++23 August 2023: Wouter
++ - Tag for 1.18.0rc1 release. This became the 1.18.0 release on
++ 30 aug 2023, with the fix from 25 aug, fix compile on NetBSD
++ included. The repository continues with version 1.18.1.
++
++22 August 2023: Wouter
++ - Set version number to 1.18.0.
++
++21 August 2023: Wouter
++ - Debug Windows ci workflow.
++ - Fix windows ci workflow to install bison and flex.
++ - Fix for #925: unbound.service: Main process exited, code=killed,
++ status=11/SEGV. Fixes cachedb configuration handling.
++ - Fix #923: processQueryResponse() THROWAWAY should be mindful of
++ fail_reply.
++ - Fix unit test for unbound-control to work when threads are disabled,
++ and fix cache dump check.
++
++18 August 2023: Wouter
++ - Fix for iter_dec_attempts that could cause a hang, part of
++ capsforid and qname minimisation, depending on the settings.
++ - Fix uninitialized memory passed in padding bytes of cmsg to sendmsg.
++ - Fix stat_values test to work with dig that enables DNS cookies.
++
++17 August 2023: Wouter
++ - Merge PR #762: Downstream DNS Server Cookies a la RFC7873 and
++ RFC9018. Create server cookies for clients that send client cookies.
++ This needs to be explicitly turned on in the config file with:
++ `answer-cookie: yes`. A `cookie-secret:` can be configured for
++ anycast setups. Without one, a random cookie secret is generated.
++ The acl option `allow_cookie` allows queries with either a valid
++ cookie or over a stateful transport. The statistics output has
++ `queries_cookie_valid` and `queries_cookie_client` and
++ `queries_cookie_invalid` information. The `ip\-ratelimit\-cookie:`
++ value determines a rate limit for queries with cookies, if desired.
++ - Fix regional_alloc_init for potential unaligned source of the copy.
++ - Fix ip_ratelimit test to work with dig that enables DNS cookies.
++
++2 August 2023: George
++ - Move a cache reply callback in worker.c closer to the cache reply
++ generation.
++
++1 August 2023: George
++ - Merge #911 from natalie-reece: Exclude EDE before other EDNS options
++ when there isn't enough space.
++ - For #911: Try to trim EXTRA-TEXT (and LDNS_EDE_OTHER options
++ altogether) before giving up on attaching EDE options.
++ - More braces and formatting for Fix for EDNS EDE size calculation to
++ avoid future bugs.
++ - Fix to use the now cached EDE, if any, for CD_bit queries.
++
++1 August 2023: Wouter
++ - Fix for EDNS EDE size calculation.
++
++31 July 2023: George
++ - Merge #790 from Tom Carpay: Add support for EDE caching in cachedb
++ and subnetcache.
++
++31 July 2023: Wouter
++ - iana portlist update.
++
++30 July 2023: George
++ - Merge #759 from Tom Carpay: Add EDE (RFC8914) caching.
++
++28 July 2023: George
++ - Fix unused variable compile warning for kernel timestamps in
++ netevent.c
++
++21 July 2023: George
++ - Merge #857 from eaglegai: fix potential memory leaks when errors
++ happen.
++ - For #857: fix mixed declarations and code.
++ - Merge #118 from mibere: Changed verbosity level for Redis init &
++ deinit.
++ - Merge #390 from Frank Riley: Add missing callbacks to the python
++ module.
++ - Cleaner failure code for callback functions in interface.i.
++ - Merge #889 from borisVanhoof: Free memory in error case + remove
++ unused function.
++ - For #889: use netcat-openbsd instead of netcat-traditional.
++ - For #889: Account for num_detached_states before possible
++ mesh_state_delete when erroring out.
++
++20 July 2023: George
++ - Merge #909 from headshog: Numeric truncation when parsing TYPEXX and
++ CLASSXX representation.
++ - For #909: Fix return values.
++ - Merge #901 from Sergei Trofimovich: config: improve handling of
++ unknown modules.
++
++20 July 2023: Wouter
++ - For #909: Fix RR class comparison.
++
++14 July 2023: George
++ - More clear description of the different auth-zone behaviors on the
++ man page.
++
++13 July 2023: George
++ - Merge #880 from chipitsine: services/authzone.c: remove redundant
++ check.
++
++11 July 2023: George
++ - Merge #664 from tilan7763: Add prefetch support for subnet cache
++ entries.
++ - For #664: Easier code flow for subnetcache prefetching.
++ - For #664: Add testcase.
++ - For #664: Rename subnet_prefetch tests to subnet_global_prefetch to
++ differentiate from the new subnet prefetch support.
++
++3 July 2023: George
++ - Merge #739: Add SVCB dohpath support.
++ - Code cleanup for sldns_str2wire_svcparam_key_lookup.
++ - Merge #802: add validation EDEs to queries where the CD bit is set.
++ - For #802: Cleanup comments and add RCODE check for CD bit test case.
++ - Skip the 00-lint test. splint is not maintained; it either does not
++ work or produces false positives. Static analysis is handled in the
++ clang test.
++
++3 July 2023: Wouter
++ - Fix #906: warning: ‘Py_SetProgramName’ is deprecated.
++ - Fix dereference of NULL variable warning in mesh_do_callback.
++
++29 June 2023: George
++ - More fixes for reference counting for python module and clean up
++ failure code.
++ - Merge #827 from rcmcdonald91: Eliminate unnecessary Python reloading
++ which causes memory leaks.
++
++29 June 2023: Wouter
++ - Fix python modules with multiple scripts, by incrementing reference
++ counts.
++
++27 June 2023: George
++ - Merge #892: Add cachedb hit stat. Introduces 'num.query.cachedb' as
++ a new statistical counter.
++ - Remove warning about unknown cast-function-type warning pragma.
++
++22 June 2023: Wouter
++ - Merge #903: contrib: add yocto compatible init script.
++
++15 June 2023: Philip
++ - Fix for issue #887 (Timeouts to forward servers on BSD based
++ system with ASLR)
++ - Probably fixes #516 (Stream reuse does not work on Windows) as well
++
++14 June 2023: George
++ - Properly handle all return values of worker_check_request during
++ early EDE code.
++ - Do not check the incoming request more than once.
++
++12 June 2023: Wouter
++ - Merge #896: Fix: #895: pythonmodule: add all site-packages
++ directories to sys.path.
++ - Fix #895: python + sysconfig gives ANOTHER path comparing to
++ distutils.
++ - Fix for uncertain unit test for doh buffer size events.
++
++25 May 2023: Wouter
++ - Fix unbound-dnstap-socket printout when no query is present.
++ - Fix unbound-dnstap-socket time fraction conversion for printout.
++
++19 May 2023: Wouter
++ - Fix RPZ removal of client-ip, nsip, nsdname triggers from IXFR.
++ - Fix to remove unused variables from RPZ clientip data structure.
++
++16 May 2023: Wouter
++ - Fix #888: [FR] Use kernel timestamps for dnstap.
++ - Fix to print debug log for ancillary data with correct IP address.
++
++11 May 2023: Wouter
++ - Fix warning in windows compile, in set_recvtimestamp.
++
++4 May 2023: Wouter
++ - Fix #885: Error: util/configlexer.c: No such file or directory,
++ adds error messages explaining to install flex and bison.
++ - Fix to remove unused whitespace from acx_nlnetlabs.m4 and config.h.
++ - Fix doxygen in addr_to_nat64 header definition.
++
++1 May 2023: George
++ - Merge #722 from David 'eqvinox' Lamparter: NAT64 support.
++ - For #722: minor fixes, formatting, refactoring.
++
++1 May 2023: Wouter
++ - Fix RPZ IP responses with trigger rpz-drop on cache entries, that
++ they are dropped.
++
++26 April 2023: Philip
++ - Fix issue #860: Bad interaction with 0 TTL records and serve-expired
++
++26 April 2023: Wouter
++ - Merge #882 from vvfedorenko: Features/dropqueuedpackets, with
++ sock-queue-timeout option that drops packets that have been in the
++ socket queue for too long. Added statistics num.queries_timed_out
++ and query.queue_time_us.max that track the socket queue timeouts.
++ - Fix for #882: small changes, date updated in Copyright for
++ util/timeval_func.c and util/timeval_func.h. Man page entries and
++ example entry.
++ - Fix for #882: document variable to stop doxygen warning.
++
++19 April 2023: Wouter
++ - Fix for #878: Invalid IP address in unbound.conf causes Segmentation
++ Fault on OpenBSD.
++
++14 April 2023: Wouter
++ - Merge #875: change obsolete txt URL in unbound-anchor.c to point
++ to RFC 7958, and Fix #874.
++
++13 April 2023: Wouter
++ - Fix build badge, from failing travis link to github ci action link.
++
++6 April 2023: Wouter
++ - Fix for #870: Add test case for the qname minimisation and CNAME.
++
++4 April 2023: Wouter
++ - Fix #870: NXDOMAIN instead of NOERROR rcode when asked for existing
++ CNAME record.
++
++24 March 2023: Philip
++ - Fix issue #676: Unencrypted query is sent when
++ forward-tls-upstream: yes is used without tls-cert-bundle
++ - Extra consistency check to make sure that when TLS is requested,
++ either we set up a TLS connection or we return an error.
++
++21 March 2023: Philip
++ - Fix issue #851: reserved identifier violation
++
++20 March 2023: Wouter
++ - iana portlist update.
++
++17 March 2023: George
++ - Fix #812, fix #846, by using the SSL_OP_IGNORE_UNEXPECTED_EOF option
++ to ignore the unexpected eof while reading in openssl >= 3.
++
++16 March 2023: Wouter
++ - Fix ssl.h include brackets, instead of quotes.
++
++14 March 2023: Wouter
++ - Fix unbound-dnstap-socket test program to reply the finish frame
++ over a TLS connection correctly.
++
++23 February 2023: Wouter
++ - Fix for #852: Completion of error handling.
++
++21 February 2023: Philip
++ - Fix #825: Unexpected behavior with client-subnet-always-forward
++ and serve-expired
++
++10 February 2023: George
++ - Clean up iterator/iterator.c::error_response_cache() and allow for
++ better interaction with serve-expired, prefetch and cached error
++ responses.
++
++9 February 2023: George
++ - Allow TTL refresh of expired error responses.
++ - Add testcase for refreshing expired error responses.
++
++9 February 2023: Wouter
++ - Fix to ignore entirely empty responses, and try at another authority.
++ This turns completely empty responses, a type of noerror/nodata into
++ a servfail, but they do not conform to RFC2308, and the retry can
++ fetch improved content.
++ - Fix unit tests for spurious empty messages.
++ - Fix consistency of unit test without roundrobin answers for the
++ cnametooptout unit test.
++ - Fix to git ignore the library symbol file that configure can create.
++
++8 February 2023: Wouter
++ - Fix #841: Unbound won't build with aaaa-filter-iterator.patch.
++
++30 January 2023: George
++ - Add duration variable for speed_local.test.
++
++26 January 2023: Wouter
++ - Fix acx_nlnetlabs.m4 for -Wstrict-prototypes.
++
++23 January 2023: George
++ - Fix #833: [FR] Ability to set the Redis password.
++
++23 January 2023: Wouter
++ - Fix #835: [FR] Ability to use Redis unix sockets.
++
++20 January 2023: Wouter
++ - Merge #819: Added new static zone type block_a to suppress all A
++ queries for specific zones.
++
++19 January 2023: Wouter
++ - Set max-udp-size default to 1232. This is the same default value as
++ the default value for edns-buffer-size. It restricts client edns
++ buffer size choices, and makes unbound behave similar to other DNS
++ resolvers. The new choice, down from 4096 means it is harder to get
++ large responses from Unbound. Thanks to Xiang Li, from NISL Lab,
++ Tsinghua University.
++ - Add harden-unknown-additional option. It removes
++ unknown records from the authority section and additional section.
++ Thanks to Xiang Li, from NISL Lab, Tsinghua University.
++ - Set default for harden-unknown-additional to no. So that it does
++ not hamper future protocol developments.
++ - Fix test for new default.
++
++18 January 2023: Wouter
++ - Fix not following cleared RD flags potentially enables amplification
++ DDoS attacks, reported by Xiang Li and Wei Xu from NISL Lab,
++ Tsinghua University. The fix stops query loops, by refusing to send
++ RD=0 queries to a forwarder, they still get answered from cache.
++
++13 January 2023: Wouter
++ - Merge #826: Аdd a metric about the maximum number of collisions in
++ lrushah.
++ - Improve documentation for #826, describe the large collisions amount.
++
++9 January 2023: Wouter
++ - Fix python module install path detection.
++ - Fix python version detection in configure.
++
++6 January 2023: Wouter
++ - Fix #823: Response change to NODATA for some ANY queries since
++ 1.12, tested on 1.16.1.
++ - Fix wildcard in hyperlocal zone service degradation, reported
++ by Sergey Kacheev. This fix is included in 1.17.1rc2.
++ That became 1.17.1 on 12 Jan 2023, the code repo continues
++ with 1.17.2. 1.17.1 excludes fix #823, it is included forwards.
++
+ 5 January 2023: Wouter
+ - Tag for 1.17.1 release.
+
+@@ -4426,7 +4899,7 @@
+ - Fix that with openssl 1.1 control-use-cert: no uses less cpu, by
+ using no encryption over the unix socket.
+
+-22 Novenber 2016: Ralph
++22 November 2016: Ralph
+ - Make access-control-tag-data RDATA absolute. This makes the RDATA
+ origin consistent between local-data and access-control-tag-data.
+ - Fix NSEC ENT wildcard check. Matching wildcard does not have to be a
+--- contrib/unbound/doc/README.orig
++++ contrib/unbound/doc/README
+@@ -1,4 +1,4 @@
+-README for Unbound 1.17.1
++README for Unbound 1.19.1
+ Copyright 2007 NLnet Labs
+ http://unbound.net
+
+--- contrib/unbound/doc/README.DNS64.orig
++++ contrib/unbound/doc/README.DNS64
+@@ -28,3 +28,23 @@
+ ;; ANSWER SECTION:
+ jazz-v4.viagenie.ca. 86400 IN AAAA 64:ff9b::ce7b:1f02
+
++
++NAT64 support was added by David Lamparter in 2022; license(s) of the
++surrounding code apply. Note that NAT64 is closely related but functionally
++orthogonal to DNS64; it allows Unbound to send outgoing queries to IPv4-only
++servers over IPv6 through the configured NAT64 prefix. This allows running
++an Unbound instance on an IPv6-only host without breaking every single domain
++that only has IPv4 servers. Whether that Unbound instance also does DNS64 is
++an independent choice.
++
++To enable NAT64 in Unbound, add to unbound.conf's "server" section:
++
++ do-nat64: yes
++
++The NAT64 prefix defaults to the DNS64 prefix, which in turn defaults to the
++standard 64:FF9B::/96 prefix. You can reconfigure it with:
++
++ nat64-prefix: 64:FF9B::/96
++
++To test NAT64 operation, pick a domain that only has IPv4 reachability for its
++nameservers and try resolving any names in that domain.
+--- contrib/unbound/doc/example.conf.in.orig
++++ contrib/unbound/doc/example.conf.in
+@@ -1,7 +1,7 @@
+ #
+ # Example configuration file.
+ #
+-# See unbound.conf(5) man page, version 1.17.1.
++# See unbound.conf(5) man page, version 1.19.1.
+ #
+ # this is a comment.
+
+@@ -143,8 +143,8 @@
+ # edns-buffer-size: 1232
+
+ # Maximum UDP response size (not applied to TCP response).
+- # Suggested values are 512 to 4096. Default is 4096. 65536 disables it.
+- # max-udp-size: 4096
++ # Suggested values are 512 to 4096. Default is 1232. 65536 disables it.
++ # max-udp-size: 1232
+
+ # max memory to use for stream(tcp and tls) waiting result buffers.
+ # stream-wait-size: 4m
+@@ -243,6 +243,18 @@
+ # Enable IPv6, "yes" or "no".
+ # do-ip6: yes
+
++ # If running unbound on an IPv6-only host, domains that only have
++ # IPv4 servers would become unresolveable. If NAT64 is available in
++ # the network, unbound can use NAT64 to reach these servers with
++ # the following option. This is NOT needed for enabling DNS64 on a
++ # system that has IPv4 connectivity.
++ # Consider also enabling prefer-ip6 to prefer native IPv6 connections
++ # to nameservers.
++ # do-nat64: no
++
++ # NAT64 prefix. Defaults to using dns64-prefix value.
++ # nat64-prefix: 64:ff9b::0/96
++
+ # Enable UDP, "yes" or "no".
+ # do-udp: yes
+
+@@ -274,6 +286,10 @@
+ # Timeout for EDNS TCP keepalive, in msec.
+ # edns-tcp-keepalive-timeout: 120000
+
++ # UDP queries that have waited in the socket buffer for a long time
++ # can be dropped. Default is 0, disabled. In seconds, such as 3.
++ # sock-queue-timeout: 0
++
+ # Use systemd socket activation for UDP, TCP, and control sockets.
+ # use-systemd: no
+
+@@ -503,6 +519,10 @@
+ # to validate the zone.
+ # harden-algo-downgrade: no
+
++ # Harden against unknown records in the authority section and the
++ # additional section.
++ # harden-unknown-additional: no
++
+ # Sent minimum amount of information to upstream servers to enhance
+ # privacy. Only sent minimum required labels of the QNAME and set QTYPE
+ # to A when possible.
+@@ -663,6 +683,11 @@
+ # that set CD but cannot validate themselves.
+ # ignore-cd-flag: no
+
++ # Disable the DO flag in outgoing requests. It is helpful for upstream
++ # devices that cannot handle DNSSEC information. But do not enable it
++ # otherwise, because it would stop DNSSEC validation.
++ # disable-edns-do: no
++
+ # Serve expired responses from cache, with serve-expired-reply-ttl in
+ # the response, and then attempt to fetch the data afresh.
+ # serve-expired: no
+@@ -810,6 +835,8 @@
+ # o always_transparent, always_refuse, always_nxdomain, always_nodata,
+ # always_deny resolve in that way but ignore local data for
+ # that name
++ # o block_a resolves all records normally but returns
++ # NODATA for A queries and ignores local data for that name
+ # o always_null returns 0.0.0.0 or ::0 for any name in the zone.
+ # o noview breaks out of that view towards global local-zones.
+ #
+@@ -1199,6 +1226,8 @@
+ # backend: "testframe"
+ # # secret seed string to calculate hashed keys
+ # secret-seed: "default"
++# # if the backend should be read from, but not written to.
++# cachedb-no-store: no
+ #
+ # # For "redis" backend:
+ # # (to enable, use --with-libhiredis to configure before compiling)
+@@ -1206,10 +1235,16 @@
+ # redis-server-host: 127.0.0.1
+ # # redis server's TCP port
+ # redis-server-port: 6379
++# # if the server uses a unix socket, set its path, or "" when not used.
++# # redis-server-path: "/var/lib/redis/redis-server.sock"
++# # if the server uses an AUTH password, specify here, or "" when not used.
++# # redis-server-password: ""
+ # # timeout (in ms) for communication with the redis server
+ # redis-timeout: 100
+ # # set timeout on redis records based on DNS response TTL
+ # redis-expire-records: no
++# # redis logical database to use, 0 is the default database.
++# redis-logical-db: 0
+
+ # IPSet
+ # Add specify domain into set via ipset.
+--- contrib/unbound/doc/libunbound.3.in.orig
++++ contrib/unbound/doc/libunbound.3.in
+@@ -1,4 +1,4 @@
+-.TH "libunbound" "3" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "libunbound" "3" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" libunbound.3 -- unbound library functions manual
+ .\"
+@@ -44,7 +44,7 @@
+ .B ub_ctx_zone_remove,
+ .B ub_ctx_data_add,
+ .B ub_ctx_data_remove
+-\- Unbound DNS validating resolver 1.17.1 functions.
++\- Unbound DNS validating resolver 1.19.1 functions.
+ .SH "SYNOPSIS"
+ .B #include <unbound.h>
+ .LP
+--- contrib/unbound/doc/unbound-anchor.8.in.orig
++++ contrib/unbound/doc/unbound-anchor.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound-anchor" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound-anchor" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-anchor.8 -- unbound anchor maintenance utility manual
+ .\"
+--- contrib/unbound/doc/unbound-checkconf.8.in.orig
++++ contrib/unbound/doc/unbound-checkconf.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound-checkconf" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound-checkconf" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-checkconf.8 -- unbound configuration checker manual
+ .\"
+--- contrib/unbound/doc/unbound-control.8.in.orig
++++ contrib/unbound/doc/unbound-control.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound-control" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound-control" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-control.8 -- unbound remote control manual
+ .\"
+@@ -369,6 +369,15 @@
+ .I threadX.num.queries_ip_ratelimited
+ number of queries rate limited by thread
+ .TP
++.I threadX.num.queries_cookie_valid
++number of queries with a valid DNS Cookie by thread
++.TP
++.I threadX.num.queries_cookie_client
++number of queries with a client part only DNS Cookie by thread
++.TP
++.I threadX.num.queries_cookie_invalid
++number of queries with an invalid DNS Cookie by thread
++.TP
+ .I threadX.num.cachehits
+ number of queries that were successfully answered using a cache lookup
+ .TP
+@@ -398,6 +407,14 @@
+ .I threadX.num.expired
+ number of replies that served an expired cache entry.
+ .TP
++.I threadX.num.queries_timed_out
++number of queries that are dropped because they waited in the UDP socket buffer
++for too long.
++.TP
++.I threadX.query.queue_time_us.max
++The maximum wait time for packets in the socket buffer, in microseconds. This
++is only reported when sock-queue-timeout is enabled.
++.TP
+ .I threadX.num.recursivereplies
+ The number of replies sent to queries that needed recursive processing. Could be smaller than threadX.num.cachemiss if due to timeouts no replies were sent for some queries.
+ .TP
+@@ -438,6 +455,18 @@
+ .I total.num.queries
+ summed over threads.
+ .TP
++.I total.num.queries_ip_ratelimited
++summed over threads.
++.TP
++.I total.num.queries_cookie_valid
++summed over threads.
++.TP
++.I total.num.queries_cookie_client
++summed over threads.
++.TP
++.I total.num.queries_cookie_invalid
++summed over threads.
++.TP
+ .I total.num.cachehits
+ summed over threads.
+ .TP
+@@ -462,6 +491,12 @@
+ .I total.num.expired
+ summed over threads.
+ .TP
++.I total.num.queries_timed_out
++summed over threads.
++.TP
++.I total.query.queue_time_us.max
++the maximum of the thread values.
++.TP
+ .I total.num.recursivereplies
+ summed over threads.
+ .TP
+@@ -597,7 +632,7 @@
+ .TP
+ .I num.query.dnscrypt.shared_secret.cachemiss
+ The number of dnscrypt queries that did not find a shared secret in the cache.
+-The can be use to compute the shared secret hitrate.
++This can be used to compute the shared secret hitrate.
+ .TP
+ .I num.query.dnscrypt.replay
+ The number of dnscrypt queries that found a nonce hit in the nonce cache and
+@@ -653,6 +688,18 @@
+ The number of items in the key cache. These are DNSSEC keys, one item
+ per delegation point, and their validation status.
+ .TP
++.I msg.cache.max_collisions
++The maximum number of hash table collisions in the msg cache. This is the
++number of hashes that are identical when a new element is inserted in the
++hash table. If the value is very large, like hundreds, something is wrong
++with the performance of the hash table, hash values are incorrect or malicious.
++.TP
++.I rrset.cache.max_collisions
++The maximum number of hash table collisions in the rrset cache. This is the
++number of hashes that are identical when a new element is inserted in the
++hash table. If the value is very large, like hundreds, something is wrong
++with the performance of the hash table, hash values are incorrect or malicious.
++.TP
+ .I dnscrypt_shared_secret.cache.count
+ The number of items in the shared secret cache. These are precomputed shared
+ secrets for a given client public key/server secret key pair. Shared secrets
+@@ -692,7 +739,12 @@
+ .I num.query.subnet_cache
+ Number of queries answered from the edns client subnet cache. These are
+ counted as cachemiss by the main counters, but hit the client subnet
+-specific cache, after getting processed by the edns client subnet module.
++specific cache after getting processed by the edns client subnet module.
++.TP
++.I num.query.cachedb
++Number of queries answered from the external cache of cachedb.
++These are counted as cachemiss by the main counters, but hit the cachedb
++external cache after getting processed by the cachedb module.
+ .TP
+ .I num.rpz.action.<rpz_action>
+ Number of queries answered using configured RPZ policy, per RPZ action type.
+--- contrib/unbound/doc/unbound-host.1.in.orig
++++ contrib/unbound/doc/unbound-host.1.in
+@@ -1,4 +1,4 @@
+-.TH "unbound\-host" "1" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound\-host" "1" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-host.1 -- unbound DNS lookup utility
+ .\"
+--- contrib/unbound/doc/unbound.8.in.orig
++++ contrib/unbound/doc/unbound.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound.8 -- unbound manual
+ .\"
+@@ -9,7 +9,7 @@
+ .\"
+ .SH "NAME"
+ .B unbound
+-\- Unbound DNS validating resolver 1.17.1.
++\- Unbound DNS validating resolver 1.19.1.
+ .SH "SYNOPSIS"
+ .B unbound
+ .RB [ \-h ]
+--- contrib/unbound/doc/unbound.conf.5.in.orig
++++ contrib/unbound/doc/unbound.conf.5.in
+@@ -1,4 +1,4 @@
+-.TH "unbound.conf" "5" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound.conf" "5" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound.conf.5 -- unbound.conf manual
+ .\"
+@@ -233,7 +233,8 @@
+ .B max\-udp\-size: \fI<number>
+ Maximum UDP response size (not applied to TCP response). 65536 disables the
+ udp response size maximum, and uses the choice from the client, always.
+-Suggested values are 512 to 4096. Default is 4096.
++Suggested values are 512 to 4096. Default is 1232. The default value is the
++same as the default for edns\-buffer\-size.
+ .TP
+ .B stream\-wait\-size: \fI<number>
+ Number of bytes size maximum to use for waiting stream buffers. Default is
+@@ -504,6 +505,14 @@
+ A minimum actual timeout of 200 milliseconds is observed regardless of the
+ advertised timeout.
+ .TP
++.B sock\-queue\-timeout: \fI<sec>\fR
++UDP queries that have waited in the socket buffer for a long time can be
++dropped. Default is 0, disabled. The time is set in seconds, 3 could be a
++good value to ignore old queries that likely the client does not need a reply
++for any more. This could happen if the host has not been able to service
++the queries for a while, i.e. Unbound is not running, and then is enabled
++again. It uses timestamp socket options.
++.TP
+ .B tcp\-upstream: \fI<yes or no>
+ Enable or disable whether the upstream queries use TCP only for transport.
+ Default is no. Useful in tunneling scenarios. If set to no you can specify
+@@ -692,17 +701,17 @@
+ .B access\-control: \fI<IP netblock> <action>
+ The netblock is given as an IP4 or IP6 address with /size appended for a
+ classless network block. The action can be \fIdeny\fR, \fIrefuse\fR,
+-\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIdeny_non_local\fR or
+-\fIrefuse_non_local\fR.
++\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIallow_cookie\fR,
++\fIdeny_non_local\fR or \fIrefuse_non_local\fR.
+ The most specific netblock match is used, if none match \fIrefuse\fR is used.
+ The order of the access\-control statements therefore does not matter.
+ .IP
+-The action \fIdeny\fR stops queries from hosts from that netblock.
++The \fIdeny\fR action stops queries from hosts from that netblock.
+ .IP
+-The action \fIrefuse\fR stops queries too, but sends a DNS rcode REFUSED
++The \fIrefuse\fR action stops queries too, but sends a DNS rcode REFUSED
+ error message back.
+ .IP
+-The action \fIallow\fR gives access to clients from that netblock.
++The \fIallow\fR action gives access to clients from that netblock.
+ It gives only access for recursion clients (which is
+ what almost all clients need). Nonrecursive queries are refused.
+ .IP
+@@ -722,13 +731,27 @@
+ zones to a resolver DNS server, but only supports stub domains and
+ sends queries to the resolver DNS server with the RD bit cleared.
+ .IP
+-The action \fIallow_snoop\fR gives nonrecursive access too. This give
++The \fIallow_snoop\fR action gives nonrecursive access too. This give
+ both recursive and non recursive access. The name \fIallow_snoop\fR refers
+ to cache snooping, a technique to use nonrecursive queries to examine
+ the cache contents (for malicious acts). However, nonrecursive queries can
+ also be a valuable debugging tool (when you want to examine the cache
+ contents). In that case use \fIallow_snoop\fR for your administration host.
+ .IP
++The \fIallow_cookie\fR action allows access to UDP queries that contain a
++valid DNS Cookie as specified in RFC 7873 and RFC 9018, when the
++\fBanswer\-cookie\fR option is enabled.
++UDP queries containing only a DNS Client Cookie and no Server Cookie, or an
++invalid DNS Cookie, will receive a BADCOOKIE response including a newly
++generated DNS Cookie, allowing clients to retry with that DNS Cookie.
++The \fIallow_cookie\fR action will also accept requests over stateful
++transports, regardless of the presence of an DNS Cookie and regardless of the
++\fBanswer\-cookie\fR setting.
++If \fBip\-ratelimit\fR is used, clients with a valid DNS Cookie will bypass the
++ratelimit.
++If a ratelimit for such clients is still needed, \fBip\-ratelimit\-cookie\fR
++can be used instead.
++.IP
+ By default only localhost is \fIallow\fRed, the rest is \fIrefuse\fRd.
+ The default is \fIrefuse\fRd, because that is protocol\-friendly. The DNS
+ protocol is not designed to handle dropped packets due to policy, and
+@@ -1019,6 +1042,12 @@
+ that allow this feature to work, but sometimes they do not, and turning
+ this option off avoids that validation failure.
+ .TP
++.B harden\-unknown\-additional: \fI<yes or no>
++Harden against unknown records in the authority section and additional
++section. Default is no. If no, such records are copied from the upstream
++and presented to the client together with the answer. If yes, it could
++hamper future protocol developments that want to add records.
++.TP
+ .B use\-caps\-for\-id: \fI<yes or no>
+ Use 0x20\-encoded random bits in the query to foil spoof attempts.
+ This perturbs the lowercase and uppercase of query names sent to
+@@ -1273,6 +1302,20 @@
+ the clients, and then Unbound provides them with DNSSEC protection.
+ The default value is "no".
+ .TP
++.B disable\-edns\-do: \fI<yes or no>
++Disable the EDNS DO flag in upstream requests.
++It breaks DNSSEC validation for Unbound's clients.
++This results in the upstream name servers to not include DNSSEC records in
++their replies and could be helpful for devices that cannot handle DNSSEC
++information.
++When the option is enabled, clients that set the DO flag receive no EDNS
++record in the response to indicate the lack of support to them.
++If this option is enabled but Unbound is already configured for DNSSEC
++validation (i.e., the validator module is enabled; default) this option is
++implicitly turned off with a warning as to not break DNSSEC validation in
++Unbound.
++Default is no.
++.TP
+ .B serve\-expired: \fI<yes or no>
+ If enabled, Unbound attempts to serve old responses from cache with a
+ TTL of \fBserve\-expired\-reply\-ttl\fR in the response without waiting for the
+@@ -1391,10 +1434,10 @@
+ Configure a local zone. The type determines the answer to give if
+ there is no match from local\-data. The types are deny, refuse, static,
+ transparent, redirect, nodefault, typetransparent, inform, inform_deny,
+-inform_redirect, always_transparent, always_refuse, always_nxdomain, always_null, noview,
+-and are explained below. After that the default settings are listed. Use
+-local\-data: to enter data into the local zone. Answers for local zones
+-are authoritative DNS answers. By default the zones are class IN.
++inform_redirect, always_transparent, block_a, always_refuse, always_nxdomain,
++always_null, noview, and are explained below. After that the default settings
++are listed. Use local\-data: to enter data into the local zone. Answers for
++local zones are authoritative DNS answers. By default the zones are class IN.
+ .IP
+ If you need more complicated authoritative data, with referrals, wildcards,
+ CNAME/DNAME support, or DNSSEC authoritative service, setup a stub\-zone for
+@@ -1469,6 +1512,12 @@
+ \h'5'\fIalways_transparent\fR
+ Like transparent, but ignores local data and resolves normally.
+ .TP 10
++\h'5'\fIblock_a\fR
++Like transparent, but ignores local data and resolves normally all query
++types excluding A. For A queries it unconditionally returns NODATA.
++Useful in cases when there is a need to explicitly force all apps to use
++IPv6 protocol and avoid any queries to IPv4.
++.TP 10
+ \h'5'\fIalways_refuse\fR
+ Like refuse, but ignores local data and refuses the query.
+ .TP 10
+@@ -1785,11 +1834,27 @@
+ .TP 5
+ .B ip\-ratelimit: \fI<number or 0>
+ Enable global ratelimiting of queries accepted per IP address.
+-If 0, the default, it is disabled. This option is experimental at this time.
++This option is experimental at this time.
+ The ratelimit is in queries per second that are allowed. More queries are
+ completely dropped and will not receive a reply, SERVFAIL or otherwise.
+ IP ratelimiting happens before looking in the cache. This may be useful for
+ mitigating amplification attacks.
++Default is 0 (disabled).
++.TP 5
++.B ip\-ratelimit\-cookie: \fI<number or 0>
++Enable global ratelimiting of queries accepted per IP address with a valid DNS
++Cookie.
++This option is experimental at this time.
++The ratelimit is in queries per second that are allowed.
++More queries are completely dropped and will not receive a reply, SERVFAIL or
++otherwise.
++IP ratelimiting happens before looking in the cache.
++This option could be useful in combination with \fIallow_cookie\fR in an
++attempt to mitigate other amplification attacks than UDP reflections (e.g.,
++attacks targeting Unbound itself) which are already handled with DNS Cookies.
++If used, the value is suggested to be higher than \fBip\-ratelimit\fR e.g.,
++tenfold.
++Default is 0 (disabled).
+ .TP 5
+ .B ip\-ratelimit\-size: \fI<memory size>
+ Give the size of the data structure in which the current ongoing rates are
+@@ -1858,6 +1923,18 @@
+ use the fastest specified number of servers with the fast\-server\-permil
+ option, that turns this on or off. The default is to use the fastest 3 servers.
+ .TP 5
++.B answer\-cookie: \fI<yes or no>
++If enabled, Unbound will answer to requests containing DNS Cookies as
++specified in RFC 7873 and RFC 9018.
++Default is no.
++.TP 5
++.B cookie\-secret: \fI<128 bit hex string>
++Server's secret for DNS Cookie generation.
++Useful to explicitly set for servers in an anycast deployment that need to
++share the secret in order to verify each other's Server Cookies.
++An example hex string would be "000102030405060708090a0b0c0d0e0f".
++Default is a 128 bits random secret generated at startup time.
++.TP 5
+ .B edns\-client\-string: \fI<IP netblock> <string>
+ Include an EDNS0 option containing configured ascii string in queries with
+ destination address matching the configured IP netblock. This configuration
+@@ -2091,13 +2168,32 @@
+ Authority zones are configured with \fBauth\-zone:\fR, and each one must
+ have a \fBname:\fR. There can be multiple ones, by listing multiple auth\-zone clauses, each with a different name, pertaining to that part of the namespace.
+ The authority zone with the name closest to the name looked up is used.
+-Authority zones are processed after \fBlocal\-zones\fR and before
+-cache (\fBfor\-downstream:\fR \fIyes\fR), and when used in this manner
+-make Unbound respond like an authority server. Authority zones are also
+-processed after cache, just before going to the network to fetch
+-information for recursion (\fBfor\-upstream:\fR \fIyes\fR), and when used
+-in this manner provide a local copy of an authority server that speeds up
+-lookups of that data.
++Authority zones can be processed on two distinct, non-exclusive, configurable
++stages.
++.LP
++With \fBfor\-downstream:\fR \fIyes\fR (default), authority zones are processed
++after \fBlocal\-zones\fR and before cache.
++When used in this manner, Unbound responds like an authority server with no
++further processing other than returning an answer from the zone contents.
++A notable example, in this case, is CNAME records which are returned verbatim
++to downstream clients without further resolution.
++.LP
++With \fBfor\-upstream:\fR \fIyes\fR (default), authority zones are processed
++after the cache lookup, just before going to the network to fetch
++information for recursion.
++When used in this manner they provide a local copy of an authority server
++that speeds up lookups for that data during resolving.
++.LP
++If both options are enabled (default), client queries for an authority zone are
++answered authoritatively from Unbound, while internal queries that require data
++from the authority zone consult the local zone data instead of going to the
++network.
++.LP
++An interesting configuration is \fBfor\-downstream:\fR \fIno\fR,
++\fBfor\-upstream:\fR \fIyes\fR that allows for hyperlocal behavior where both
++client and internal queries consult the local zone data while resolving.
++In this case, the aforementioned CNAME example will result in a thoroughly
++resolved answer.
+ .LP
+ Authority zones can be read from zonefile. And can be kept updated via
+ AXFR and IXFR. After update the zonefile is rewritten. The update mechanism
+@@ -2291,6 +2387,21 @@
+ used by dns64 processing instead. Can be entered multiple times, list a
+ new domain for which it applies, one per line. Applies also to names
+ underneath the name given.
++.SS "NAT64 Operation"
++.LP
++NAT64 operation allows using a NAT64 prefix for outbound requests to IPv4-only
++servers. It is controlled by two options in the \fBserver:\fR section:
++.TP
++.B do\-nat64: \fI<yes or no>\fR
++Use NAT64 to reach IPv4-only servers.
++Consider also enabling \fBprefer\-ip6\fR to prefer native IPv6 connections to
++nameservers.
++Default no.
++.TP
++.B nat64\-prefix: \fI<IPv6 prefix>\fR
++Use a specific NAT64 prefix to reach IPv4-only servers. Defaults to using
++the prefix configured in \fBdns64\-prefix\fR, which in turn defaults to
++64:ff9b::/96. The prefix length must be one of /32, /40, /48, /56, /64 or /96.
+ .SS "DNSCrypt Options"
+ .LP
+ The
+@@ -2570,6 +2681,11 @@
+ If the backend database is shared by multiple Unbound instances,
+ all instances must use the same secret seed.
+ This option defaults to "default".
++.TP
++.B cachedb-no-store: \fI<yes or no>\fR
++If the backend should be read from, but not written to. This makes this
++instance not store dns messages in the backend. But if data is available it
++is retrieved. The default is no.
+ .P
+ The following
+ .B cachedb
+@@ -2586,6 +2702,16 @@
+ The TCP port number of the Redis server.
+ This option defaults to 6379.
+ .TP
++.B redis-server-path: \fI<unix socket path>\fR
++The unix socket path to connect to the redis server. Off by default, and it
++can be set to "" to turn this off. Unix sockets may have better throughput
++than the IP address option.
++.TP
++.B redis-server-password: \fI"<password>"\fR
++The Redis AUTH password to use for the redis server.
++Only relevant if Redis is configured for client password authorisation.
++Off by default, and it can be set to "" to turn this off.
++.TP
+ .B redis-timeout: \fI<msec>\fR
+ The period until when Unbound waits for a response from the Redis sever.
+ If this timeout expires Unbound closes the connection, treats it as
+@@ -2600,6 +2726,17 @@
+ this option is internally reverted to "no". Redis SETEX support is required
+ for this option (Redis >= 2.0.0).
+ This option defaults to no.
++.TP
++.B redis-logical-db: \fI<logical database index>
++The logical database in Redis to use.
++These are databases in the same Redis instance sharing the same configuration
++and persisted in the same RDB/AOF file.
++If unsure about using this option, Redis documentation
++(https://redis.io/commands/select/) suggests not to use a single Redis instance
++for multiple unrelated applications.
++The default database in Redis is 0 while other logical databases need to be
++explicitly SELECT'ed upon connecting.
++This option defaults to 0.
+ .SS DNSTAP Logging Options
+ DNSTAP support, when compiled in by using \fB\-\-enable\-dnstap\fR, is enabled
+ in the \fBdnstap:\fR section.
+--- contrib/unbound/dynlibmod/dynlibmod.c.orig
++++ contrib/unbound/dynlibmod/dynlibmod.c
+@@ -75,6 +75,7 @@
+ struct config_strlist* cfg_item = env->cfg->dynlib_file;
+ struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env));
+ __DYNMOD dynamic_library;
++ int i;
+ if (!de)
+ {
+ log_err("dynlibmod[%d]: malloc failure", dynlib_mod_idx);
+@@ -84,7 +85,7 @@
+ env->modinfo[id] = (void*) de;
+
+ de->fname = NULL;
+- for(int i = dynlib_mod_idx;
++ for(i = dynlib_mod_idx;
+ i != 0 && cfg_item != NULL;
+ i--, cfg_item = cfg_item->next) {}
+
+--- contrib/unbound/edns-subnet/subnetmod.c.orig
++++ contrib/unbound/edns-subnet/subnetmod.c
+@@ -156,6 +156,7 @@
+ qstate->no_cache_store = 0;
+ }
+
++ sq->subnet_sent_no_subnet = 0;
+ if(sq->ecs_server_out.subnet_validdata && ((sq->subnet_downstream &&
+ qstate->env->cfg->client_subnet_always_forward) ||
+ ecs_is_whitelisted(sn_env->whitelist,
+@@ -166,6 +167,14 @@
+ * set. */
+ if(!edns_opt_list_find(qstate->edns_opts_back_out,
+ qstate->env->cfg->client_subnet_opcode)) {
++ /* if the client is not wanting an EDNS subnet option,
++ * omit it and store that we omitted it but actually
++ * are doing EDNS subnet to the server. */
++ if(sq->ecs_server_out.subnet_source_mask == 0) {
++ sq->subnet_sent_no_subnet = 1;
++ sq->subnet_sent = 0;
++ return 1;
++ }
+ subnet_ecs_opt_list_append(&sq->ecs_server_out,
+ &qstate->edns_opts_back_out, qstate, region);
+ }
+@@ -352,7 +361,7 @@
+ ((struct subnet_qstate*)qstate->minfo[id])->qinfo_hash :
+ query_info_hash(&qstate->qinfo, qstate->query_flags);
+ /* Step 1, general qinfo lookup */
+- struct lruhash_entry *lru_entry = slabhash_lookup(subnet_msg_cache, h,
++ struct lruhash_entry* lru_entry = slabhash_lookup(subnet_msg_cache, h,
+ &qstate->qinfo, 1);
+ int need_to_insert = (lru_entry == NULL);
+ if (!lru_entry) {
+@@ -396,7 +405,7 @@
+ log_err("subnetcache: cache insertion failed");
+ return;
+ }
+-
++
+ /* store RRsets */
+ for(i=0; i<rep->rrset_count; i++) {
+ rep->ref[i].key = rep->rrsets[i];
+@@ -421,7 +430,7 @@
+
+ /** Lookup in cache and reply true iff reply is sent. */
+ static int
+-lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
++lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq, int prefetch)
+ {
+ struct lruhash_entry *e;
+ struct module_env *env = qstate->env;
+@@ -473,6 +482,10 @@
+ INET6_SIZE);
+ sq->ecs_client_out.subnet_validdata = 1;
+ }
++
++ if (prefetch && *qstate->env->now >= ((struct reply_info *)node->elem)->prefetch_ttl) {
++ qstate->need_refetch = 1;
++ }
+ return 1;
+ }
+
+@@ -509,18 +522,18 @@
+ * module_finished */
+ return module_finished;
+ }
+-
++
+ /* We have not asked for subnet data */
+- if (!sq->subnet_sent) {
++ if (!sq->subnet_sent && !sq->subnet_sent_no_subnet) {
+ if (s_in->subnet_validdata)
+ verbose(VERB_QUERY, "subnetcache: received spurious data");
+ if (sq->subnet_downstream) /* Copy back to client */
+ cp_edns_bad_response(c_out, c_in);
+ return module_finished;
+ }
+-
++
+ /* subnet sent but nothing came back */
+- if (!s_in->subnet_validdata) {
++ if (!s_in->subnet_validdata && !sq->subnet_sent_no_subnet) {
+ /* The authority indicated no support for edns subnet. As a
+ * consequence the answer ended up in the regular cache. It
+ * is still useful to put it in the edns subnet cache for
+@@ -535,11 +548,23 @@
+ cp_edns_bad_response(c_out, c_in);
+ return module_finished;
+ }
+-
++
++ /* Purposefully there was no sent subnet, and there is consequently
++ * no subnet in the answer. If there was, use the subnet in the answer
++ * anyway. But if there is not, treat it as a prefix 0 answer. */
++ if(sq->subnet_sent_no_subnet && !s_in->subnet_validdata) {
++ /* Fill in 0.0.0.0/0 scope 0, or ::0/0 scope 0, for caching. */
++ s_in->subnet_addr_fam = s_out->subnet_addr_fam;
++ s_in->subnet_source_mask = 0;
++ s_in->subnet_scope_mask = 0;
++ memset(s_in->subnet_addr, 0, INET6_SIZE);
++ s_in->subnet_validdata = 1;
++ }
++
+ /* Being here means we have asked for and got a subnet specific
+ * answer. Also, the answer from the authority is not yet cached
+ * anywhere. */
+-
++
+ /* can we accept response? */
+ if(s_out->subnet_addr_fam != s_in->subnet_addr_fam ||
+ s_out->subnet_source_mask != s_in->subnet_source_mask ||
+@@ -552,6 +577,7 @@
+ (void)edns_opt_list_remove(&qstate->edns_opts_back_out,
+ qstate->env->cfg->client_subnet_opcode);
+ sq->subnet_sent = 0;
++ sq->subnet_sent_no_subnet = 0;
+ return module_restart_next;
+ }
+
+@@ -672,6 +698,7 @@
+ edns_opt_list_remove(&qstate->edns_opts_back_out,
+ qstate->env->cfg->client_subnet_opcode);
+ sq->subnet_sent = 0;
++ sq->subnet_sent_no_subnet = 0;
+ memset(&sq->ecs_server_out, 0, sizeof(sq->ecs_server_out));
+ } else if (!sq->track_max_scope &&
+ FLAGS_GET_RCODE(response->rep->flags) == LDNS_RCODE_NOERROR &&
+@@ -733,6 +760,9 @@
+ sq->ecs_server_in.subnet_scope_mask >
+ sq->max_scope))
+ sq->max_scope = sq->ecs_server_in.subnet_scope_mask;
++ } else if(sq->subnet_sent_no_subnet) {
++ /* The answer can be stored as scope 0, not in global cache. */
++ qstate->no_cache_store = 1;
+ }
+
+ return 1;
+@@ -779,6 +809,11 @@
+ &qstate->mesh_info->reply_list->query_reply.client_addr,
+ &sq->ecs_client_in, qstate->env->cfg);
+ }
++ else if(qstate->client_addr.ss_family != AF_UNSPEC) {
++ subnet_option_from_ss(
++ &qstate->client_addr,
++ &sq->ecs_client_in, qstate->env->cfg);
++ }
+
+ if(sq->ecs_client_in.subnet_validdata == 0) {
+ /* No clients are interested in result or we could not
+@@ -802,7 +837,9 @@
+
+ if(!sq->started_no_cache_lookup && !qstate->blacklist) {
+ lock_rw_wrlock(&sne->biglock);
+- if(lookup_and_reply(qstate, id, sq)) {
++ if(qstate->mesh_info->reply_list &&
++ lookup_and_reply(qstate, id, sq,
++ qstate->env->cfg->prefetch)) {
+ sne->num_msg_cache++;
+ lock_rw_unlock(&sne->biglock);
+ verbose(VERB_QUERY, "subnetcache: answered from cache");
+--- contrib/unbound/edns-subnet/subnetmod.h.orig
++++ contrib/unbound/edns-subnet/subnetmod.h
+@@ -85,6 +85,13 @@
+ struct ecs_data ecs_server_out;
+ int subnet_downstream;
+ int subnet_sent;
++ /**
++ * If there was no subnet sent because the client used source prefix
++ * length 0 for omitting the information. Then the answer is cached
++ * like subnet was a /0 scope. Like the subnet_sent flag, but when
++ * the EDNS subnet option is omitted because the client asked.
++ */
++ int subnet_sent_no_subnet;
+ /** keep track of longest received scope, set after receiving CNAME for
+ * incoming QNAME. */
+ int track_max_scope;
+--- contrib/unbound/ipset/ipset.c.orig
++++ contrib/unbound/ipset/ipset.c
+@@ -158,10 +158,10 @@
+ qs = NULL;
+ plen = strlen(p->str);
+
+- if (dlen >= plen) {
++ if (dlen == plen || (dlen > plen && dname[dlen - plen - 1] == '.' )) {
+ ds = dname + (dlen - plen);
+ }
+- if (qlen >= plen) {
++ if (qlen == plen || (qlen > plen && qname[qlen - plen - 1] == '.' )) {
+ qs = qname + (qlen - plen);
+ }
+ if ((ds && strncasecmp(p->str, ds, plen) == 0)
+--- contrib/unbound/iterator/iter_delegpt.c.orig
++++ contrib/unbound/iterator/iter_delegpt.c
+@@ -321,6 +321,45 @@
+ }
+ }
+
++int
++delegpt_addr_on_result_list(struct delegpt* dp, struct delegpt_addr* find)
++{
++ struct delegpt_addr* a = dp->result_list;
++ while(a) {
++ if(a == find)
++ return 1;
++ a = a->next_result;
++ }
++ return 0;
++}
++
++void
++delegpt_usable_list_remove_addr(struct delegpt* dp, struct delegpt_addr* del)
++{
++ struct delegpt_addr* usa = dp->usable_list, *prev = NULL;
++ while(usa) {
++ if(usa == del) {
++ /* snip off the usable list */
++ if(prev)
++ prev->next_usable = usa->next_usable;
++ else dp->usable_list = usa->next_usable;
++ return;
++ }
++ prev = usa;
++ usa = usa->next_usable;
++ }
++}
++
++void
++delegpt_add_to_result_list(struct delegpt* dp, struct delegpt_addr* a)
++{
++ if(delegpt_addr_on_result_list(dp, a))
++ return;
++ delegpt_usable_list_remove_addr(dp, a);
++ a->next_result = dp->result_list;
++ dp->result_list = a;
++}
++
+ void
+ delegpt_add_unused_targets(struct delegpt* dp)
+ {
+--- contrib/unbound/iterator/iter_delegpt.h.orig
++++ contrib/unbound/iterator/iter_delegpt.h
+@@ -457,4 +457,29 @@
+ /** get memory in use by dp */
+ size_t delegpt_get_mem(struct delegpt* dp);
+
++/**
++ * See if the addr is on the result list.
++ * @param dp: delegation point.
++ * @param find: the pointer is searched for on the result list.
++ * @return 1 if found, 0 if not found.
++ */
++int delegpt_addr_on_result_list(struct delegpt* dp, struct delegpt_addr* find);
++
++/**
++ * Remove the addr from the usable list.
++ * @param dp: the delegation point.
++ * @param del: the addr to remove from the list, the pointer is searched for.
++ */
++void delegpt_usable_list_remove_addr(struct delegpt* dp,
++ struct delegpt_addr* del);
++
++/**
++ * Add the delegpt_addr back to the result list, if it is not already on
++ * the result list. Also removes it from the usable list.
++ * @param dp: delegation point.
++ * @param a: addr to add, nothing happens if it is already on the result list.
++ * It is removed from the usable list.
++ */
++void delegpt_add_to_result_list(struct delegpt* dp, struct delegpt_addr* a);
++
+ #endif /* ITERATOR_ITER_DELEGPT_H */
+--- contrib/unbound/iterator/iter_priv.c.orig
++++ contrib/unbound/iterator/iter_priv.c
+@@ -207,28 +207,6 @@
+ return sizeof(*priv) + regional_get_mem(priv->region);
+ }
+
+-/** remove RR from msgparse RRset, return true if rrset is entirely bad */
+-static int
+-remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* rrset,
+- struct rr_parse* prev, struct rr_parse** rr, struct sockaddr_storage* addr, socklen_t addrlen)
+-{
+- if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN && str) {
+- uint8_t buf[LDNS_MAX_DOMAINLEN+1];
+- dname_pkt_copy(pkt, buf, rrset->dname);
+- log_name_addr(VERB_QUERY, str, buf, addr, addrlen);
+- }
+- if(prev)
+- prev->next = (*rr)->next;
+- else rrset->rr_first = (*rr)->next;
+- if(rrset->rr_last == *rr)
+- rrset->rr_last = prev;
+- rrset->rr_count --;
+- rrset->size -= (*rr)->size;
+- /* rr struct still exists, but is unlinked, so that in the for loop
+- * the rr->next works fine to continue. */
+- return rrset->rr_count == 0;
+-}
+-
+ int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt,
+ struct rrset_parse* rrset)
+ {
+@@ -261,7 +239,7 @@
+ INET_SIZE);
+ memmove(&addr, &sa, len);
+ if(priv_lookup_addr(priv, &addr, len)) {
+- if(remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, &rr, &addr, len))
++ if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len))
+ return 1;
+ continue;
+ }
+@@ -284,7 +262,7 @@
+ INET6_SIZE);
+ memmove(&addr, &sa, len);
+ if(priv_lookup_addr(priv, &addr, len)) {
+- if(remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, &rr, &addr, len))
++ if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len))
+ return 1;
+ continue;
+ }
+--- contrib/unbound/iterator/iter_resptype.c.orig
++++ contrib/unbound/iterator/iter_resptype.c
+@@ -42,6 +42,7 @@
+ #include "config.h"
+ #include "iterator/iter_resptype.h"
+ #include "iterator/iter_delegpt.h"
++#include "iterator/iterator.h"
+ #include "services/cache/dns.h"
+ #include "util/net_help.h"
+ #include "util/data/dname.h"
+@@ -105,7 +106,8 @@
+
+ enum response_type
+ response_type_from_server(int rdset,
+- struct dns_msg* msg, struct query_info* request, struct delegpt* dp)
++ struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
++ int* empty_nodata_found)
+ {
+ uint8_t* origzone = (uint8_t*)"\000"; /* the default */
+ struct ub_packed_rrset_key* s;
+@@ -284,6 +286,22 @@
+
+ /* If we've gotten this far, this is NOERROR/NODATA (which could
+ * be an entirely empty message) */
++ /* For entirely empty messages, try again, at first, then accept
++ * it it happens more. A regular noerror/nodata response has a soa
++ * negative ttl value in the authority section. This makes it try
++ * again at another authority. And decides between storing a 5 second
++ * empty message or a 5 second servfail response. */
++ if(msg->rep->an_numrrsets == 0 && msg->rep->ns_numrrsets == 0 &&
++ msg->rep->ar_numrrsets == 0) {
++ if(empty_nodata_found) {
++ /* detect as throwaway at first, but accept later. */
++ (*empty_nodata_found)++;
++ if(*empty_nodata_found < EMPTY_NODATA_RETRY_COUNT)
++ return RESPONSE_TYPE_THROWAWAY;
++ return RESPONSE_TYPE_ANSWER;
++ }
++ return RESPONSE_TYPE_ANSWER;
++ }
+ /* check if recursive answer; saying it has empty cache */
+ if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset)
+ return RESPONSE_TYPE_REC_LAME;
+--- contrib/unbound/iterator/iter_resptype.h.orig
++++ contrib/unbound/iterator/iter_resptype.h
+@@ -119,9 +119,11 @@
+ * @param request: the request that generated the response.
+ * @param dp: The delegation point that was being queried
+ * when the response was returned.
++ * @param empty_nodata_found: flag to keep track of empty nodata detection.
+ * @return the response type (CNAME or ANSWER).
+ */
+ enum response_type response_type_from_server(int rdset,
+- struct dns_msg* msg, struct query_info* request, struct delegpt* dp);
++ struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
++ int* empty_nodata_found);
+
+ #endif /* ITERATOR_ITER_RESPTYPE_H */
+--- contrib/unbound/iterator/iter_scrub.c.orig
++++ contrib/unbound/iterator/iter_scrub.c
+@@ -346,6 +346,26 @@
+ return 0;
+ }
+
++/** Check if type is allowed in the authority section */
++static int
++type_allowed_in_authority_section(uint16_t tp)
++{
++ if(tp == LDNS_RR_TYPE_SOA || tp == LDNS_RR_TYPE_NS ||
++ tp == LDNS_RR_TYPE_DS || tp == LDNS_RR_TYPE_NSEC ||
++ tp == LDNS_RR_TYPE_NSEC3)
++ return 1;
++ return 0;
++}
++
++/** Check if type is allowed in the additional section */
++static int
++type_allowed_in_additional_section(uint16_t tp)
++{
++ if(tp == LDNS_RR_TYPE_A || tp == LDNS_RR_TYPE_AAAA)
++ return 1;
++ return 0;
++}
++
+ /**
+ * This routine normalizes a response. This includes removing "irrelevant"
+ * records from the answer and additional sections and (re)synthesizing
+@@ -355,11 +375,13 @@
+ * @param msg: msg to normalize.
+ * @param qinfo: original query.
+ * @param region: where to allocate synthesized CNAMEs.
++ * @param env: module env with config options.
+ * @return 0 on error.
+ */
+ static int
+ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
+- struct query_info* qinfo, struct regional* region)
++ struct query_info* qinfo, struct regional* region,
++ struct module_env* env)
+ {
+ uint8_t* sname = qinfo->qname;
+ size_t snamelen = qinfo->qname_len;
+@@ -511,6 +533,7 @@
+
+ /* Mark additional names from AUTHORITY */
+ while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) {
++ /* protect internals of recursor by making sure to del these */
+ if(rrset->type==LDNS_RR_TYPE_DNAME ||
+ rrset->type==LDNS_RR_TYPE_CNAME ||
+ rrset->type==LDNS_RR_TYPE_A ||
+@@ -519,6 +542,13 @@
+ "RRset:", pkt, msg, prev, &rrset);
+ continue;
+ }
++ /* Allowed list of types in the authority section */
++ if(env->cfg->harden_unknown_additional &&
++ !type_allowed_in_authority_section(rrset->type)) {
++ remove_rrset("normalize: removing irrelevant "
++ "RRset:", pkt, msg, prev, &rrset);
++ continue;
++ }
+ /* only one NS set allowed in authority section */
+ if(rrset->type==LDNS_RR_TYPE_NS) {
+ /* NS set must be pertinent to the query */
+@@ -576,7 +606,6 @@
+ * found in ANSWER and AUTHORITY. */
+ /* These records have not been marked OK previously */
+ while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) {
+- /* FIXME: what about other types? */
+ if(rrset->type==LDNS_RR_TYPE_A ||
+ rrset->type==LDNS_RR_TYPE_AAAA)
+ {
+@@ -589,6 +618,7 @@
+ continue;
+ }
+ }
++ /* protect internals of recursor by making sure to del these */
+ if(rrset->type==LDNS_RR_TYPE_DNAME ||
+ rrset->type==LDNS_RR_TYPE_CNAME ||
+ rrset->type==LDNS_RR_TYPE_NS) {
+@@ -596,6 +626,13 @@
+ "RRset:", pkt, msg, prev, &rrset);
+ continue;
+ }
++ /* Allowed list of types in the additional section */
++ if(env->cfg->harden_unknown_additional &&
++ !type_allowed_in_additional_section(rrset->type)) {
++ remove_rrset("normalize: removing irrelevant "
++ "RRset:", pkt, msg, prev, &rrset);
++ continue;
++ }
+ prev = rrset;
+ rrset = rrset->rrset_all_next;
+ }
+@@ -679,6 +716,56 @@
+ return 0;
+ }
+
++/** Remove individual RRs, if the length is wrong. Returns true if the RRset
++ * has been removed. */
++static int
++scrub_sanitize_rr_length(sldns_buffer* pkt, struct msg_parse* msg,
++ struct rrset_parse* prev, struct rrset_parse** rrset, int* added_ede,
++ struct module_qstate* qstate)
++{
++ struct rr_parse* rr, *rr_prev = NULL;
++ for(rr = (*rrset)->rr_first; rr; rr = rr->next) {
++
++ /* Sanity check for length of records
++ * An A record should be 6 bytes only
++ * (2 bytes for length and 4 for IPv4 addr)*/
++ if((*rrset)->type == LDNS_RR_TYPE_A && rr->size != 6 ) {
++ if(!*added_ede) {
++ *added_ede = 1;
++ errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
++ LDNS_EDE_OTHER);
++ }
++ if(msgparse_rrset_remove_rr("sanitize: removing type A RR of inappropriate length:",
++ pkt, *rrset, rr_prev, rr, NULL, 0)) {
++ remove_rrset("sanitize: removing type A RRset of inappropriate length:",
++ pkt, msg, prev, rrset);
++ return 1;
++ }
++ continue;
++ }
++
++ /* Sanity check for length of records
++ * An AAAA record should be 18 bytes only
++ * (2 bytes for length and 16 for IPv6 addr)*/
++ if((*rrset)->type == LDNS_RR_TYPE_AAAA && rr->size != 18 ) {
++ if(!*added_ede) {
++ *added_ede = 1;
++ errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
++ LDNS_EDE_OTHER);
++ }
++ if(msgparse_rrset_remove_rr("sanitize: removing type AAAA RR of inappropriate length:",
++ pkt, *rrset, rr_prev, rr, NULL, 0)) {
++ remove_rrset("sanitize: removing type AAAA RRset of inappropriate length:",
++ pkt, msg, prev, rrset);
++ return 1;
++ }
++ continue;
++ }
++ rr_prev = rr;
++ }
++ return 0;
++}
++
+ /**
+ * Given a response event, remove suspect RRsets from the response.
+ * "Suspect" rrsets are potentially poison. Note that this routine expects
+@@ -691,15 +778,17 @@
+ * @param zonename: name of server zone.
+ * @param env: module environment with config and cache.
+ * @param ie: iterator environment with private address data.
++ * @param qstate: for setting errinf for EDE error messages.
+ * @return 0 on error.
+ */
+ static int
+ scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
+ struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
+- struct iter_env* ie)
++ struct iter_env* ie, struct module_qstate* qstate)
+ {
+ int del_addi = 0; /* if additional-holding rrsets are deleted, we
+ do not trust the normalized additional-A-AAAA any more */
++ int added_rrlen_ede = 0;
+ struct rrset_parse* rrset, *prev;
+ prev = NULL;
+ rrset = msg->rrset_first;
+@@ -744,6 +833,14 @@
+ rrset = msg->rrset_first;
+ while(rrset) {
+
++ /* Sanity check for length of records */
++ if(rrset->type == LDNS_RR_TYPE_A ||
++ rrset->type == LDNS_RR_TYPE_AAAA) {
++ if(scrub_sanitize_rr_length(pkt, msg, prev, &rrset,
++ &added_rrlen_ede, qstate))
++ continue;
++ }
++
+ /* remove private addresses */
+ if( (rrset->type == LDNS_RR_TYPE_A ||
+ rrset->type == LDNS_RR_TYPE_AAAA)) {
+@@ -817,7 +914,8 @@
+ int
+ scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
+ struct query_info* qinfo, uint8_t* zonename, struct regional* region,
+- struct module_env* env, struct iter_env* ie)
++ struct module_env* env, struct module_qstate* qstate,
++ struct iter_env* ie)
+ {
+ /* basic sanity checks */
+ log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
+@@ -846,10 +944,10 @@
+ }
+
+ /* normalize the response, this cleans up the additional. */
+- if(!scrub_normalize(pkt, msg, qinfo, region))
++ if(!scrub_normalize(pkt, msg, qinfo, region, env))
+ return 0;
+ /* delete all out-of-zone information */
+- if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
++ if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
+ return 0;
+ return 1;
+ }
+--- contrib/unbound/iterator/iter_scrub.h.orig
++++ contrib/unbound/iterator/iter_scrub.h
+@@ -48,6 +48,7 @@
+ struct regional;
+ struct module_env;
+ struct iter_env;
++struct module_qstate;
+
+ /**
+ * Cleanup the passed dns message.
+@@ -59,11 +60,13 @@
+ * Used to determine out of bailiwick information.
+ * @param regional: where to allocate (new) parts of the message.
+ * @param env: module environment with config settings and cache.
++ * @param qstate: for setting errinf for EDE error messages.
+ * @param ie: iterator module environment data.
+ * @return: false if the message is total waste. true if scrubbed with success.
+ */
+ int scrub_message(struct sldns_buffer* pkt, struct msg_parse* msg,
+ struct query_info* qinfo, uint8_t* zonename, struct regional* regional,
+- struct module_env* env, struct iter_env* ie);
++ struct module_env* env, struct module_qstate* qstate,
++ struct iter_env* ie);
+
+ #endif /* ITERATOR_ITER_SCRUB_H */
+--- contrib/unbound/iterator/iter_utils.c.orig
++++ contrib/unbound/iterator/iter_utils.c
+@@ -71,6 +71,11 @@
+ /** time when nameserver glue is said to be 'recent' */
+ #define SUSPICION_RECENT_EXPIRY 86400
+
++/** if NAT64 is enabled and no NAT64 prefix is configured, first fall back to
++ * DNS64 prefix. If that is not configured, fall back to this default value.
++ */
++static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96";
++
+ /** fillup fetch policy array */
+ static void
+ fetch_fill(struct iter_env* ie, const char* str)
+@@ -142,6 +147,7 @@
+ int
+ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+ {
++ const char *nat64_prefix;
+ int i;
+ /* target fetch policy */
+ if(!read_fetch_policy(iter_env, cfg->target_fetch_policy))
+@@ -172,8 +178,32 @@
+ }
+
+ }
++
++ nat64_prefix = cfg->nat64_prefix;
++ if(!nat64_prefix)
++ nat64_prefix = cfg->dns64_prefix;
++ if(!nat64_prefix)
++ nat64_prefix = DEFAULT_NAT64_PREFIX;
++ if(!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr,
++ &iter_env->nat64_prefix_addrlen,
++ &iter_env->nat64_prefix_net)) {
++ log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
++ return 0;
++ }
++ if(!addr_is_ip6(&iter_env->nat64_prefix_addr,
++ iter_env->nat64_prefix_addrlen)) {
++ log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix);
++ return 0;
++ }
++ if(!prefixnet_is_nat64(iter_env->nat64_prefix_net)) {
++ log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
++ nat64_prefix);
++ return 0;
++ }
++
+ iter_env->supports_ipv6 = cfg->do_ip6;
+ iter_env->supports_ipv4 = cfg->do_ip4;
++ iter_env->use_nat64 = cfg->do_nat64;
+ iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
+ iter_env->max_sent_count = cfg->max_sent_count;
+ iter_env->max_query_restarts = cfg->max_query_restarts;
+@@ -240,7 +270,8 @@
+ if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) {
+ return -1; /* there is no ip6 available */
+ }
+- if(!iter_env->supports_ipv4 && !addr_is_ip6(&a->addr, a->addrlen)) {
++ if(!iter_env->supports_ipv4 && !iter_env->use_nat64 &&
++ !addr_is_ip6(&a->addr, a->addrlen)) {
+ return -1; /* there is no ip4 available */
+ }
+ /* check lameness - need zone , class info */
+@@ -747,10 +778,15 @@
+
+ int
+ iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
+- struct delegpt* dp, int supports_ipv4, int supports_ipv6)
++ struct delegpt* dp, int supports_ipv4, int supports_ipv6,
++ int use_nat64)
+ {
+ struct delegpt_ns* ns;
+ struct delegpt_addr* a;
++
++ if(supports_ipv6 && use_nat64)
++ supports_ipv4 = 1;
++
+ /* check:
+ * o RD qflag is on.
+ * o no addresses are provided.
+@@ -1310,8 +1346,7 @@
+ for(a=dp->target_list; a; a = a->next_target) {
+ if(a->attempts >= outbound_msg_retry) {
+ /* add back to result list */
+- a->next_result = dp->result_list;
+- dp->result_list = a;
++ delegpt_add_to_result_list(dp, a);
+ }
+ if(a->attempts > d)
+ a->attempts -= d;
+--- contrib/unbound/iterator/iter_utils.h.orig
++++ contrib/unbound/iterator/iter_utils.h
+@@ -189,10 +189,13 @@
+ * if not, then the IPv4 addresses are useless.
+ * @param supports_ipv6: if we support ipv6 for lookups to the target.
+ * if not, then the IPv6 addresses are useless.
++ * @param use_nat64: if we support NAT64 for lookups to the target.
++ * if yes, IPv4 addresses are useful even if we don't support IPv4.
+ * @return true if dp is useless.
+ */
+-int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
+- struct delegpt* dp, int supports_ipv4, int supports_ipv6);
++int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
++ struct delegpt* dp, int supports_ipv4, int supports_ipv6,
++ int use_nat64);
+
+ /**
+ * See if qname has DNSSEC needs. This is true if there is a trust anchor above
+--- contrib/unbound/iterator/iterator.c.orig
++++ contrib/unbound/iterator/iterator.c
+@@ -255,7 +255,7 @@
+ log_err("out of memory adding missing");
+ }
+ delegpt_mark_neg(dpns, qstate->qinfo.qtype);
+- if((dpns->got4 == 2 || !ie->supports_ipv4) &&
++ if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+ (dpns->got6 == 2 || !ie->supports_ipv6)) {
+ dpns->resolved = 1; /* mark as failed */
+ target_count_increase_nx(super_iq, 1);
+@@ -302,81 +302,65 @@
+ static int
+ error_response_cache(struct module_qstate* qstate, int id, int rcode)
+ {
+- if(!qstate->no_cache_store) {
+- /* store in cache */
+- struct reply_info err;
+- if(qstate->prefetch_leeway > NORR_TTL) {
+- verbose(VERB_ALGO, "error response for prefetch in cache");
+- /* attempt to adjust the cache entry prefetch */
+- if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo,
+- NORR_TTL, qstate->query_flags))
+- return error_response(qstate, id, rcode);
+- /* if that fails (not in cache), fall through to store err */
++ struct reply_info err;
++ struct msgreply_entry* msg;
++ if(qstate->no_cache_store) {
++ return error_response(qstate, id, rcode);
++ }
++ if(qstate->prefetch_leeway > NORR_TTL) {
++ verbose(VERB_ALGO, "error response for prefetch in cache");
++ /* attempt to adjust the cache entry prefetch */
++ if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo,
++ NORR_TTL, qstate->query_flags))
++ return error_response(qstate, id, rcode);
++ /* if that fails (not in cache), fall through to store err */
++ }
++ if((msg=msg_cache_lookup(qstate->env,
++ qstate->qinfo.qname, qstate->qinfo.qname_len,
++ qstate->qinfo.qtype, qstate->qinfo.qclass,
++ qstate->query_flags, 0,
++ qstate->env->cfg->serve_expired_ttl_reset)) != NULL) {
++ struct reply_info* rep = (struct reply_info*)msg->entry.data;
++ if(qstate->env->cfg->serve_expired &&
++ qstate->env->cfg->serve_expired_ttl_reset && rep &&
++ *qstate->env->now + qstate->env->cfg->serve_expired_ttl
++ > rep->serve_expired_ttl) {
++ verbose(VERB_ALGO, "reset serve-expired-ttl for "
++ "response in cache");
++ rep->serve_expired_ttl = *qstate->env->now +
++ qstate->env->cfg->serve_expired_ttl;
+ }
+- if(qstate->env->cfg->serve_expired) {
+- /* if serving expired contents, and such content is
+- * already available, don't overwrite this servfail */
+- struct msgreply_entry* msg;
+- if((msg=msg_cache_lookup(qstate->env,
+- qstate->qinfo.qname, qstate->qinfo.qname_len,
+- qstate->qinfo.qtype, qstate->qinfo.qclass,
+- qstate->query_flags, 0,
+- qstate->env->cfg->serve_expired_ttl_reset))
+- != NULL) {
+- if(qstate->env->cfg->serve_expired_ttl_reset) {
+- struct reply_info* rep =
+- (struct reply_info*)msg->entry.data;
+- if(rep && *qstate->env->now +
+- qstate->env->cfg->serve_expired_ttl >
+- rep->serve_expired_ttl) {
+- rep->serve_expired_ttl =
+- *qstate->env->now +
+- qstate->env->cfg->serve_expired_ttl;
+- }
+- }
+- lock_rw_unlock(&msg->entry.lock);
+- return error_response(qstate, id, rcode);
+- }
+- /* serving expired contents, but nothing is cached
+- * at all, so the servfail cache entry is useful
+- * (stops waste of time on this servfail NORR_TTL) */
+- } else {
+- /* don't overwrite existing (non-expired) data in
+- * cache with a servfail */
+- struct msgreply_entry* msg;
+- if((msg=msg_cache_lookup(qstate->env,
+- qstate->qinfo.qname, qstate->qinfo.qname_len,
+- qstate->qinfo.qtype, qstate->qinfo.qclass,
+- qstate->query_flags, *qstate->env->now, 0))
+- != NULL) {
+- struct reply_info* rep = (struct reply_info*)
+- msg->entry.data;
+- if(FLAGS_GET_RCODE(rep->flags) ==
+- LDNS_RCODE_NOERROR ||
+- FLAGS_GET_RCODE(rep->flags) ==
+- LDNS_RCODE_NXDOMAIN) {
+- /* we have a good entry,
+- * don't overwrite */
+- lock_rw_unlock(&msg->entry.lock);
+- return error_response(qstate, id, rcode);
+- }
+- lock_rw_unlock(&msg->entry.lock);
+- }
+-
++ if(rep && (FLAGS_GET_RCODE(rep->flags) ==
++ LDNS_RCODE_NOERROR ||
++ FLAGS_GET_RCODE(rep->flags) ==
++ LDNS_RCODE_NXDOMAIN ||
++ FLAGS_GET_RCODE(rep->flags) ==
++ LDNS_RCODE_YXDOMAIN) &&
++ (qstate->env->cfg->serve_expired ||
++ *qstate->env->now <= rep->ttl)) {
++ /* we have a good entry, don't overwrite */
++ lock_rw_unlock(&msg->entry.lock);
++ return error_response(qstate, id, rcode);
+ }
+- memset(&err, 0, sizeof(err));
+- err.flags = (uint16_t)(BIT_QR | BIT_RA);
+- FLAGS_SET_RCODE(err.flags, rcode);
+- err.qdcount = 1;
+- err.ttl = NORR_TTL;
+- err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl);
+- err.serve_expired_ttl = NORR_TTL;
+- /* do not waste time trying to validate this servfail */
+- err.security = sec_status_indeterminate;
+- verbose(VERB_ALGO, "store error response in message cache");
+- iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL,
+- qstate->query_flags, qstate->qstarttime);
+- }
++ lock_rw_unlock(&msg->entry.lock);
++ /* nothing interesting is cached (already error response or
++ * expired good record when we don't serve expired), so this
++ * servfail cache entry is useful (stops waste of time on this
++ * servfail NORR_TTL) */
++ }
++ /* store in cache */
++ memset(&err, 0, sizeof(err));
++ err.flags = (uint16_t)(BIT_QR | BIT_RA);
++ FLAGS_SET_RCODE(err.flags, rcode);
++ err.qdcount = 1;
++ err.ttl = NORR_TTL;
++ err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl);
++ err.serve_expired_ttl = NORR_TTL;
++ /* do not waste time trying to validate this servfail */
++ err.security = sec_status_indeterminate;
++ verbose(VERB_ALGO, "store error response in message cache");
++ iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL,
++ qstate->query_flags, qstate->qstarttime);
+ return error_response(qstate, id, rcode);
+ }
+
+@@ -590,6 +574,54 @@
+ return 1;
+ }
+
++/** fill fail address for later recovery */
++static void
++fill_fail_addr(struct iter_qstate* iq, struct sockaddr_storage* addr,
++ socklen_t addrlen)
++{
++ if(addrlen == 0) {
++ iq->fail_addr_type = 0;
++ return;
++ }
++ if(((struct sockaddr_in*)addr)->sin_family == AF_INET) {
++ iq->fail_addr_type = 4;
++ memcpy(&iq->fail_addr.in,
++ &((struct sockaddr_in*)addr)->sin_addr,
++ sizeof(iq->fail_addr.in));
++ }
++#ifdef AF_INET6
++ else if(((struct sockaddr_in*)addr)->sin_family == AF_INET6) {
++ iq->fail_addr_type = 6;
++ memcpy(&iq->fail_addr.in6,
++ &((struct sockaddr_in6*)addr)->sin6_addr,
++ sizeof(iq->fail_addr.in6));
++ }
++#endif
++ else {
++ iq->fail_addr_type = 0;
++ }
++}
++
++/** print fail addr to string */
++static void
++print_fail_addr(struct iter_qstate* iq, char* buf, size_t len)
++{
++ if(iq->fail_addr_type == 4) {
++ if(inet_ntop(AF_INET, &iq->fail_addr.in, buf,
++ (socklen_t)len) == 0)
++ (void)strlcpy(buf, "(inet_ntop error)", len);
++ }
++#ifdef AF_INET6
++ else if(iq->fail_addr_type == 6) {
++ if(inet_ntop(AF_INET6, &iq->fail_addr.in6, buf,
++ (socklen_t)len) == 0)
++ (void)strlcpy(buf, "(inet_ntop error)", len);
++ }
++#endif
++ else
++ (void)strlcpy(buf, "", len);
++}
++
+ /** add response specific error information for log servfail */
+ static void
+ errinf_reply(struct module_qstate* qstate, struct iter_qstate* iq)
+@@ -597,16 +629,14 @@
+ if(qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail)
+ return;
+ if((qstate->reply && qstate->reply->remote_addrlen != 0) ||
+- (iq->fail_reply && iq->fail_reply->remote_addrlen != 0)) {
++ (iq->fail_addr_type != 0)) {
+ char from[256], frm[512];
+ if(qstate->reply && qstate->reply->remote_addrlen != 0)
+ addr_to_str(&qstate->reply->remote_addr,
+ qstate->reply->remote_addrlen, from,
+ sizeof(from));
+ else
+- addr_to_str(&iq->fail_reply->remote_addr,
+- iq->fail_reply->remote_addrlen, from,
+- sizeof(from));
++ print_fail_addr(iq, from, sizeof(from));
+ snprintf(frm, sizeof(frm), "from %s", from);
+ errinf(qstate, frm);
+ }
+@@ -1137,7 +1167,7 @@
+ * Generate a NS check request to obtain authoritative information
+ * on an NS rrset.
+ *
+- * @param qstate: the qtstate that triggered the need to prime.
++ * @param qstate: the qstate that triggered the need to prime.
+ * @param iq: iterator query state.
+ * @param id: module id.
+ */
+@@ -1419,6 +1449,39 @@
+ }
+ iq->qchase.qname = sname;
+ iq->qchase.qname_len = slen;
++ if(qstate->env->auth_zones) {
++ /* apply rpz qname triggers after cname */
++ struct dns_msg* forged_response =
++ rpz_callback_from_iterator_cname(qstate, iq);
++ while(forged_response && reply_find_rrset_section_an(
++ forged_response->rep, iq->qchase.qname,
++ iq->qchase.qname_len, LDNS_RR_TYPE_CNAME,
++ iq->qchase.qclass)) {
++ /* another cname to follow */
++ if(!handle_cname_response(qstate, iq, forged_response,
++ &sname, &slen)) {
++ errinf(qstate, "malloc failure, CNAME info");
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
++ }
++ iq->qchase.qname = sname;
++ iq->qchase.qname_len = slen;
++ forged_response =
++ rpz_callback_from_iterator_cname(qstate, iq);
++ }
++ if(forged_response != NULL) {
++ qstate->ext_state[id] = module_finished;
++ qstate->return_rcode = LDNS_RCODE_NOERROR;
++ qstate->return_msg = forged_response;
++ iq->response = forged_response;
++ next_state(iq, FINISHED_STATE);
++ if(!iter_prepend(iq, qstate->return_msg, qstate->region)) {
++ log_err("rpz: after cached cname, prepend rrsets: out of memory");
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
++ }
++ qstate->return_msg->qinfo = qstate->qinfo;
++ return 0;
++ }
++ }
+ /* This *is* a query restart, even if it is a cheap
+ * one. */
+ iq->dp = NULL;
+@@ -1451,6 +1514,19 @@
+ errinf(qstate, "malloc failure for forward zone");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
++ if((qstate->query_flags&BIT_RD)==0) {
++ /* If the server accepts RD=0 queries and forwards
++ * with RD=1, then if the server is listed as an NS
++ * entry, it starts query loops. Stop that loop by
++ * disallowing the query. The RD=0 was previously used
++ * to check the cache with allow_snoop. For stubs,
++ * the iterator pass would have primed the stub and
++ * then cached information can be used for further
++ * queries. */
++ verbose(VERB_ALGO, "cannot forward RD=0 query, to stop query loops");
++ errinf(qstate, "cannot forward RD=0 query");
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
++ }
+ iq->refetch_glue = 0;
+ iq->minimisation_state = DONOT_MINIMISE_STATE;
+ /* the request has been forwarded.
+@@ -1560,18 +1636,19 @@
+
+ /* see if this dp not useless.
+ * It is useless if:
+- * o all NS items are required glue.
++ * o all NS items are required glue.
+ * or the query is for NS item that is required glue.
+ * o no addresses are provided.
+ * o RD qflag is on.
+ * Instead, go up one level, and try to get even further
+- * If the root was useless, use safety belt information.
++ * If the root was useless, use safety belt information.
+ * Only check cache returns, because replies for servers
+ * could be useless but lead to loops (bumping into the
+ * same server reply) if useless-checked.
+ */
+- if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
+- iq->dp, ie->supports_ipv4, ie->supports_ipv6)) {
++ if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
++ iq->dp, ie->supports_ipv4, ie->supports_ipv6,
++ ie->use_nat64)) {
+ struct delegpt* retdp = NULL;
+ if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &retdp)) {
+ if(retdp) {
+@@ -1932,7 +2009,7 @@
+ break;
+ }
+ /* Send the A request. */
+- if(ie->supports_ipv4 &&
++ if((ie->supports_ipv4 || ie->use_nat64) &&
+ ((ns->lame && !ns->done_pside4) ||
+ (!ns->lame && !ns->got4))) {
+ if(!generate_target_query(qstate, iq, id,
+@@ -2085,14 +2162,14 @@
+ /* if this nameserver is at a delegation point, but that
+ * delegation point is a stub and we cannot go higher, skip*/
+ if( ((ie->supports_ipv6 && !ns->done_pside6) ||
+- (ie->supports_ipv4 && !ns->done_pside4)) &&
++ ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) &&
+ !can_have_last_resort(qstate->env, ns->name, ns->namelen,
+ iq->qchase.qclass, NULL)) {
+ log_nametypeclass(VERB_ALGO, "cannot pside lookup ns "
+ "because it is also a stub/forward,",
+ ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
+ if(ie->supports_ipv6) ns->done_pside6 = 1;
+- if(ie->supports_ipv4) ns->done_pside4 = 1;
++ if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1;
+ continue;
+ }
+ /* query for parent-side A and AAAA for nameservers */
+@@ -2117,7 +2194,7 @@
+ return 0;
+ }
+ }
+- if(ie->supports_ipv4 && !ns->done_pside4) {
++ if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) {
+ /* Send the A request. */
+ if(!generate_parentside_target_query(qstate, iq, id,
+ ns->name, ns->namelen,
+@@ -2259,6 +2336,8 @@
+ int tf_policy;
+ struct delegpt_addr* target;
+ struct outbound_entry* outq;
++ struct sockaddr_storage real_addr;
++ socklen_t real_addrlen;
+ int auth_fallback = 0;
+ uint8_t* qout_orig = NULL;
+ size_t qout_orig_len = 0;
+@@ -2384,7 +2463,7 @@
+ }
+ if(!ie->supports_ipv6)
+ delegpt_no_ipv6(iq->dp);
+- if(!ie->supports_ipv4)
++ if(!ie->supports_ipv4 && !ie->use_nat64)
+ delegpt_no_ipv4(iq->dp);
+ delegpt_log(VERB_ALGO, iq->dp);
+
+@@ -2805,23 +2884,36 @@
+ /* We have a valid target. */
+ if(verbosity >= VERB_QUERY) {
+ log_query_info(VERB_QUERY, "sending query:", &iq->qinfo_out);
+- log_name_addr(VERB_QUERY, "sending to target:", iq->dp->name,
++ log_name_addr(VERB_QUERY, "sending to target:", iq->dp->name,
+ &target->addr, target->addrlen);
+ verbose(VERB_ALGO, "dnssec status: %s%s",
+ iq->dnssec_expected?"expected": "not expected",
+ iq->dnssec_lame_query?" but lame_query anyway": "");
+ }
++
++ real_addr = target->addr;
++ real_addrlen = target->addrlen;
++
++ if(ie->use_nat64 && target->addr.ss_family == AF_INET) {
++ addr_to_nat64(&target->addr, &ie->nat64_prefix_addr,
++ ie->nat64_prefix_addrlen, ie->nat64_prefix_net,
++ &real_addr, &real_addrlen);
++ log_name_addr(VERB_QUERY, "applied NAT64:",
++ iq->dp->name, &real_addr, real_addrlen);
++ }
++
+ fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query));
+ outq = (*qstate->env->send_query)(&iq->qinfo_out,
+ iq->chase_flags | (iq->chase_to_rd?BIT_RD:0),
+ /* unset CD if to forwarder(RD set) and not dnssec retry
+ * (blacklist nonempty) and no trust-anchors are configured
+ * above the qname or on the first attempt when dnssec is on */
+- EDNS_DO| ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&&
++ (qstate->env->cfg->disable_edns_do?0:EDNS_DO)|
++ ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&&
+ !qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env,
+ &iq->qinfo_out)||target->attempts==1)?0:BIT_CD),
+ iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted(
+- ie, iq), sq_check_ratelimit, &target->addr, target->addrlen,
++ ie, iq), sq_check_ratelimit, &real_addr, real_addrlen,
+ iq->dp->name, iq->dp->namelen,
+ (iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream),
+ (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream),
+@@ -2838,7 +2930,7 @@
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ log_addr(VERB_QUERY, "error sending query to auth server",
+- &target->addr, target->addrlen);
++ &real_addr, real_addrlen);
+ if(qstate->env->cfg->qname_minimisation)
+ iq->minimisation_state = SKIP_MINIMISE_STATE;
+ return next_state(iq, QUERYTARGETS_STATE);
+@@ -2882,7 +2974,7 @@
+ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
+ struct iter_env* ie, int id)
+ {
+- int dnsseclame = 0;
++ int dnsseclame = 0, origtypecname = 0, orig_empty_nodata_found;
+ enum response_type type;
+
+ iq->num_current_queries--;
+@@ -2902,12 +2994,25 @@
+ return next_state(iq, QUERYTARGETS_STATE);
+ }
+ iq->timeout_count = 0;
++ orig_empty_nodata_found = iq->empty_nodata_found;
+ type = response_type_from_server(
+ (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
+- iq->response, &iq->qinfo_out, iq->dp);
++ iq->response, &iq->qinfo_out, iq->dp, &iq->empty_nodata_found);
+ iq->chase_to_rd = 0;
+ /* remove TC flag, if this is erroneously set by TCP upstream */
+ iq->response->rep->flags &= ~BIT_TC;
++ if(orig_empty_nodata_found != iq->empty_nodata_found &&
++ iq->empty_nodata_found < EMPTY_NODATA_RETRY_COUNT) {
++ /* try to search at another server */
++ if(qstate->reply) {
++ struct delegpt_addr* a = delegpt_find_addr(
++ iq->dp, &qstate->reply->remote_addr,
++ qstate->reply->remote_addrlen);
++ /* make selection disprefer it */
++ if(a) a->lame = 1;
++ }
++ return next_state(iq, QUERYTARGETS_STATE);
++ }
+ if(type == RESPONSE_TYPE_REFERRAL && (iq->chase_flags&BIT_RD) &&
+ !iq->auth_zone_response) {
+ /* When forwarding (RD bit is set), we handle referrals
+@@ -2965,6 +3070,8 @@
+ /* YXDOMAIN is a permanent error, no need to retry */
+ type = RESPONSE_TYPE_ANSWER;
+ }
++ if(type == RESPONSE_TYPE_CNAME)
++ origtypecname = 1;
+ if(type == RESPONSE_TYPE_CNAME && iq->response->rep->an_numrrsets >= 1
+ && ntohs(iq->response->rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_DNAME) {
+ uint8_t* sname = NULL;
+@@ -3050,11 +3157,14 @@
+ iq->minimisation_state = DONOT_MINIMISE_STATE;
+ }
+ if(FLAGS_GET_RCODE(iq->response->rep->flags) ==
+- LDNS_RCODE_NXDOMAIN) {
++ LDNS_RCODE_NXDOMAIN && !origtypecname) {
+ /* Stop resolving when NXDOMAIN is DNSSEC
+ * signed. Based on assumption that nameservers
+ * serving signed zones do not return NXDOMAIN
+ * for empty-non-terminals. */
++ /* If this response is actually a CNAME type,
++ * the nxdomain rcode may not be for the qname,
++ * and so it is not the final response. */
+ if(iq->dnssec_expected)
+ return final_state(iq);
+ /* Make subrequest to validate intermediate
+@@ -3182,7 +3292,7 @@
+ (*qstate->env->detach_subs)(qstate);
+ iq->num_target_queries = 0;
+ iq->response = NULL;
+- iq->fail_reply = NULL;
++ iq->fail_addr_type = 0;
+ verbose(VERB_ALGO, "cleared outbound list for next round");
+ return next_state(iq, QUERYTARGETS_STATE);
+ } else if(type == RESPONSE_TYPE_CNAME) {
+@@ -3438,7 +3548,7 @@
+ iq->response->rep->flags &= ~(BIT_RD|BIT_RA); /* ignore rec-lame */
+ type = response_type_from_server(
+ (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
+- iq->response, &iq->qchase, iq->dp);
++ iq->response, &iq->qchase, iq->dp, NULL);
+ if(type == RESPONSE_TYPE_ANSWER) {
+ qstate->return_rcode = LDNS_RCODE_NOERROR;
+ qstate->return_msg = iq->response;
+@@ -3564,7 +3674,7 @@
+ } else {
+ verbose(VERB_ALGO, "iterator TargetResponse failed");
+ delegpt_mark_neg(dpns, qstate->qinfo.qtype);
+- if((dpns->got4 == 2 || !ie->supports_ipv4) &&
++ if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+ (dpns->got6 == 2 || !ie->supports_ipv6)) {
+ dpns->resolved = 1; /* fail the target */
+ /* do not count cached answers */
+@@ -3809,6 +3919,26 @@
+ /* make sure QR flag is on */
+ iq->response->rep->flags |= BIT_QR;
+
++ /* explicitly set the EDE string to NULL */
++ iq->response->rep->reason_bogus_str = NULL;
++ if((qstate->env->cfg->val_log_level >= 2 ||
++ qstate->env->cfg->log_servfail) && qstate->errinf &&
++ !qstate->env->cfg->val_log_squelch) {
++ char* err_str = errinf_to_str_misc(qstate);
++ if(err_str) {
++ size_t err_str_len = strlen(err_str);
++ verbose(VERB_ALGO, "iterator EDE: %s", err_str);
++ /* allocate space and store the error
++ * string */
++ iq->response->rep->reason_bogus_str = regional_alloc(
++ qstate->region,
++ sizeof(char) * (err_str_len+1));
++ memcpy(iq->response->rep->reason_bogus_str,
++ err_str, err_str_len+1);
++ }
++ free(err_str);
++ }
++
+ /* we have finished processing this query */
+ qstate->ext_state[id] = module_finished;
+
+@@ -3987,7 +4117,8 @@
+ }
+
+ /* parse message */
+- iq->fail_reply = qstate->reply;
++ fill_fail_addr(iq, &qstate->reply->remote_addr,
++ qstate->reply->remote_addrlen);
+ prs = (struct msg_parse*)regional_alloc(qstate->env->scratch,
+ sizeof(struct msg_parse));
+ if(!prs) {
+@@ -4031,7 +4162,7 @@
+
+ /* normalize and sanitize: easy to delete items from linked lists */
+ if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name,
+- qstate->env->scratch, qstate->env, ie)) {
++ qstate->env->scratch, qstate->env, qstate, ie)) {
+ /* if 0x20 enabled, start fallback, but we have no message */
+ if(event == module_event_capsfail && !iq->caps_fallback) {
+ iq->caps_fallback = 1;
+--- contrib/unbound/iterator/iterator.h.orig
++++ contrib/unbound/iterator/iterator.h
+@@ -101,9 +101,11 @@
+ * Chosen so that the UNKNOWN_SERVER_NICENESS falls within the band of a
+ * fast server, this causes server exploration as a side benefit. msec. */
+ #define RTT_BAND 400
++/** Number of retries for empty nodata packets before it is accepted. */
++#define EMPTY_NODATA_RETRY_COUNT 2
+
+ /**
+- * Global state for the iterator.
++ * Global state for the iterator.
+ */
+ struct iter_env {
+ /** A flag to indicate whether or not we have an IPv6 route */
+@@ -112,6 +114,18 @@
+ /** A flag to indicate whether or not we have an IPv4 route */
+ int supports_ipv4;
+
++ /** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */
++ int use_nat64;
++
++ /** NAT64 prefix address, cf. dns64_env->prefix_addr */
++ struct sockaddr_storage nat64_prefix_addr;
++
++ /** sizeof(sockaddr_in6) */
++ socklen_t nat64_prefix_addrlen;
++
++ /** CIDR mask length of NAT64 prefix */
++ int nat64_prefix_net;
++
+ /** A set of inetaddrs that should never be queried. */
+ struct iter_donotq* donotq;
+
+@@ -403,6 +417,11 @@
+ */
+ int refetch_glue;
+
++ /**
++ * This flag detects that a completely empty nodata was received,
++ * already so that it is accepted later. */
++ int empty_nodata_found;
++
+ /** list of pending queries to authoritative servers. */
+ struct outbound_list outlist;
+
+@@ -439,7 +458,14 @@
+ /** true if there have been parse failures of reply packets */
+ int parse_failures;
+ /** a failure printout address for last received answer */
+- struct comm_reply* fail_reply;
++ union {
++ struct in_addr in;
++#ifdef AF_INET6
++ struct in6_addr in6;
++#endif
++ } fail_addr;
++ /** which fail_addr, 0 is nothing, 4 or 6 */
++ int fail_addr_type;
+ };
+
+ /**
+--- contrib/unbound/libunbound/libworker.c.orig
++++ contrib/unbound/libunbound/libworker.c
+@@ -62,6 +62,7 @@
+ #include "util/random.h"
+ #include "util/config_file.h"
+ #include "util/netevent.h"
++#include "util/proxy_protocol.h"
+ #include "util/storage/lookup3.h"
+ #include "util/storage/slabhash.h"
+ #include "util/net_help.h"
+@@ -168,15 +169,15 @@
+ hints_delete(w->env->hints);
+ w->env->hints = NULL;
+ }
+- if(cfg->ssl_upstream || (cfg->tls_cert_bundle && cfg->tls_cert_bundle[0]) || cfg->tls_win_cert) {
+- w->sslctx = connect_sslctx_create(NULL, NULL,
+- cfg->tls_cert_bundle, cfg->tls_win_cert);
+- if(!w->sslctx) {
+- /* to make the setup fail after unlock */
+- hints_delete(w->env->hints);
+- w->env->hints = NULL;
+- }
++#ifdef HAVE_SSL
++ w->sslctx = connect_sslctx_create(NULL, NULL,
++ cfg->tls_cert_bundle, cfg->tls_win_cert);
++ if(!w->sslctx) {
++ /* to make the setup fail after unlock */
++ hints_delete(w->env->hints);
++ w->env->hints = NULL;
+ }
++#endif
+ if(!w->is_bg || w->is_bg_thread) {
+ lock_basic_unlock(&ctx->cfglock);
+ }
+@@ -265,6 +266,7 @@
+ w->env->kill_sub = &mesh_state_delete;
+ w->env->detect_cycle = &mesh_detect_cycle;
+ comm_base_timept(w->base, &w->env->now, &w->env->now_tv);
++ pp_init(&sldns_write_uint16, &sldns_write_uint32);
+ return w;
+ }
+
+@@ -605,6 +607,8 @@
+ edns->opt_list_out = NULL;
+ edns->opt_list_inplace_cb_out = NULL;
+ edns->padding_block_size = 0;
++ edns->cookie_present = 0;
++ edns->cookie_valid = 0;
+ if(sldns_buffer_capacity(w->back->udp_buff) < 65535)
+ edns->udp_size = (uint16_t)sldns_buffer_capacity(
+ w->back->udp_buff);
+--- contrib/unbound/libunbound/unbound-event.h.orig
++++ contrib/unbound/libunbound/unbound-event.h
+@@ -52,8 +52,8 @@
+ * unbound was compiled with, otherwise it wouldn't work, the event and
+ * event_base structures would be different.
+ */
+-#ifndef _UB_UNBOUND_EVENT_H
+-#define _UB_UNBOUND_EVENT_H
++#ifndef UB_UNBOUND_EVENT_H
++#define UB_UNBOUND_EVENT_H
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -262,4 +262,4 @@
+ }
+ #endif
+
+-#endif /* _UB_UNBOUND_H */
++#endif /* UB_UNBOUND_EVENT_H */
+--- contrib/unbound/libunbound/unbound.h.orig
++++ contrib/unbound/libunbound/unbound.h
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -36,7 +36,7 @@
+ /**
+ * \file
+ *
+- * This file contains functions to resolve DNS queries and
++ * This file contains functions to resolve DNS queries and
+ * validate the answers. Synchronously and asynchronously.
+ *
+ * Several ways to use this interface from an application wishing
+@@ -65,7 +65,7 @@
+ * ... or process() calls my_callback() with results.
+ *
+ * ... if the application has nothing more to do, wait for answer
+- * ub_wait(ctx);
++ * ub_wait(ctx);
+ *
+ * Application threaded. Blocking.
+ * Blocking, same as above. The current thread does the work.
+@@ -83,7 +83,7 @@
+ * CRYPTO_set_id_callback and CRYPTO_set_locking_callback.
+ *
+ * If no threading is compiled in, the above async example uses fork(2) to
+- * create a process to perform the work. The forked process exits when the
++ * create a process to perform the work. The forked process exits when the
+ * calling process exits, or ctx_delete() is called.
+ * Otherwise, for asynchronous with threading, a worker thread is created.
+ *
+@@ -94,8 +94,8 @@
+ * The second calls another worker thread (or process) to perform the work.
+ * And no buffers need to be set up, but a context-switch happens.
+ */
+-#ifndef _UB_UNBOUND_H
+-#define _UB_UNBOUND_H
++#ifndef UB_UNBOUND_H
++#define UB_UNBOUND_H
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -128,10 +128,10 @@
+ /** the class asked for */
+ int qclass;
+
+- /**
+- * a list of network order DNS rdata items, terminated with a
++ /**
++ * a list of network order DNS rdata items, terminated with a
+ * NULL pointer, so that data[0] is the first result entry,
+- * data[1] the second, and the last entry is NULL.
++ * data[1] the second, and the last entry is NULL.
+ * If there was no data, data[0] is NULL.
+ */
+ char** data;
+@@ -139,8 +139,8 @@
+ /** the length in bytes of the data items, len[i] for data[i] */
+ int* len;
+
+- /**
+- * canonical name for the result (the final cname).
++ /**
++ * canonical name for the result (the final cname).
+ * zero terminated string.
+ * May be NULL if no canonical name exists.
+ */
+@@ -165,9 +165,9 @@
+ */
+ int havedata;
+
+- /**
++ /**
+ * If there was no data, and the domain did not exist, this is true.
+- * If it is false, and there was no data, then the domain name
++ * If it is false, and there was no data, then the domain name
+ * is purported to exist, but the requested data type is not available.
+ */
+ int nxdomain;
+@@ -182,19 +182,19 @@
+ */
+ int secure;
+
+- /**
+- * If the result was not secure (secure==0), and this result is due
++ /**
++ * If the result was not secure (secure==0), and this result is due
+ * to a security failure, bogus is true.
+ * This means the data has been actively tampered with, signatures
+- * failed, expected signatures were not present, timestamps on
++ * failed, expected signatures were not present, timestamps on
+ * signatures were out of date and so on.
+ *
+- * If !secure and !bogus, this can happen if the data is not secure
+- * because security is disabled for that domain name.
++ * If !secure and !bogus, this can happen if the data is not secure
++ * because security is disabled for that domain name.
+ * This means the data is from a domain where data is not signed.
+ */
+ int bogus;
+-
++
+ /**
+ * If the result is bogus this contains a string (zero terminated)
+ * that describes the failure. There may be other errors as well
+@@ -222,7 +222,7 @@
+ * The readable function definition looks like:
+ * void my_callback(void* my_arg, int err, struct ub_result* result);
+ * It is called with
+- * void* my_arg: your pointer to a (struct of) data of your choice,
++ * void* my_arg: your pointer to a (struct of) data of your choice,
+ * or NULL.
+ * int err: if 0 all is OK, otherwise an error occurred and no results
+ * are forthcoming.
+@@ -301,8 +301,8 @@
+ * This is a power-users interface that lets you specify all sorts
+ * of options.
+ * @param str: the string is malloced and returned here. NULL on error.
+- * The caller must free() the string. In cases with multiple
+- * entries (auto-trust-anchor-file), a newline delimited list is
++ * The caller must free() the string. In cases with multiple
++ * entries (auto-trust-anchor-file), a newline delimited list is
+ * returned in the string.
+ * @return 0 if OK else an error code (malloc failure, syntax error).
+ */
+@@ -321,10 +321,10 @@
+ int ub_ctx_config(struct ub_ctx* ctx, const char* fname);
+
+ /**
+- * Set machine to forward DNS queries to, the caching resolver to use.
+- * IP4 or IP6 address. Forwards all DNS requests to that machine, which
+- * is expected to run a recursive resolver. If the proxy is not
+- * DNSSEC-capable, validation may fail. Can be called several times, in
++ * Set machine to forward DNS queries to, the caching resolver to use.
++ * IP4 or IP6 address. Forwards all DNS requests to that machine, which
++ * is expected to run a recursive resolver. If the proxy is not
++ * DNSSEC-capable, validation may fail. Can be called several times, in
+ * that case the addresses are used as backup servers.
+ *
+ * To read the list of nameservers from /etc/resolv.conf (from DHCP or so),
+@@ -389,7 +389,7 @@
+
+ /**
+ * Read list of hosts from the filename given.
+- * Usually "/etc/hosts".
++ * Usually "/etc/hosts".
+ * These addresses are not flagged as DNSSEC secure when queried for.
+ *
+ * @param ctx: context.
+@@ -403,7 +403,7 @@
+ /**
+ * Add a trust anchor to the given context.
+ * The trust anchor is a string, on one line, that holds a valid DNSKEY or
+- * DS RR.
++ * DS RR.
+ * @param ctx: context.
+ * At this time it is only possible to add trusted keys before the
+ * first resolve is done.
+@@ -465,7 +465,7 @@
+ * Set debug verbosity for the context
+ * Output is directed to stderr.
+ * @param ctx: context.
+- * @param d: debug level, 0 is off, 1 is very minimal, 2 is detailed,
++ * @param d: debug level, 0 is off, 1 is very minimal, 2 is detailed,
+ * and 3 is lots.
+ * @return 0 if OK, else error.
+ */
+@@ -474,10 +474,10 @@
+ /**
+ * Set a context behaviour for asynchronous action.
+ * @param ctx: context.
+- * @param dothread: if true, enables threading and a call to resolve_async()
++ * @param dothread: if true, enables threading and a call to resolve_async()
+ * creates a thread to handle work in the background.
+ * If false, a process is forked to handle work in the background.
+- * Changes to this setting after async() calls have been made have
++ * Changes to this setting after async() calls have been made have
+ * no effect (delete and re-create the context to change).
+ * @return 0 if OK, else error.
+ */
+@@ -495,7 +495,7 @@
+
+ /**
+ * Wait for a context to finish with results. Calls ub_process() after
+- * the wait for you. After the wait, there are no more outstanding
++ * the wait for you. After the wait, there are no more outstanding
+ * asynchronous queries.
+ * @param ctx: context.
+ * @return: 0 if OK, else error.
+@@ -530,11 +530,11 @@
+ * @param rrtype: type of RR in host order, 1 is A (address).
+ * @param rrclass: class of RR in host order, 1 is IN (for internet).
+ * @param result: the result data is returned in a newly allocated result
+- * structure. May be NULL on return, return value is set to an error
++ * structure. May be NULL on return, return value is set to an error
+ * in that case (out of memory).
+ * @return 0 if OK, else error.
+ */
+-int ub_resolve(struct ub_ctx* ctx, const char* name, int rrtype,
++int ub_resolve(struct ub_ctx* ctx, const char* name, int rrtype,
+ int rrclass, struct ub_result** result);
+
+ /**
+@@ -561,11 +561,11 @@
+ * If an error happens during processing, your callback will be called
+ * with error set to a nonzero value (and result==NULL).
+ * @param async_id: if you pass a non-NULL value, an identifier number is
+- * returned for the query as it is in progress. It can be used to
++ * returned for the query as it is in progress. It can be used to
+ * cancel the query.
+ * @return 0 if OK, else error.
+ */
+-int ub_resolve_async(struct ub_ctx* ctx, const char* name, int rrtype,
++int ub_resolve_async(struct ub_ctx* ctx, const char* name, int rrtype,
+ int rrclass, void* mydata, ub_callback_type callback, int* async_id);
+
+ /**
+@@ -589,7 +589,7 @@
+ */
+ void ub_resolve_free(struct ub_result* result);
+
+-/**
++/**
+ * Convert error value to a human readable string.
+ * @param err: error code from one of the libunbound functions.
+ * The error codes are from the type enum ub_ctx_err.
+@@ -605,7 +605,7 @@
+ int ub_ctx_print_local_zones(struct ub_ctx* ctx);
+
+ /**
+- * Add a new zone with the zonetype to the local authority info of the
++ * Add a new zone with the zonetype to the local authority info of the
+ * library.
+ * @param ctx: context. Is finalized by the routine.
+ * @param zone_name: name of the zone in text, "example.com"
+@@ -613,7 +613,7 @@
+ * @param zone_type: type of the zone (like for unbound.conf) in text.
+ * @return 0 if OK, else error.
+ */
+-int ub_ctx_zone_add(struct ub_ctx* ctx, const char *zone_name,
++int ub_ctx_zone_add(struct ub_ctx* ctx, const char *zone_name,
+ const char *zone_type);
+
+ /**
+@@ -649,7 +649,7 @@
+ */
+ const char* ub_version(void);
+
+-/**
++/**
+ * Some global statistics that are not in struct stats_info,
+ * this struct is shared on a shm segment (shm-key in unbound.conf)
+ */
+@@ -695,13 +695,22 @@
+ long long num_queries;
+ /** number of queries that have been dropped/ratelimited by ip. */
+ long long num_queries_ip_ratelimited;
++ /** number of queries with a valid DNS Cookie. */
++ long long num_queries_cookie_valid;
++ /** number of queries with only the client part of the DNS Cookie. */
++ long long num_queries_cookie_client;
++ /** number of queries with invalid DNS Cookie. */
++ long long num_queries_cookie_invalid;
+ /** number of queries that had a cache-miss. */
+ long long num_queries_missed_cache;
+ /** number of prefetch queries - cachehits with prefetch */
+ long long num_queries_prefetch;
+-
++ /** number of queries which are too late to process */
++ long long num_queries_timed_out;
++ /** the longest wait time in the queue */
++ long long max_query_time_us;
+ /**
+- * Sum of the querylistsize of the worker for
++ * Sum of the querylistsize of the worker for
+ * every query that missed cache. To calculate average.
+ */
+ long long sum_query_list_size;
+@@ -773,12 +782,12 @@
+ long long tcp_accept_usage;
+ /** expired answers served from cache */
+ long long ans_expired;
+- /** histogram data exported to array
++ /** histogram data exported to array
+ * if the array is the same size, no data is lost, and
+ * if all histograms are same size (is so by default) then
+ * adding up works well. */
+ long long hist[UB_STATS_BUCKET_NUM];
+-
++
+ /** number of message cache entries */
+ long long msg_cache_count;
+ /** number of rrset cache entries */
+@@ -788,6 +797,11 @@
+ /** number of key cache entries */
+ long long key_cache_count;
+
++ /** maximum number of collisions in the msg cache */
++ long long msg_cache_max_collisions;
++ /** maximum number of collisions in the rrset cache */
++ long long rrset_cache_max_collisions;
++
+ /** number of queries that used dnscrypt */
+ long long num_query_dnscrypt_crypted;
+ /** number of queries that queried dnscrypt certificates */
+@@ -819,6 +833,8 @@
+ /** number of queries answered from edns-subnet specific data, and
+ * the answer was from the edns-subnet cache. */
+ long long num_query_subnet_cache;
++ /** number of queries served from cachedb */
++ long long num_query_cachedb;
+ /** number of bytes in the stream wait buffers */
+ long long mem_stream_wait;
+ /** number of bytes in the HTTP2 query buffers */
+@@ -831,7 +847,7 @@
+ long long rpz_action[UB_STATS_RPZ_ACTION_NUM];
+ };
+
+-/**
++/**
+ * Statistics to send over the control pipe when asked
+ * This struct is made to be memcopied, sent in binary.
+ * shm mapped with (number+1) at num_threads+1, with first as total
+@@ -860,4 +876,4 @@
+ }
+ #endif
+
+-#endif /* _UB_UNBOUND_H */
++#endif /* UB_UNBOUND_H */
+--- contrib/unbound/services/authzone.c.orig
++++ contrib/unbound/services/authzone.c
+@@ -1306,8 +1306,8 @@
+ auth_data_delete(node);
+ }
+ if(z->rpz) {
+- rpz_remove_rr(z->rpz, z->namelen, dname, dname_len, rr_type,
+- rr_class, rdata, rdatalen);
++ rpz_remove_rr(z->rpz, z->name, z->namelen, dname, dname_len,
++ rr_type, rr_class, rdata, rdatalen);
+ }
+ return 1;
+ }
+@@ -2475,6 +2475,7 @@
+ struct auth_rrset** rrset)
+ {
+ struct auth_data* n = node;
++ struct auth_rrset* lookrrset;
+ *ce = NULL;
+ *rrset = NULL;
+ if(!node_exact) {
+@@ -2497,21 +2498,23 @@
+ /* see if the current candidate has issues */
+ /* not zone apex and has type NS */
+ if(n->namelen != z->namelen &&
+- (*rrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) &&
++ (lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) &&
+ /* delegate here, but DS at exact the dp has notype */
+ (qinfo->qtype != LDNS_RR_TYPE_DS ||
+ n->namelen != qinfo->qname_len)) {
+ /* referral */
+ /* this is ce and the lowernode is nonexisting */
+ *ce = n;
+- return 0;
++ *rrset = lookrrset;
++ node_exact = 0;
+ }
+ /* not equal to qname and has type DNAME */
+ if(n->namelen != qinfo->qname_len &&
+- (*rrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) {
++ (lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) {
+ /* this is ce and the lowernode is nonexisting */
+ *ce = n;
+- return 0;
++ *rrset = lookrrset;
++ node_exact = 0;
+ }
+
+ if(*ce == NULL && !domain_has_only_nsec3(n)) {
+@@ -5420,6 +5423,8 @@
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+ edns.padding_block_size = 0;
++ edns.cookie_present = 0;
++ edns.cookie_valid = 0;
+ if(sldns_buffer_capacity(buf) < 65535)
+ edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
+ else edns.udp_size = 65535;
+@@ -6613,6 +6618,8 @@
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+ edns.padding_block_size = 0;
++ edns.cookie_present = 0;
++ edns.cookie_valid = 0;
+ if(sldns_buffer_capacity(buf) < 65535)
+ edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
+ else edns.udp_size = 65535;
+@@ -7510,7 +7517,7 @@
+ size_t j;
+ if(!rrlist[i])
+ continue;
+- if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
++ if(rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
+ query_dname_compare(z->name, node->name)==0) {
+ /* omit RRSIGs over type ZONEMD at apex */
+ continue;
+@@ -7767,6 +7774,7 @@
+ enum sec_status sec;
+ struct val_env* ve;
+ int m;
++ int verified = 0;
+ m = modstack_find(mods, "validator");
+ if(m == -1) {
+ auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have "
+@@ -7790,7 +7798,7 @@
+ "zonemd: verify %s RRset with DNSKEY", typestr);
+ }
+ sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, NULL,
+- LDNS_SECTION_ANSWER, NULL);
++ LDNS_SECTION_ANSWER, NULL, &verified);
+ if(sec == sec_status_secure) {
+ return 1;
+ }
+--- contrib/unbound/services/cache/dns.c.orig
++++ contrib/unbound/services/cache/dns.c
+@@ -132,31 +132,6 @@
+ slabhash_remove(env->msg_cache, h, &k);
+ }
+
+-/** remove servfail msg cache entry */
+-static void
+-msg_del_servfail(struct module_env* env, struct query_info* qinfo,
+- uint32_t flags)
+-{
+- struct msgreply_entry* e;
+- /* see if the entry is servfail, and then remove it, so that
+- * lookups move from the cacheresponse stage to the recursionresponse
+- * stage */
+- e = msg_cache_lookup(env, qinfo->qname, qinfo->qname_len,
+- qinfo->qtype, qinfo->qclass, flags, 0, 0);
+- if(!e) return;
+- /* we don't check for the ttl here, also expired servfail entries
+- * are removed. If the user uses serve-expired, they would still be
+- * used to answer from cache */
+- if(FLAGS_GET_RCODE(((struct reply_info*)e->entry.data)->flags)
+- != LDNS_RCODE_SERVFAIL) {
+- lock_rw_unlock(&e->entry.lock);
+- return;
+- }
+- lock_rw_unlock(&e->entry.lock);
+- msg_cache_remove(env, qinfo->qname, qinfo->qname_len, qinfo->qtype,
+- qinfo->qclass, flags);
+-}
+-
+ void
+ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
+ hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside,
+@@ -182,13 +157,20 @@
+ /* we do not store the message, but we did store the RRs,
+ * which could be useful for delegation information */
+ verbose(VERB_ALGO, "TTL 0: dropped msg from cache");
+- free(rep);
+- /* if the message is SERVFAIL in cache, remove that SERVFAIL,
++ reply_info_delete(rep, NULL);
++ /* if the message is in the cache, remove that msg,
+ * so that the TTL 0 response can be returned for future
+- * responses (i.e. don't get answered by the servfail from
++ * responses (i.e. don't get answered from
+ * cache, but instead go to recursion to get this TTL0
+- * response). */
+- msg_del_servfail(env, qinfo, flags);
++ * response).
++ * Possible messages that could be in the cache:
++ * - SERVFAIL
++ * - NXDOMAIN
++ * - NODATA
++ * - an older record that is expired
++ * - an older record that did not yet expire */
++ msg_cache_remove(env, qinfo->qname, qinfo->qname_len,
++ qinfo->qtype, qinfo->qclass, flags);
+ return;
+ }
+
+@@ -610,6 +592,7 @@
+ if(!msg->rep)
+ return NULL;
+ msg->rep->reason_bogus = LDNS_EDE_NONE;
++ msg->rep->reason_bogus_str = NULL;
+ if(num > RR_COUNT_MAX)
+ return NULL; /* integer overflow protection */
+ msg->rep->rrsets = (struct ub_packed_rrset_key**)
+@@ -672,6 +655,10 @@
+ msg->rep->rrset_count = r->rrset_count;
+ msg->rep->authoritative = r->authoritative;
+ msg->rep->reason_bogus = r->reason_bogus;
++ if(r->reason_bogus_str) {
++ msg->rep->reason_bogus_str = regional_strdup(region, r->reason_bogus_str);
++ }
++
+ if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) {
+ return NULL;
+ }
+@@ -703,6 +690,28 @@
+ return msg;
+ }
+
++struct dns_msg*
++dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region)
++{
++ size_t i;
++ struct dns_msg* res = NULL;
++ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count);
++ if(!res) return NULL;
++ *res->rep = *origin->rep;
++ if(origin->rep->reason_bogus_str) {
++ res->rep->reason_bogus_str = regional_strdup(region,
++ origin->rep->reason_bogus_str);
++ }
++ for(i=0; i<res->rep->rrset_count; i++) {
++ res->rep->rrsets[i] = packed_rrset_copy_region(
++ origin->rep->rrsets[i], region, 0);
++ if(!res->rep->rrsets[i]) {
++ return NULL;
++ }
++ }
++ return res;
++}
++
+ /** synthesize RRset-only response from cached RRset item */
+ static struct dns_msg*
+ rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
+@@ -1075,7 +1084,6 @@
+ /* ttl must be relative ;i.e. 0..86400 not time(0)+86400.
+ * the env->now is added to message and RRsets in this routine. */
+ /* the leeway is used to invalidate other rrsets earlier */
+-
+ if(is_referral) {
+ /* store rrsets */
+ struct rrset_ref ref;
+@@ -1092,7 +1100,7 @@
+ ((ntohs(ref.key->rk.type)==LDNS_RR_TYPE_NS
+ && !pside) ? qstarttime:*env->now + leeway));
+ }
+- free(rep);
++ reply_info_delete(rep, NULL);
+ return 1;
+ } else {
+ /* store msg, and rrsets */
+--- contrib/unbound/services/cache/dns.h.orig
++++ contrib/unbound/services/cache/dns.h
+@@ -164,6 +164,15 @@
+ struct reply_info* r, struct regional* region, time_t now,
+ int allow_expired, struct regional* scratch);
+
++/**
++ * Deep copy a dns_msg to a region.
++ * @param origin: the dns_msg to copy.
++ * @param region: the region to copy all the data to.
++ * @return the new dns_msg or NULL on malloc error.
++ */
++struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin,
++ struct regional* region);
++
+ /**
+ * Find cached message
+ * @param env: module environment with the DNS cache.
+--- contrib/unbound/services/cache/infra.c.orig
++++ contrib/unbound/services/cache/infra.c
+@@ -67,6 +67,11 @@
+ * in queries per second. */
+ int infra_ip_ratelimit = 0;
+
++/** ratelimit value for client ip addresses,
++ * in queries per second.
++ * For clients with a valid DNS Cookie. */
++int infra_ip_ratelimit_cookie = 0;
++
+ size_t
+ infra_sizefunc(void* k, void* ATTR_UNUSED(d))
+ {
+@@ -1051,9 +1056,50 @@
+ return s;
+ }
+
++/* Returns 1 if the limit has not been exceeded, 0 otherwise. */
++static int
++check_ip_ratelimit(struct sockaddr_storage* addr, socklen_t addrlen,
++ struct sldns_buffer* buffer, int premax, int max, int has_cookie)
++{
++ int limit;
++
++ if(has_cookie) limit = infra_ip_ratelimit_cookie;
++ else limit = infra_ip_ratelimit;
++
++ /* Disabled */
++ if(limit == 0) return 1;
++
++ if(premax <= limit && max > limit) {
++ char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12];
++ addr_to_str(addr, addrlen, client_ip, sizeof(client_ip));
++ qnm[0]=0;
++ if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE &&
++ LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) {
++ (void)sldns_wire2str_rrquestion_buf(
++ sldns_buffer_at(buffer, LDNS_HEADER_SIZE),
++ sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE,
++ qnm, sizeof(qnm));
++ if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n')
++ qnm[strlen(qnm)-1] = 0; /*remove newline*/
++ if(strchr(qnm, '\t'))
++ *strchr(qnm, '\t') = ' ';
++ if(strchr(qnm, '\t'))
++ *strchr(qnm, '\t') = ' ';
++ verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s %s",
++ client_ip, limit,
++ has_cookie?"(cookie)":"", qnm);
++ } else {
++ verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s (no query name)",
++ client_ip, limit,
++ has_cookie?"(cookie)":"");
++ }
++ }
++ return (max <= limit);
++}
++
+ int infra_ip_ratelimit_inc(struct infra_cache* infra,
+ struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
+- int backoff, struct sldns_buffer* buffer)
++ int has_cookie, int backoff, struct sldns_buffer* buffer)
+ {
+ int max;
+ struct lruhash_entry* entry;
+@@ -1070,31 +1116,8 @@
+ (*cur)++;
+ max = infra_rate_max(entry->data, timenow, backoff);
+ lock_rw_unlock(&entry->lock);
+-
+- if(premax <= infra_ip_ratelimit && max > infra_ip_ratelimit) {
+- char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12];
+- addr_to_str(addr, addrlen, client_ip, sizeof(client_ip));
+- qnm[0]=0;
+- if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE &&
+- LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) {
+- (void)sldns_wire2str_rrquestion_buf(
+- sldns_buffer_at(buffer, LDNS_HEADER_SIZE),
+- sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE,
+- qnm, sizeof(qnm));
+- if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n')
+- qnm[strlen(qnm)-1] = 0; /*remove newline*/
+- if(strchr(qnm, '\t'))
+- *strchr(qnm, '\t') = ' ';
+- if(strchr(qnm, '\t'))
+- *strchr(qnm, '\t') = ' ';
+- verbose(VERB_OPS, "ip_ratelimit exceeded %s %d %s",
+- client_ip, infra_ip_ratelimit, qnm);
+- } else {
+- verbose(VERB_OPS, "ip_ratelimit exceeded %s %d (no query name)",
+- client_ip, infra_ip_ratelimit);
+- }
+- }
+- return (max <= infra_ip_ratelimit);
++ return check_ip_ratelimit(addr, addrlen, buffer, premax, max,
++ has_cookie);
+ }
+
+ /* create */
+--- contrib/unbound/services/cache/infra.h.orig
++++ contrib/unbound/services/cache/infra.h
+@@ -153,6 +153,8 @@
+
+ /** ip ratelimit, 0 is off */
+ extern int infra_ip_ratelimit;
++/** ip ratelimit for DNS Cookie clients, 0 is off */
++extern int infra_ip_ratelimit_cookie;
+
+ /**
+ * key for ip_ratelimit lookups, a source IP.
+@@ -419,13 +421,14 @@
+ * @param addr: client address
+ * @param addrlen: client address length
+ * @param timenow: what time it is now.
++ * @param has_cookie: if the request came with a DNS Cookie.
+ * @param backoff: if backoff is enabled.
+ * @param buffer: with query for logging.
+ * @return 1 if it could be incremented. 0 if the increment overshot the
+ * ratelimit and the query should be dropped. */
+ int infra_ip_ratelimit_inc(struct infra_cache* infra,
+ struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
+- int backoff, struct sldns_buffer* buffer);
++ int has_cookie, int backoff, struct sldns_buffer* buffer);
+
+ /**
+ * Get memory used by the infra cache.
+--- contrib/unbound/services/listen_dnsport.c.orig
++++ contrib/unbound/services/listen_dnsport.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -79,9 +79,11 @@
+ #ifdef HAVE_NET_IF_H
+ #include <net/if.h>
+ #endif
+-
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++#include <linux/net_tstamp.h>
++#endif
+ /** number of queued TCP connections for listen() */
+-#define TCP_BACKLOG 256
++#define TCP_BACKLOG 256
+
+ #ifndef THREADS_DISABLED
+ /** lock on the counter of stream buffer memory */
+@@ -187,7 +189,7 @@
+ log_err("systemd sd_listen_fds(): %s", strerror(-r));
+ return -1;
+ }
+-
++
+ for(i = 0; i < r; i++) {
+ if(sd_is_socket(SD_LISTEN_FDS_START + i, family, socktype, listen)) {
+ s = SD_LISTEN_FDS_START + i;
+@@ -253,7 +255,7 @@
+ return -1;
+ }
+ #else
+- if(WSAGetLastError() == WSAEAFNOSUPPORT ||
++ if(WSAGetLastError() == WSAEAFNOSUPPORT ||
+ WSAGetLastError() == WSAEPROTONOSUPPORT) {
+ *noproto = 1;
+ return -1;
+@@ -270,7 +272,7 @@
+ #endif
+ if(listen) {
+ #ifdef SO_REUSEADDR
+- if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
++ if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
+ (socklen_t)sizeof(on)) < 0) {
+ log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s",
+ sock_strerror(errno));
+@@ -368,9 +370,9 @@
+ socklen_t slen = (socklen_t)sizeof(got);
+ # ifdef SO_RCVBUFFORCE
+ /* Linux specific: try to use root permission to override
+- * system limits on rcvbuf. The limit is stored in
++ * system limits on rcvbuf. The limit is stored in
+ * /proc/sys/net/core/rmem_max or sysctl net.core.rmem_max */
+- if(setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&rcv,
++ if(setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&rcv,
+ (socklen_t)sizeof(rcv)) < 0) {
+ if(errno != EPERM) {
+ log_err("setsockopt(..., SO_RCVBUFFORCE, "
+@@ -381,7 +383,7 @@
+ return -1;
+ }
+ # endif /* SO_RCVBUFFORCE */
+- if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&rcv,
++ if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&rcv,
+ (socklen_t)sizeof(rcv)) < 0) {
+ log_err("setsockopt(..., SO_RCVBUF, "
+ "...) failed: %s", sock_strerror(errno));
+@@ -392,7 +394,7 @@
+ }
+ /* check if we got the right thing or if system
+ * reduced to some system max. Warn if so */
+- if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&got,
++ if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&got,
+ &slen) >= 0 && got < rcv/2) {
+ log_warn("so-rcvbuf %u was not granted. "
+ "Got %u. To fix: start with "
+@@ -413,9 +415,9 @@
+ socklen_t slen = (socklen_t)sizeof(got);
+ # ifdef SO_SNDBUFFORCE
+ /* Linux specific: try to use root permission to override
+- * system limits on sndbuf. The limit is stored in
++ * system limits on sndbuf. The limit is stored in
+ * /proc/sys/net/core/wmem_max or sysctl net.core.wmem_max */
+- if(setsockopt(s, SOL_SOCKET, SO_SNDBUFFORCE, (void*)&snd,
++ if(setsockopt(s, SOL_SOCKET, SO_SNDBUFFORCE, (void*)&snd,
+ (socklen_t)sizeof(snd)) < 0) {
+ if(errno != EPERM) {
+ log_err("setsockopt(..., SO_SNDBUFFORCE, "
+@@ -426,7 +428,7 @@
+ return -1;
+ }
+ # endif /* SO_SNDBUFFORCE */
+- if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&snd,
++ if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&snd,
+ (socklen_t)sizeof(snd)) < 0) {
+ log_err("setsockopt(..., SO_SNDBUF, "
+ "...) failed: %s", sock_strerror(errno));
+@@ -437,7 +439,7 @@
+ }
+ /* check if we got the right thing or if system
+ * reduced to some system max. Warn if so */
+- if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&got,
++ if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&got,
+ &slen) >= 0 && got < snd/2) {
+ log_warn("so-sndbuf %u was not granted. "
+ "Got %u. To fix: start with "
+@@ -469,7 +471,7 @@
+ # endif
+ ) {
+ int val=(v6only==2)?0:1;
+- if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
++ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void*)&val, (socklen_t)sizeof(val)) < 0) {
+ log_err("setsockopt(..., IPV6_V6ONLY"
+ ", ...) failed: %s", sock_strerror(errno));
+@@ -576,7 +578,7 @@
+ int action;
+ # if defined(IP_PMTUDISC_OMIT)
+ action = IP_PMTUDISC_OMIT;
+- if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER,
++ if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER,
+ &action, (socklen_t)sizeof(action)) < 0) {
+
+ if (errno != EINVAL) {
+@@ -609,7 +611,7 @@
+ /* the IP_DONTFRAG option if defined in the 11.0 OSX headers,
+ * but does not work on that version, so we exclude it */
+ int off = 0;
+- if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG,
++ if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG,
+ &off, (socklen_t)sizeof(off)) < 0) {
+ log_err("setsockopt(..., IP_DONTFRAG, ...) failed: %s",
+ strerror(errno));
+@@ -647,7 +649,7 @@
+ if(WSAGetLastError() != WSAEADDRINUSE &&
+ WSAGetLastError() != WSAEADDRNOTAVAIL &&
+ !(WSAGetLastError() == WSAEACCES && verbosity < 4 && !listen)) {
+- log_err_addr("can't bind socket",
++ log_err_addr("can't bind socket",
+ wsa_strerror(WSAGetLastError()),
+ (struct sockaddr_storage*)addr, addrlen);
+ }
+@@ -749,7 +751,7 @@
+ }
+ #endif
+ #ifdef SO_REUSEADDR
+- if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
++ if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
+ (socklen_t)sizeof(on)) < 0) {
+ log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s",
+ sock_strerror(errno));
+@@ -793,7 +795,7 @@
+ && !got_fd_from_systemd
+ # endif
+ ) {
+- if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
++ if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void*)&on, (socklen_t)sizeof(on)) < 0) {
+ log_err("setsockopt(..., IPV6_V6ONLY, ...) failed: %s",
+ sock_strerror(errno));
+@@ -845,7 +847,7 @@
+ addr->ai_addrlen);
+ }
+ #else
+- log_err_addr("can't bind socket",
++ log_err_addr("can't bind socket",
+ wsa_strerror(WSAGetLastError()),
+ (struct sockaddr_storage*)addr->ai_addr,
+ addr->ai_addrlen);
+@@ -873,7 +875,7 @@
+ /* 5 is recommended on linux */
+ qlen = 5;
+ #endif
+- if ((setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &qlen,
++ if ((setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &qlen,
+ sizeof(qlen))) == -1 ) {
+ #ifdef ENOPROTOOPT
+ /* squelch ENOPROTOOPT: freebsd server mode with kernel support
+@@ -999,7 +1001,7 @@
+ * Create socket from getaddrinfo results
+ */
+ static int
+-make_sock(int stype, const char* ifname, const char* port,
++make_sock(int stype, const char* ifname, const char* port,
+ struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd,
+ int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind,
+ int use_systemd, int dscp, struct unbound_socket* ub_sock)
+@@ -1015,10 +1017,10 @@
+ return -1;
+ }
+ #endif
+- log_err("node %s:%s getaddrinfo: %s %s",
++ log_err("node %s:%s getaddrinfo: %s %s",
+ ifname?ifname:"default", port, gai_strerror(r),
+ #ifdef EAI_SYSTEM
+- r==EAI_SYSTEM?(char*)strerror(errno):""
++ (r==EAI_SYSTEM?(char*)strerror(errno):"")
+ #else
+ ""
+ #endif
+@@ -1055,7 +1057,7 @@
+
+ /** make socket and first see if ifname contains port override info */
+ static int
+-make_sock_port(int stype, const char* ifname, const char* port,
++make_sock_port(int stype, const char* ifname, const char* port,
+ struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd,
+ int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind,
+ int use_systemd, int dscp, struct unbound_socket* ub_sock)
+@@ -1114,9 +1116,28 @@
+ return 1;
+ }
+
++/** set fd to receive software timestamps */
++static int
++set_recvtimestamp(int s)
++{
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++ int opt = SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE;
++ if (setsockopt(s, SOL_SOCKET, SO_TIMESTAMPNS, (void*)&opt, (socklen_t)sizeof(opt)) < 0) {
++ log_err("setsockopt(..., SO_TIMESTAMPNS, ...) failed: %s",
++ strerror(errno));
++ return 0;
++ }
++ return 1;
++#else
++ log_err("packets timestamping is not supported on this platform");
++ (void)s;
++ return 0;
++#endif
++}
++
+ /** set fd to receive source address packet info */
+ static int
+-set_recvpktinfo(int s, int family)
++set_recvpktinfo(int s, int family)
+ {
+ #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) || (defined(IP_RECVDSTADDR) && defined(IP_SENDSRCADDR)) || defined(IP_PKTINFO)
+ int on = 1;
+@@ -1214,6 +1235,9 @@
+ * @param use_systemd: if true, fetch sockets from systemd.
+ * @param dnscrypt_port: dnscrypt service port number
+ * @param dscp: DSCP to use.
++ * @param sock_queue_timeout: the sock_queue_timeout from config. Seconds to
++ * wait to discard if UDP packets have waited for long in the socket
++ * buffer.
+ * @return: returns false on error.
+ */
+ static int
+@@ -1223,7 +1247,8 @@
+ struct config_strlist* tls_additional_port, int https_port,
+ struct config_strlist* proxy_protocol_port,
+ int* reuseport, int transparent, int tcp_mss, int freebind,
+- int http2_nodelay, int use_systemd, int dnscrypt_port, int dscp)
++ int http2_nodelay, int use_systemd, int dnscrypt_port, int dscp,
++ int sock_queue_timeout)
+ {
+ int s, noip6=0;
+ int is_https = if_is_https(ifname, port, https_port);
+@@ -1252,7 +1277,8 @@
+ if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1,
+ &noip6, rcv, snd, reuseport, transparent,
+ tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock)) == -1) {
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ if(noip6) {
+ log_warn("IPv6 protocol not available");
+@@ -1263,15 +1289,20 @@
+ /* getting source addr packet info is highly non-portable */
+ if(!set_recvpktinfo(s, hints->ai_family)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
++ if (sock_queue_timeout && !set_recvtimestamp(s)) {
++ log_warn("socket timestamping is not available");
++ }
+ if(!port_insert(list, s, is_dnscrypt
+ ?listen_type_udpancil_dnscrypt:listen_type_udpancil,
+ is_pp2, ub_sock)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
+@@ -1283,7 +1314,8 @@
+ if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1,
+ &noip6, rcv, snd, reuseport, transparent,
+ tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock)) == -1) {
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ if(noip6) {
+ log_warn("IPv6 protocol not available");
+@@ -1291,11 +1323,17 @@
+ }
+ return 0;
+ }
++ if (sock_queue_timeout && !set_recvtimestamp(s)) {
++ log_warn("socket timestamping is not available");
++ }
+ if(!port_insert(list, s, is_dnscrypt
+- ?listen_type_udp_dnscrypt:listen_type_udp,
++ ?listen_type_udp_dnscrypt :
++ (sock_queue_timeout ?
++ listen_type_udpancil:listen_type_udp),
+ is_pp2, ub_sock)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
+@@ -1318,7 +1356,8 @@
+ if((s = make_sock_port(SOCK_STREAM, ifname, port, hints, 1,
+ &noip6, 0, 0, reuseport, transparent, tcp_mss, nodelay,
+ freebind, use_systemd, dscp, ub_sock)) == -1) {
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ if(noip6) {
+ /*log_warn("IPv6 protocol not available");*/
+@@ -1330,7 +1369,8 @@
+ verbose(VERB_ALGO, "setup TCP for SSL service");
+ if(!port_insert(list, s, port_type, is_pp2, ub_sock)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
+@@ -1338,7 +1378,7 @@
+ return 1;
+ }
+
+-/**
++/**
+ * Add items to commpoint list in front.
+ * @param c: commpoint to add.
+ * @param front: listen struct.
+@@ -1389,7 +1429,7 @@
+ }
+ }
+
+-struct listen_dnsport*
++struct listen_dnsport*
+ listen_create(struct comm_base* base, struct listen_port* ports,
+ size_t bufsize, int tcp_accept_count, int tcp_idle_timeout,
+ int harden_large_queries, uint32_t http_max_streams,
+@@ -1460,9 +1500,13 @@
+ }
+ } else if(ports->ftype == listen_type_udpancil ||
+ ports->ftype == listen_type_udpancil_dnscrypt) {
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
+ cp = comm_point_create_udp_ancil(base, ports->fd,
+ front->udp_buff, ports->pp2_enabled, cb,
+ cb_arg, ports->socket);
++#else
++ log_warn("This system does not support UDP ancilliary data.");
++#endif
+ }
+ if(!cp) {
+ log_err("can't create commpoint");
+@@ -1525,10 +1569,10 @@
+ }
+ }
+
+-void
++void
+ listen_delete(struct listen_dnsport* front)
+ {
+- if(!front)
++ if(!front)
+ return;
+ listen_list_delete(front->cps);
+ #ifdef USE_DNSCRYPT
+@@ -1802,7 +1846,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1819,7 +1863,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1838,7 +1882,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1854,7 +1898,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1872,7 +1916,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1888,7 +1932,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1908,7 +1952,8 @@
+ }
+ /* rc_ports don't have ub_socket */
+ if(list->socket) {
+- freeaddrinfo(list->socket->addr);
++ if(list->socket->addr)
++ freeaddrinfo(list->socket->addr);
+ free(list->socket);
+ }
+ free(list);
+@@ -1919,8 +1964,8 @@
+ size_t listen_get_mem(struct listen_dnsport* listen)
+ {
+ struct listen_list* p;
+- size_t s = sizeof(*listen) + sizeof(*listen->base) +
+- sizeof(*listen->udp_buff) +
++ size_t s = sizeof(*listen) + sizeof(*listen->base) +
++ sizeof(*listen->udp_buff) +
+ sldns_buffer_capacity(listen->udp_buff);
+ #ifdef USE_DNSCRYPT
+ s += sizeof(*listen->dnscrypt_udp_buff);
+@@ -2001,7 +2046,7 @@
+ }
+ req->open_req_list = NULL;
+ req->num_open_req = 0;
+-
++
+ /* free pending writable result packets */
+ item = req->done_req_list;
+ while(item) {
+@@ -2060,7 +2105,7 @@
+ wr = 1;
+ if(!req->read_is_closed)
+ rd = 1;
+-
++
+ if(wr) {
+ req->cp->tcp_is_reading = 0;
+ comm_point_stop_listening(req->cp);
+@@ -2196,7 +2241,7 @@
+ }
+ req->in_worker_handle = 0;
+ /* it should be waiting in the mesh for recursion.
+- * If mesh failed to add a new entry and called commpoint_drop_reply.
++ * If mesh failed to add a new entry and called commpoint_drop_reply.
+ * Then the mesh state has been cleared. */
+ if(req->is_drop) {
+ /* the reply has been dropped, stream has been closed. */
+@@ -2256,7 +2301,7 @@
+ last = req->done_req_list;
+ while(last && last->next)
+ last = last->next;
+-
++
+ /* create new element */
+ item = (struct tcp_req_done_item*)malloc(sizeof(*item));
+ if(!item) {
+@@ -2615,7 +2660,7 @@
+ "buffer already assigned to stream");
+ return -1;
+ }
+-
++
+ /* the c->buffer might be used by mesh_send_reply and no be cleard
+ * need to be cleared before use */
+ sldns_buffer_clear(h2_session->c->buffer);
+--- contrib/unbound/services/localzone.c.orig
++++ contrib/unbound/services/localzone.c
+@@ -1308,6 +1308,7 @@
+ else rep.ns_numrrsets = 1;
+ rep.rrset_count = 1;
+ rep.rrsets = &rrset;
++ rep.reason_bogus = LDNS_EDE_NONE;
+ udpsize = edns->udp_size;
+ edns->edns_version = EDNS_ADVERTISED_VERSION;
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+@@ -1603,7 +1604,7 @@
+ struct local_data key;
+ struct local_data* ld = NULL;
+ struct local_rrset* lr = NULL;
+- if(z->type == local_zone_always_transparent)
++ if(z->type == local_zone_always_transparent || z->type == local_zone_block_a)
+ return 1;
+ if(z->type != local_zone_transparent
+ && z->type != local_zone_typetransparent
+@@ -1679,6 +1680,16 @@
+ } else if(lz_type == local_zone_typetransparent
+ || lz_type == local_zone_always_transparent) {
+ /* no NODATA or NXDOMAINS for this zone type */
++ return 0;
++ } else if(lz_type == local_zone_block_a) {
++ /* Return NODATA for all A queries */
++ if(qinfo->qtype == LDNS_RR_TYPE_A) {
++ local_error_encode(qinfo, env, edns, repinfo, buf, temp,
++ LDNS_RCODE_NOERROR, (LDNS_RCODE_NOERROR|BIT_AA),
++ LDNS_EDE_NONE, NULL);
++ return 1;
++ }
++
+ return 0;
+ } else if(lz_type == local_zone_always_null) {
+ /* 0.0.0.0 or ::0 or noerror/nodata for this zone type,
+@@ -1846,7 +1857,8 @@
+ if(z && (lzt == local_zone_transparent ||
+ lzt == local_zone_typetransparent ||
+ lzt == local_zone_inform ||
+- lzt == local_zone_always_transparent) &&
++ lzt == local_zone_always_transparent ||
++ lzt == local_zone_block_a) &&
+ local_zone_does_not_cover(z, qinfo, labs)) {
+ lock_rw_unlock(&z->lock);
+ z = NULL;
+@@ -1894,6 +1906,7 @@
+
+ if(lzt != local_zone_always_refuse
+ && lzt != local_zone_always_transparent
++ && lzt != local_zone_block_a
+ && lzt != local_zone_always_nxdomain
+ && lzt != local_zone_always_nodata
+ && lzt != local_zone_always_deny
+@@ -1924,6 +1937,7 @@
+ case local_zone_inform_deny: return "inform_deny";
+ case local_zone_inform_redirect: return "inform_redirect";
+ case local_zone_always_transparent: return "always_transparent";
++ case local_zone_block_a: return "block_a";
+ case local_zone_always_refuse: return "always_refuse";
+ case local_zone_always_nxdomain: return "always_nxdomain";
+ case local_zone_always_nodata: return "always_nodata";
+@@ -1958,6 +1972,8 @@
+ *t = local_zone_inform_redirect;
+ else if(strcmp(type, "always_transparent") == 0)
+ *t = local_zone_always_transparent;
++ else if(strcmp(type, "block_a") == 0)
++ *t = local_zone_block_a;
+ else if(strcmp(type, "always_refuse") == 0)
+ *t = local_zone_always_refuse;
+ else if(strcmp(type, "always_nxdomain") == 0)
+--- contrib/unbound/services/localzone.h.orig
++++ contrib/unbound/services/localzone.h
+@@ -88,6 +88,8 @@
+ local_zone_inform_redirect,
+ /** resolve normally, even when there is local data */
+ local_zone_always_transparent,
++ /** resolve normally, even when there is local data but return NODATA for A queries */
++ local_zone_block_a,
+ /** answer with error, even when there is local data */
+ local_zone_always_refuse,
+ /** answer with nxdomain, even when there is local data */
+--- contrib/unbound/services/mesh.c.orig
++++ contrib/unbound/services/mesh.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -63,82 +63,13 @@
+ #include "util/data/dname.h"
+ #include "respip/respip.h"
+ #include "services/listen_dnsport.h"
++#include "util/timeval_func.h"
+
+ #ifdef CLIENT_SUBNET
+ #include "edns-subnet/subnetmod.h"
+ #include "edns-subnet/edns-subnet.h"
+ #endif
+
+-/** subtract timers and the values do not overflow or become negative */
+-static void
+-timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start)
+-{
+-#ifndef S_SPLINT_S
+- time_t end_usec = end->tv_usec;
+- d->tv_sec = end->tv_sec - start->tv_sec;
+- if(end_usec < start->tv_usec) {
+- end_usec += 1000000;
+- d->tv_sec--;
+- }
+- d->tv_usec = end_usec - start->tv_usec;
+-#endif
+-}
+-
+-/** add timers and the values do not overflow or become negative */
+-static void
+-timeval_add(struct timeval* d, const struct timeval* add)
+-{
+-#ifndef S_SPLINT_S
+- d->tv_sec += add->tv_sec;
+- d->tv_usec += add->tv_usec;
+- if(d->tv_usec >= 1000000 ) {
+- d->tv_usec -= 1000000;
+- d->tv_sec++;
+- }
+-#endif
+-}
+-
+-/** divide sum of timers to get average */
+-static void
+-timeval_divide(struct timeval* avg, const struct timeval* sum, size_t d)
+-{
+-#ifndef S_SPLINT_S
+- size_t leftover;
+- if(d <= 0) {
+- avg->tv_sec = 0;
+- avg->tv_usec = 0;
+- return;
+- }
+- avg->tv_sec = sum->tv_sec / d;
+- avg->tv_usec = sum->tv_usec / d;
+- /* handle fraction from seconds divide */
+- leftover = sum->tv_sec - avg->tv_sec*d;
+- if(leftover <= 0)
+- leftover = 0;
+- avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
+- if(avg->tv_sec < 0)
+- avg->tv_sec = 0;
+- if(avg->tv_usec < 0)
+- avg->tv_usec = 0;
+-#endif
+-}
+-
+-/** histogram compare of time values */
+-static int
+-timeval_smaller(const struct timeval* x, const struct timeval* y)
+-{
+-#ifndef S_SPLINT_S
+- if(x->tv_sec < y->tv_sec)
+- return 1;
+- else if(x->tv_sec == y->tv_sec) {
+- if(x->tv_usec <= y->tv_usec)
+- return 1;
+- else return 0;
+- }
+- else return 0;
+-#endif
+-}
+-
+ /**
+ * Compare two response-ip client info entries for the purpose of mesh state
+ * compare. It returns 0 if ci_a and ci_b are considered equal; otherwise
+@@ -249,7 +180,7 @@
+ return mesh_state_compare(a->s, b->s);
+ }
+
+-struct mesh_area*
++struct mesh_area*
+ mesh_create(struct module_stack* stack, struct module_env* env)
+ {
+ struct mesh_area* mesh = calloc(1, sizeof(struct mesh_area));
+@@ -275,6 +206,7 @@
+ mesh->stats_jostled = 0;
+ mesh->stats_dropped = 0;
+ mesh->ans_expired = 0;
++ mesh->ans_cachedb = 0;
+ mesh->max_reply_states = env->cfg->num_queries_per_thread;
+ mesh->max_forever_states = (mesh->max_reply_states+1)/2;
+ #ifndef S_SPLINT_S
+@@ -298,7 +230,7 @@
+ * traversal and rbtree rebalancing do not work together */
+ }
+
+-void
++void
+ mesh_delete(struct mesh_area* mesh)
+ {
+ if(!mesh)
+@@ -341,7 +273,7 @@
+ if(m && m->reply_list && m->list_select == mesh_jostle_list) {
+ /* how old is it? */
+ struct timeval age;
+- timeval_subtract(&age, mesh->env->now_tv,
++ timeval_subtract(&age, mesh->env->now_tv,
+ &m->reply_list->start_time);
+ if(timeval_smaller(&mesh->jostle_max, &age)) {
+ /* its a goner */
+@@ -517,6 +449,8 @@
+ comm_point_send_reply(rep);
+ return;
+ }
++ /* set detached (it is now) */
++ mesh->num_detached_states++;
+ if(unique)
+ mesh_state_make_unique(s);
+ s->s.rpz_passthru = rpz_passthru;
+@@ -525,13 +459,14 @@
+ s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in,
+ s->s.region);
+ if(!s->s.edns_opts_front_in) {
+- log_err("mesh_state_create: out of memory; SERVFAIL");
++ log_err("edns_opt_copy_region: out of memory; SERVFAIL");
+ if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL,
+ NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch, mesh->env->now_tv))
+ edns->opt_list_inplace_cb_out = NULL;
+ error_encode(r_buffer, LDNS_RCODE_SERVFAIL,
+ qinfo, qid, qflags, edns);
+ comm_point_send_reply(rep);
++ mesh_state_delete(&s->s);
+ return;
+ }
+ }
+@@ -543,8 +478,6 @@
+ #endif
+ rbtree_insert(&mesh->all, &s->node);
+ log_assert(n != NULL);
+- /* set detached (it is now) */
+- mesh->num_detached_states++;
+ added = 1;
+ }
+ if(!s->reply_list && !s->cb_list) {
+@@ -585,11 +518,11 @@
+ /* move to either the forever or the jostle_list */
+ if(mesh->num_forever_states < mesh->max_forever_states) {
+ mesh->num_forever_states ++;
+- mesh_list_insert(s, &mesh->forever_first,
++ mesh_list_insert(s, &mesh->forever_first,
+ &mesh->forever_last);
+ s->list_select = mesh_forever_list;
+ } else {
+- mesh_list_insert(s, &mesh->jostle_first,
++ mesh_list_insert(s, &mesh->jostle_first,
+ &mesh->jostle_last);
+ s->list_select = mesh_jostle_list;
+ }
+@@ -610,9 +543,9 @@
+ return;
+ }
+
+-int
++int
+ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo,
+- uint16_t qflags, struct edns_data* edns, sldns_buffer* buf,
++ uint16_t qflags, struct edns_data* edns, sldns_buffer* buf,
+ uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru)
+ {
+ struct mesh_state* s = NULL;
+@@ -637,6 +570,8 @@
+ if(!s) {
+ return 0;
+ }
++ /* set detached (it is now) */
++ mesh->num_detached_states++;
+ if(unique)
+ mesh_state_make_unique(s);
+ s->s.rpz_passthru = rpz_passthru;
+@@ -644,6 +579,7 @@
+ s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in,
+ s->s.region);
+ if(!s->s.edns_opts_front_in) {
++ mesh_state_delete(&s->s);
+ return 0;
+ }
+ }
+@@ -654,8 +590,6 @@
+ #endif
+ rbtree_insert(&mesh->all, &s->node);
+ log_assert(n != NULL);
+- /* set detached (it is now) */
+- mesh->num_detached_states++;
+ added = 1;
+ }
+ if(!s->reply_list && !s->cb_list) {
+@@ -672,6 +606,8 @@
+ }
+ /* add serve expired timer if not already there */
+ if(timeout && !mesh_serve_expired_init(s, timeout)) {
++ if(added)
++ mesh_state_delete(&s->s);
+ return 0;
+ }
+ /* update statistics */
+@@ -773,7 +709,7 @@
+ * attached its own ECS data. */
+ static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh,
+ struct query_info* qinfo, uint16_t qflags, time_t leeway, int run,
+- int rpz_passthru, struct comm_reply* rep, struct edns_option* edns_list)
++ int rpz_passthru, struct sockaddr_storage* addr, struct edns_option* edns_list)
+ {
+ struct mesh_state* s = NULL;
+ struct edns_option* opt = NULL;
+@@ -803,20 +739,10 @@
+ return;
+ }
+ } else {
+- /* Fake the ECS data from the client's IP */
+- struct ecs_data ecs;
+- memset(&ecs, 0, sizeof(ecs));
+- subnet_option_from_ss(&rep->client_addr, &ecs, mesh->env->cfg);
+- if(ecs.subnet_validdata == 0) {
+- log_err("prefetch_subnet subnet_option_from_ss: invalid data");
+- return;
+- }
+- subnet_ecs_opt_list_append(&ecs, &s->s.edns_opts_front_in,
+- &s->s, s->s.region);
+- if(!s->s.edns_opts_front_in) {
+- log_err("prefetch_subnet subnet_ecs_opt_list_append: out of memory");
+- return;
+- }
++ /* Store the client's address. Later in the subnet module,
++ * it is decided whether to include an ECS option or not.
++ */
++ s->s.client_addr = *addr;
+ }
+ #ifdef UNBOUND_DEBUG
+ n =
+@@ -863,14 +789,14 @@
+
+ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
+ uint16_t qflags, time_t leeway, int rpz_passthru,
+- struct comm_reply* rep, struct edns_option* opt_list)
++ struct sockaddr_storage* addr, struct edns_option* opt_list)
+ {
++ (void)addr;
+ (void)opt_list;
+- (void)rep;
+ #ifdef CLIENT_SUBNET
+- if(rep)
++ if(addr)
+ mesh_schedule_prefetch_subnet(mesh, qinfo, qflags, leeway, 1,
+- rpz_passthru, rep, opt_list);
++ rpz_passthru, addr, opt_list);
+ else
+ #endif
+ mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1,
+@@ -900,7 +826,7 @@
+ int i;
+ if(!region)
+ return NULL;
+- mstate = (struct mesh_state*)regional_alloc(region,
++ mstate = (struct mesh_state*)regional_alloc(region,
+ sizeof(struct mesh_state));
+ if(!mstate) {
+ alloc_reg_release(env->alloc, region);
+@@ -970,19 +896,13 @@
+ return mstate;
+ }
+
+-int
+-mesh_state_is_unique(struct mesh_state* mstate)
+-{
+- return mstate->unique != NULL;
+-}
+-
+ void
+ mesh_state_make_unique(struct mesh_state* mstate)
+ {
+ mstate->unique = mstate;
+ }
+
+-void
++void
+ mesh_state_cleanup(struct mesh_state* mstate)
+ {
+ struct mesh_area* mesh;
+@@ -1028,7 +948,7 @@
+ alloc_reg_release(mstate->s.env->alloc, mstate->s.region);
+ }
+
+-void
++void
+ mesh_state_delete(struct module_qstate* qstate)
+ {
+ struct mesh_area* mesh;
+@@ -1041,10 +961,10 @@
+ mesh_detach_subs(&mstate->s);
+ if(mstate->list_select == mesh_forever_list) {
+ mesh->num_forever_states --;
+- mesh_list_remove(mstate, &mesh->forever_first,
++ mesh_list_remove(mstate, &mesh->forever_first,
+ &mesh->forever_last);
+ } else if(mstate->list_select == mesh_jostle_list) {
+- mesh_list_remove(mstate, &mesh->jostle_first,
++ mesh_list_remove(mstate, &mesh->jostle_first,
+ &mesh->jostle_last);
+ }
+ if(!mstate->reply_list && !mstate->cb_list
+@@ -1116,7 +1036,7 @@
+ if(!ref->s->reply_list && !ref->s->cb_list
+ && ref->s->super_set.count == 0) {
+ mesh->num_detached_states++;
+- log_assert(mesh->num_detached_states +
++ log_assert(mesh->num_detached_states +
+ mesh->num_reply_states <= mesh->all.count);
+ }
+ }
+@@ -1181,7 +1101,7 @@
+ if(!mesh_state_attachment(qstate->mesh_info, sub))
+ return 0;
+ /* if it was a duplicate attachment, the count was not zero before */
+- if(!sub->reply_list && !sub->cb_list && was_detached &&
++ if(!sub->reply_list && !sub->cb_list && was_detached &&
+ sub->super_set.count == 1) {
+ /* it used to be detached, before this one got added */
+ log_assert(mesh->num_detached_states > 0);
+@@ -1251,7 +1171,7 @@
+ else secure = 0;
+ if(!rep && rcode == LDNS_RCODE_NOERROR)
+ rcode = LDNS_RCODE_SERVFAIL;
+- if(!rcode && (rep->security == sec_status_bogus ||
++ if(!rcode && rep && (rep->security == sec_status_bogus ||
+ rep->security == sec_status_secure_sentinel_fail)) {
+ if(!(reason = errinf_to_str_bogus(&m->s)))
+ rcode = LDNS_RCODE_SERVFAIL;
+@@ -1277,13 +1197,15 @@
+ r->edns.udp_size = EDNS_ADVERTISED_SIZE;
+ r->edns.ext_rcode = 0;
+ r->edns.bits &= EDNS_DO;
++ if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO))
++ r->edns.edns_present = 0;
+
+ if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep,
+ LDNS_RCODE_NOERROR, &r->edns, NULL, m->s.region, start_time) ||
+- !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
+- r->qflags, r->buf, 0, 1,
+- m->s.env->scratch, udp_size, &r->edns,
+- (int)(r->edns.bits & EDNS_DO), secure))
++ !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
++ r->qflags, r->buf, 0, 1,
++ m->s.env->scratch, udp_size, &r->edns,
++ (int)(r->edns.bits & EDNS_DO), secure))
+ {
+ fptr_ok(fptr_whitelist_mesh_cb(r->cb));
+ (*r->cb)(r->cb_arg, LDNS_RCODE_SERVFAIL, r->buf,
+@@ -1291,7 +1213,8 @@
+ } else {
+ fptr_ok(fptr_whitelist_mesh_cb(r->cb));
+ (*r->cb)(r->cb_arg, LDNS_RCODE_NOERROR, r->buf,
+- rep->security, reason, was_ratelimited);
++ (rep?rep->security:sec_status_unchecked),
++ reason, was_ratelimited);
+ }
+ }
+ free(reason);
+@@ -1303,18 +1226,45 @@
+ mesh_is_rpz_respip_tcponly_action(struct mesh_state const* m)
+ {
+ struct respip_action_info const* respip_info = m->s.respip_action_info;
+- return respip_info == NULL
++ return (respip_info == NULL
+ ? 0
+ : (respip_info->rpz_used
+ && !respip_info->rpz_disabled
+- && respip_info->action == respip_truncate);
++ && respip_info->action == respip_truncate))
++ || m->s.tcp_required;
+ }
+
+ static inline int
+-mesh_is_udp(struct mesh_reply const* r) {
++mesh_is_udp(struct mesh_reply const* r)
++{
+ return r->query_reply.c->type == comm_udp;
+ }
+
++static inline void
++mesh_find_and_attach_ede_and_reason(struct mesh_state* m,
++ struct reply_info* rep, struct mesh_reply* r)
++{
++ /* OLD note:
++ * During validation the EDE code can be received via two
++ * code paths. One code path fills the reply_info EDE, and
++ * the other fills it in the errinf_strlist. These paths
++ * intersect at some points, but where is opaque due to
++ * the complexity of the validator. At the time of writing
++ * we make the choice to prefer the EDE from errinf_strlist
++ * but a compelling reason to do otherwise is just as valid
++ * NEW note:
++ * The compelling reason is that with caching support, the value
++ * in the reply_info is cached.
++ * The reason members of the reply_info struct should be
++ * updated as they are already cached. No reason to
++ * try and find the EDE information in errinf anymore.
++ */
++ if(rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&r->edns.opt_list_out,
++ m->s.region, rep->reason_bogus, rep->reason_bogus_str);
++ }
++}
++
+ /**
+ * Send reply to mesh reply entry
+ * @param m: mesh state to send it for.
+@@ -1346,7 +1296,7 @@
+
+ /* examine security status */
+ if(m->s.env->need_to_validate && (!(r->qflags&BIT_CD) ||
+- m->s.env->cfg->ignore_cd) && rep &&
++ m->s.env->cfg->ignore_cd) && rep &&
+ (rep->security <= sec_status_bogus ||
+ rep->security == sec_status_secure_sentinel_fail)) {
+ rcode = LDNS_RCODE_SERVFAIL;
+@@ -1401,40 +1351,17 @@
+ if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s,
+ rep, rcode, &r->edns, &r->query_reply, m->s.region, &r->start_time))
+ r->edns.opt_list_inplace_cb_out = NULL;
+- } else {
++ } else {
+ if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode,
+ &r->edns, &r->query_reply, m->s.region, &r->start_time))
+ r->edns.opt_list_inplace_cb_out = NULL;
+ }
+- /* Send along EDE BOGUS EDNS0 option when answer is bogus */
+- if(m->s.env->cfg->ede && rcode == LDNS_RCODE_SERVFAIL &&
+- m->s.env->need_to_validate && (!(r->qflags&BIT_CD) ||
+- m->s.env->cfg->ignore_cd) && rep &&
+- (rep->security <= sec_status_bogus ||
+- rep->security == sec_status_secure_sentinel_fail)) {
+- char *reason = m->s.env->cfg->val_log_level >= 2
+- ? errinf_to_str_bogus(&m->s) : NULL;
+-
+- /* During validation the EDE code can be received via two
+- * code paths. One code path fills the reply_info EDE, and
+- * the other fills it in the errinf_strlist. These paths
+- * intersect at some points, but where is opaque due to
+- * the complexity of the validator. At the time of writing
+- * we make the choice to prefer the EDE from errinf_strlist
+- * but a compelling reason to do otherwise is just as valid
+- */
+- sldns_ede_code reason_bogus = errinf_to_reason_bogus(&m->s);
+- if ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS &&
+- rep->reason_bogus != LDNS_EDE_NONE) ||
+- reason_bogus == LDNS_EDE_NONE) {
+- reason_bogus = rep->reason_bogus;
+- }
+-
+- if(reason_bogus != LDNS_EDE_NONE) {
+- edns_opt_list_append_ede(&r->edns.opt_list_out,
+- m->s.region, reason_bogus, reason);
+- }
+- free(reason);
++ /* Send along EDE EDNS0 option when SERVFAILing; usually
++ * DNSSEC validation failures */
++ /* Since we are SERVFAILing here, CD bit and rep->security
++ * is already handled. */
++ if(m->s.env->cfg->ede && rep) {
++ mesh_find_and_attach_ede_and_reason(m, rep, r);
+ }
+ error_encode(r_buffer, rcode, &m->s.qinfo, r->qid,
+ r->qflags, &r->edns);
+@@ -1447,14 +1374,26 @@
+ r->edns.udp_size = EDNS_ADVERTISED_SIZE;
+ r->edns.ext_rcode = 0;
+ r->edns.bits &= EDNS_DO;
++ if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO))
++ r->edns.edns_present = 0;
+ m->s.qinfo.qname = r->qname;
+ m->s.qinfo.local_alias = r->local_alias;
++
++ /* Attach EDE without SERVFAIL if the validation failed.
++ * Need to explicitly check for rep->security otherwise failed
++ * validation paths may attach to a secure answer. */
++ if(m->s.env->cfg->ede && rep &&
++ (rep->security <= sec_status_bogus ||
++ rep->security == sec_status_secure_sentinel_fail)) {
++ mesh_find_and_attach_ede_and_reason(m, rep, r);
++ }
++
+ if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep,
+ LDNS_RCODE_NOERROR, &r->edns, &r->query_reply, m->s.region, &r->start_time) ||
+- !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
++ !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
+ r->qflags, r_buffer, 0, 1, m->s.env->scratch,
+ udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO),
+- secure))
++ secure))
+ {
+ if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s,
+ rep, LDNS_RCODE_SERVFAIL, &r->edns, &r->query_reply, m->s.region, &r->start_time))
+@@ -1503,6 +1442,7 @@
+ struct reply_info* rep = (mstate->s.return_msg?
+ mstate->s.return_msg->rep:NULL);
+ struct timeval tv = {0, 0};
++ int i = 0;
+ /* No need for the serve expired timer anymore; we are going to reply. */
+ if(mstate->s.serve_expired_data) {
+ comm_timer_delete(mstate->s.serve_expired_data->timer);
+@@ -1522,6 +1462,7 @@
+ }
+ }
+ for(r = mstate->reply_list; r; r = r->next) {
++ i++;
+ tv = r->start_time;
+
+ /* if a response-ip address block has been stored the
+@@ -1533,16 +1474,6 @@
+ mstate->s.qinfo.qclass, r->local_alias,
+ &r->query_reply.client_addr,
+ r->query_reply.client_addrlen);
+- if(mstate->s.env->cfg->stat_extended &&
+- mstate->s.respip_action_info->rpz_used) {
+- if(mstate->s.respip_action_info->rpz_disabled)
+- mstate->s.env->mesh->rpz_action[RPZ_DISABLED_ACTION]++;
+- if(mstate->s.respip_action_info->rpz_cname_override)
+- mstate->s.env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++;
+- else
+- mstate->s.env->mesh->rpz_action[respip_action_to_rpz_action(
+- mstate->s.respip_action_info->action)]++;
+- }
+ }
+
+ /* if this query is determined to be dropped during the
+@@ -1573,6 +1504,27 @@
+ prev_buffer = r_buffer;
+ }
+ }
++ /* Account for each reply sent. */
++ if(i > 0 && mstate->s.respip_action_info &&
++ mstate->s.respip_action_info->addrinfo &&
++ mstate->s.env->cfg->stat_extended &&
++ mstate->s.respip_action_info->rpz_used) {
++ if(mstate->s.respip_action_info->rpz_disabled)
++ mstate->s.env->mesh->rpz_action[RPZ_DISABLED_ACTION] += i;
++ if(mstate->s.respip_action_info->rpz_cname_override)
++ mstate->s.env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION] += i;
++ else
++ mstate->s.env->mesh->rpz_action[respip_action_to_rpz_action(
++ mstate->s.respip_action_info->action)] += i;
++ }
++ if(!mstate->s.is_drop && i > 0) {
++ if(mstate->s.env->cfg->stat_extended
++ && mstate->s.is_cachedb_answer) {
++ mstate->s.env->mesh->ans_cachedb += i;
++ }
++ }
++
++ /* Mesh area accounting */
+ if(mstate->reply_list) {
+ mstate->reply_list = NULL;
+ if(!mstate->reply_list && !mstate->cb_list) {
+@@ -1585,6 +1537,7 @@
+ mstate->s.env->mesh->num_detached_states++;
+ }
+ mstate->replies_sent = 1;
++
+ while((c = mstate->cb_list) != NULL) {
+ /* take this cb off the list; so that the list can be
+ * changed, eg. by adds from the callback routine */
+@@ -1611,7 +1564,7 @@
+ /* callback the function to inform super of result */
+ fptr_ok(fptr_whitelist_mod_inform_super(
+ mesh->mods.mod[ref->s->s.curmod]->inform_super));
+- (*mesh->mods.mod[ref->s->s.curmod]->inform_super)(&mstate->s,
++ (*mesh->mods.mod[ref->s->s.curmod]->inform_super)(&mstate->s,
+ ref->s->s.curmod, &ref->s->s);
+ /* copy state that is always relevant to super */
+ copy_state_to_super(&mstate->s, ref->s->s.curmod, &ref->s->s);
+@@ -1635,7 +1588,7 @@
+ * desire aggregation).*/
+ key.unique = NULL;
+ key.s.client_info = cinfo;
+-
++
+ result = (struct mesh_state*)rbtree_search(&mesh->all, &key);
+ return result;
+ }
+@@ -1644,7 +1597,7 @@
+ sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg,
+ uint16_t qid, uint16_t qflags)
+ {
+- struct mesh_cb* r = regional_alloc(s->s.region,
++ struct mesh_cb* r = regional_alloc(s->s.region,
+ sizeof(struct mesh_cb));
+ if(!r)
+ return 0;
+@@ -1776,7 +1729,7 @@
+ * Handles module finished.
+ * @param mesh: the mesh area.
+ * @param mstate: currently active mesh state.
+- * Deleted if finished, calls _done and _supers to
++ * Deleted if finished, calls _done and _supers to
+ * send replies to clients and inform other mesh states.
+ * This in turn may create additional runnable mesh states.
+ * @param s: state at which the current module exited.
+@@ -1810,7 +1763,7 @@
+ }
+ if(s == module_restart_next) {
+ int curmod = mstate->s.curmod;
+- for(; mstate->s.curmod < mesh->mods.num;
++ for(; mstate->s.curmod < mesh->mods.num;
+ mstate->s.curmod++) {
+ fptr_ok(fptr_whitelist_mod_clear(
+ mesh->mods.mod[mstate->s.curmod]->clear));
+@@ -1842,9 +1795,21 @@
+ if(s == module_finished) {
+ if(mstate->s.curmod == 0) {
+ struct query_info* qinfo = NULL;
++ struct edns_option* opt_list = NULL;
++ struct sockaddr_storage addr;
+ uint16_t qflags;
+ int rpz_p = 0;
+
++#ifdef CLIENT_SUBNET
++ struct edns_option* ecs;
++ if(mstate->s.need_refetch && mstate->reply_list &&
++ modstack_find(&mesh->mods, "subnetcache") != -1 &&
++ mstate->s.env->unique_mesh) {
++ addr = mstate->reply_list->query_reply.client_addr;
++ } else
++#endif
++ memset(&addr, 0, sizeof(addr));
++
+ mesh_query_done(mstate);
+ mesh_walk_supers(mesh, mstate);
+
+@@ -1854,13 +1819,28 @@
+ * we need to make a copy of the query info here. */
+ if(mstate->s.need_refetch) {
+ mesh_copy_qinfo(mstate, &qinfo, &qflags);
++#ifdef CLIENT_SUBNET
++ /* Make also a copy of the ecs option if any */
++ if((ecs = edns_opt_list_find(
++ mstate->s.edns_opts_front_in,
++ mstate->s.env->cfg->client_subnet_opcode)) != NULL) {
++ (void)edns_opt_list_append(&opt_list,
++ ecs->opt_code, ecs->opt_len,
++ ecs->opt_data,
++ mstate->s.env->scratch);
++ }
++#endif
+ rpz_p = mstate->s.rpz_passthru;
+ }
+
+- mesh_state_delete(&mstate->s);
+ if(qinfo) {
+- mesh_schedule_prefetch(mesh, qinfo, qflags,
+- 0, 1, rpz_p);
++ mesh_state_delete(&mstate->s);
++ mesh_new_prefetch(mesh, qinfo, qflags, 0,
++ rpz_p,
++ addr.ss_family!=AF_UNSPEC?&addr:NULL,
++ opt_list);
++ } else {
++ mesh_state_delete(&mstate->s);
+ }
+ return 0;
+ }
+@@ -1888,7 +1868,7 @@
+ mstate->s.reply = NULL;
+ regional_free_all(mstate->s.env->scratch);
+ s = mstate->s.ext_state[mstate->s.curmod];
+- verbose(VERB_ALGO, "mesh_run: %s module exit state is %s",
++ verbose(VERB_ALGO, "mesh_run: %s module exit state is %s",
+ mesh->mods.mod[mstate->s.curmod]->name, strextstate(s));
+ e = NULL;
+ if(mesh_continue(mesh, mstate, s, &ev))
+@@ -1908,14 +1888,14 @@
+ }
+ }
+
+-void
++void
+ mesh_log_list(struct mesh_area* mesh)
+ {
+ char buf[30];
+ struct mesh_state* m;
+ int num = 0;
+ RBTREE_FOR(m, struct mesh_state*, &mesh->all) {
+- snprintf(buf, sizeof(buf), "%d%s%s%s%s%s%s mod%d %s%s",
++ snprintf(buf, sizeof(buf), "%d%s%s%s%s%s%s mod%d %s%s",
+ num++, (m->s.is_priming)?"p":"", /* prime */
+ (m->s.is_valrec)?"v":"", /* prime */
+ (m->s.query_flags&BIT_RD)?"RD":"",
+@@ -1924,18 +1904,18 @@
+ (m->sub_set.count!=0)?"c":"", /* children */
+ m->s.curmod, (m->reply_list)?"rep":"", /*hasreply*/
+ (m->cb_list)?"cb":"" /* callbacks */
+- );
++ );
+ log_query_info(VERB_ALGO, buf, &m->s.qinfo);
+ }
+ }
+
+-void
++void
+ mesh_stats(struct mesh_area* mesh, const char* str)
+ {
+ verbose(VERB_DETAIL, "%s %u recursion states (%u with reply, "
+ "%u detached), %u waiting replies, %u recursion replies "
+- "sent, %d replies dropped, %d states jostled out",
+- str, (unsigned)mesh->all.count,
++ "sent, %d replies dropped, %d states jostled out",
++ str, (unsigned)mesh->all.count,
+ (unsigned)mesh->num_reply_states,
+ (unsigned)mesh->num_detached_states,
+ (unsigned)mesh->num_reply_addrs,
+@@ -1944,7 +1924,7 @@
+ (unsigned)mesh->stats_jostled);
+ if(mesh->replies_sent > 0) {
+ struct timeval avg;
+- timeval_divide(&avg, &mesh->replies_sum_wait,
++ timeval_divide(&avg, &mesh->replies_sum_wait,
+ mesh->replies_sent);
+ log_info("average recursion processing time "
+ ARG_LL "d.%6.6d sec",
+@@ -1954,7 +1934,7 @@
+ }
+ }
+
+-void
++void
+ mesh_stats_clear(struct mesh_area* mesh)
+ {
+ if(!mesh)
+@@ -1968,12 +1948,13 @@
+ mesh->ans_secure = 0;
+ mesh->ans_bogus = 0;
+ mesh->ans_expired = 0;
++ mesh->ans_cachedb = 0;
+ memset(&mesh->ans_rcode[0], 0, sizeof(size_t)*UB_STATS_RCODE_NUM);
+ memset(&mesh->rpz_action[0], 0, sizeof(size_t)*UB_STATS_RPZ_ACTION_NUM);
+ mesh->ans_nodata = 0;
+ }
+
+-size_t
++size_t
+ mesh_get_mem(struct mesh_area* mesh)
+ {
+ struct mesh_state* m;
+@@ -1987,7 +1968,7 @@
+ return s;
+ }
+
+-int
++int
+ mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo,
+ uint16_t flags, int prime, int valrec)
+ {
+@@ -2104,6 +2085,7 @@
+ struct timeval tv = {0, 0};
+ int must_validate = (!(qstate->query_flags&BIT_CD)
+ || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate;
++ int i = 0;
+ if(!qstate->serve_expired_data) return;
+ verbose(VERB_ALGO, "Serve expired: Trying to reply with expired data");
+ comm_timer_delete(qstate->serve_expired_data->timer);
+@@ -2175,6 +2157,7 @@
+ log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep);
+
+ for(r = mstate->reply_list; r; r = r->next) {
++ i++;
+ tv = r->start_time;
+
+ /* If address info is returned, it means the action should be an
+@@ -2184,16 +2167,6 @@
+ qstate->qinfo.qtype, qstate->qinfo.qclass,
+ r->local_alias, &r->query_reply.client_addr,
+ r->query_reply.client_addrlen);
+-
+- if(qstate->env->cfg->stat_extended && actinfo.rpz_used) {
+- if(actinfo.rpz_disabled)
+- qstate->env->mesh->rpz_action[RPZ_DISABLED_ACTION]++;
+- if(actinfo.rpz_cname_override)
+- qstate->env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++;
+- else
+- qstate->env->mesh->rpz_action[
+- respip_action_to_rpz_action(actinfo.action)]++;
+- }
+ }
+
+ /* Add EDE Stale Answer (RCF8914). Ignore global ede as this is
+@@ -2213,11 +2186,23 @@
+ tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate);
+ prev = r;
+ prev_buffer = r_buffer;
+-
+- /* Account for each reply sent. */
+- mesh->ans_expired++;
+-
+ }
++ /* Account for each reply sent. */
++ if(i > 0) {
++ mesh->ans_expired += i;
++ if(actinfo.addrinfo && qstate->env->cfg->stat_extended &&
++ actinfo.rpz_used) {
++ if(actinfo.rpz_disabled)
++ qstate->env->mesh->rpz_action[RPZ_DISABLED_ACTION] += i;
++ if(actinfo.rpz_cname_override)
++ qstate->env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION] += i;
++ else
++ qstate->env->mesh->rpz_action[
++ respip_action_to_rpz_action(actinfo.action)] += i;
++ }
++ }
++
++ /* Mesh area accounting */
+ if(mstate->reply_list) {
+ mstate->reply_list = NULL;
+ if(!mstate->reply_list && !mstate->cb_list) {
+@@ -2228,6 +2213,7 @@
+ }
+ }
+ }
++
+ while((c = mstate->cb_list) != NULL) {
+ /* take this cb off the list; so that the list can be
+ * changed, eg. by adds from the callback routine */
+--- contrib/unbound/services/mesh.h.orig
++++ contrib/unbound/services/mesh.h
+@@ -114,6 +114,8 @@
+ size_t stats_dropped;
+ /** stats, number of expired replies sent */
+ size_t ans_expired;
++ /** stats, number of cached replies from cachedb */
++ size_t ans_cachedb;
+ /** number of replies sent */
+ size_t replies_sent;
+ /** sum of waiting times for the replies */
+@@ -335,13 +337,13 @@
+ * @param leeway: TTL leeway what to expire earlier for this update.
+ * @param rpz_passthru: if true, the rpz passthru was previously found and
+ * further rpz processing is stopped.
+- * @param rep: comm_reply for the client; to be used when subnet is enabled.
++ * @param addr: sockaddr_storage for the client; to be used with subnet.
+ * @param opt_list: edns opt_list from the client; to be used when subnet is
+ * enabled.
+ */
+ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
+ uint16_t qflags, time_t leeway, int rpz_passthru,
+- struct comm_reply* rep, struct edns_option* opt_list);
++ struct sockaddr_storage* addr, struct edns_option* opt_list);
+
+ /**
+ * Handle new event from the wire. A serviced query has returned.
+@@ -478,14 +480,6 @@
+ struct query_info* qinfo, struct respip_client_info* cinfo,
+ uint16_t qflags, int prime, int valrec);
+
+-/**
+- * Check if the mesh state is unique.
+- * A unique mesh state uses it's unique member to point to itself, else NULL.
+- * @param mstate: mesh state to check.
+- * @return true if the mesh state is unique, false otherwise.
+- */
+-int mesh_state_is_unique(struct mesh_state* mstate);
+-
+ /**
+ * Make a mesh state unique.
+ * A unique mesh state uses it's unique member to point to itself.
+--- contrib/unbound/services/modstack.c.orig
++++ contrib/unbound/services/modstack.c
+@@ -120,12 +120,16 @@
+ stack->mod[i] = module_factory(&module_conf);
+ if(!stack->mod[i]) {
+ char md[256];
++ char * s = md;
+ snprintf(md, sizeof(md), "%s", module_conf);
+- if(strchr(md, ' ')) *(strchr(md, ' ')) = 0;
+- if(strchr(md, '\t')) *(strchr(md, '\t')) = 0;
++ /* Leading spaces are present on errors. */
++ while (*s && isspace((unsigned char)*s))
++ s++;
++ if(strchr(s, ' ')) *(strchr(s, ' ')) = 0;
++ if(strchr(s, '\t')) *(strchr(s, '\t')) = 0;
+ log_err("Unknown value in module-config, module: '%s'."
+ " This module is not present (not compiled in),"
+- " See the list of linked modules with unbound -V", md);
++ " See the list of linked modules with unbound -V", s);
+ return 0;
+ }
+ }
+--- contrib/unbound/services/outside_network.c.orig
++++ contrib/unbound/services/outside_network.c
+@@ -550,9 +550,28 @@
+ log_assert(&key_p.reuse != (struct reuse_tcp*)result);
+ log_assert(&key_p != ((struct reuse_tcp*)result)->pending);
+ }
++
++ /* It is possible that we search for something before the first element
++ * in the tree. Replace a null pointer with the first element.
++ */
++ if (!result) {
++ verbose(VERB_CLIENT, "reuse_tcp_find: taking first");
++ result = rbtree_first(&outnet->tcp_reuse);
++ }
++
+ /* not found, return null */
+ if(!result || result == RBTREE_NULL)
+ return NULL;
++
++ /* It is possible that we got the previous address, but that the
++ * address we are looking for is in the tree. If the address we got
++ * is less than the address we are looking, then take the next entry.
++ */
++ if (reuse_cmp_addrportssl(result->key, &key_p.reuse) < 0) {
++ verbose(VERB_CLIENT, "reuse_tcp_find: key too low");
++ result = rbtree_next(result);
++ }
++
+ verbose(VERB_CLIENT, "reuse_tcp_find check inexact match");
+ /* inexact match, find one of possibly several connections to the
+ * same destination address, with the correct port, ssl, and
+@@ -620,6 +639,15 @@
+ log_assert(w->addrlen > 0);
+ pend->c->tcp_do_toggle_rw = 0;
+ pend->c->tcp_do_close = 0;
++
++ /* Consistency check, if we have ssl_upstream but no sslctx, then
++ * log an error and return failure.
++ */
++ if (w->ssl_upstream && !w->outnet->sslctx) {
++ log_err("SSL upstream requested but no SSL context");
++ return 0;
++ }
++
+ /* open socket */
+ s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss, w->outnet->ip_dscp);
+
+--- contrib/unbound/services/rpz.c.orig
++++ contrib/unbound/services/rpz.c
+@@ -1188,6 +1188,22 @@
+ return z;
+ }
+
++/** Find entry for RR type in the list of rrsets for the clientip. */
++static struct local_rrset*
++rpz_find_synthesized_rrset(uint16_t qtype,
++ struct clientip_synthesized_rr* data)
++{
++ struct local_rrset* cursor = data->data;
++ while( cursor != NULL) {
++ struct packed_rrset_key* packed_rrset = &cursor->rrset->rk;
++ if(htons(qtype) == packed_rrset->type) {
++ return cursor;
++ }
++ cursor = cursor->next;
++ }
++ return NULL;
++}
++
+ /**
+ * Remove RR from RPZ's local-data
+ * @param z: local-zone for RPZ, holding write lock
+@@ -1270,15 +1286,15 @@
+
+ }
+
+-/** Remove RR from RPZ's local-zone */
++/** Remove RR from rpz localzones structure */
+ static void
+-rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+- enum rpz_action a, uint16_t rr_type, uint16_t rr_class,
+- uint8_t* rdatawl, size_t rdatalen)
++rpz_remove_local_zones_trigger(struct local_zones* zones, uint8_t* dname,
++ size_t dnamelen, enum rpz_action a, uint16_t rr_type,
++ uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen)
+ {
+ struct local_zone* z;
+ int delete_zone = 1;
+- z = rpz_find_zone(r->local_zones, dname, dnamelen, rr_class,
++ z = rpz_find_zone(zones, dname, dnamelen, rr_class,
+ 1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/);
+ if(!z) {
+ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, "
+@@ -1290,15 +1306,24 @@
+ dnamelen, rr_type, rdatawl, rdatalen);
+ else if(a != localzone_type_to_rpz_action(z->type)) {
+ lock_rw_unlock(&z->lock);
+- lock_rw_unlock(&r->local_zones->lock);
++ lock_rw_unlock(&zones->lock);
+ return;
+ }
+ lock_rw_unlock(&z->lock);
+ if(delete_zone) {
+- local_zones_del_zone(r->local_zones, z);
++ local_zones_del_zone(zones, z);
+ }
+- lock_rw_unlock(&r->local_zones->lock);
+- return;
++ lock_rw_unlock(&zones->lock);
++}
++
++/** Remove RR from RPZ's local-zone */
++static void
++rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint16_t rr_class,
++ uint8_t* rdatawl, size_t rdatalen)
++{
++ rpz_remove_local_zones_trigger(r->local_zones, dname, dnamelen,
++ a, rr_type, rr_class, rdatawl, rdatalen);
+ }
+
+ static void
+@@ -1335,15 +1360,159 @@
+ lock_rw_unlock(&r->respip_set->lock);
+ }
+
++/** find and remove type from list of local_rrset entries*/
++static void
++del_local_rrset_from_list(struct local_rrset** list_head, uint16_t dtype)
++{
++ struct local_rrset* prev=NULL, *p=*list_head;
++ while(p && ntohs(p->rrset->rk.type) != dtype) {
++ prev = p;
++ p = p->next;
++ }
++ if(!p)
++ return; /* rrset type not found */
++ /* unlink it */
++ if(prev) prev->next = p->next;
++ else *list_head = p->next;
++ /* no memory recycling for zone deletions ... */
++}
++
++/** Delete client-ip trigger RR from its RRset and perhaps also the rrset
++ * from the linked list. Returns if the local data is empty and the node can
++ * be deleted too, or not. */
++static int rpz_remove_clientip_rr(struct clientip_synthesized_rr* node,
++ uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct local_rrset* rrset;
++ struct packed_rrset_data* d;
++ size_t index;
++ rrset = rpz_find_synthesized_rrset(rr_type, node);
++ if(rrset == NULL)
++ return 0; /* type not found, ignore */
++ d = (struct packed_rrset_data*)rrset->rrset->entry.data;
++ if(!packed_rrset_find_rr(d, rdatawl, rdatalen, &index))
++ return 0; /* RR not found, ignore */
++ if(d->count == 1) {
++ /* regional alloc'd */
++ /* delete the type entry from the list */
++ del_local_rrset_from_list(&node->data, rr_type);
++ /* if the list is empty, the node can be removed too */
++ if(node->data == NULL)
++ return 1;
++ } else if (d->count > 1) {
++ if(!local_rrset_remove_rr(d, index))
++ return 0;
++ }
++ return 0;
++}
++
++/** remove trigger RR from clientip_syntheized set tree. */
++static void
++rpz_clientip_remove_trigger_rr(struct clientip_synthesized_rrset* set,
++ struct sockaddr_storage* addr, socklen_t addrlen, int net,
++ enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct clientip_synthesized_rr* node;
++ int delete_node = 1;
++
++ lock_rw_wrlock(&set->lock);
++ node = (struct clientip_synthesized_rr*)addr_tree_find(&set->entries,
++ addr, addrlen, net);
++ if(node == NULL) {
++ /* netblock not found */
++ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, "
++ "RPZ address, netblock not found");
++ lock_rw_unlock(&set->lock);
++ return;
++ }
++ lock_rw_wrlock(&node->lock);
++ if(a == RPZ_LOCAL_DATA_ACTION) {
++ /* remove RR, signal whether entry can be removed */
++ delete_node = rpz_remove_clientip_rr(node, rr_type, rdatawl,
++ rdatalen);
++ } else if(a != node->action) {
++ /* ignore the RR with different action specification */
++ delete_node = 0;
++ }
++ if(delete_node) {
++ rbtree_delete(&set->entries, node->node.node.key);
++ }
++ lock_rw_unlock(&set->lock);
++ lock_rw_unlock(&node->lock);
++ if(delete_node) {
++ lock_rw_destroy(&node->lock);
++ }
++}
++
++/** Remove clientip trigger RR from RPZ. */
++static void
++rpz_remove_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct sockaddr_storage addr;
++ socklen_t addrlen;
++ int net, af;
++ if(a == RPZ_INVALID_ACTION)
++ return;
++ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af))
++ return;
++ rpz_clientip_remove_trigger_rr(r->client_set, &addr, addrlen, net,
++ a, rr_type, rdatawl, rdatalen);
++}
++
++/** Remove nsip trigger RR from RPZ. */
++static void
++rpz_remove_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct sockaddr_storage addr;
++ socklen_t addrlen;
++ int net, af;
++ if(a == RPZ_INVALID_ACTION)
++ return;
++ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af))
++ return;
++ rpz_clientip_remove_trigger_rr(r->ns_set, &addr, addrlen, net,
++ a, rr_type, rdatawl, rdatalen);
++}
++
++/** Remove nsdname trigger RR from RPZ. */
++static void
++rpz_remove_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint16_t rr_class,
++ uint8_t* rdatawl, size_t rdatalen)
++{
++ uint8_t* dname_stripped = NULL;
++ size_t dnamelen_stripped = 0;
++ if(a == RPZ_INVALID_ACTION)
++ return;
++ if(!rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped,
++ &dnamelen_stripped))
++ return;
++ rpz_remove_local_zones_trigger(r->nsdname_zones, dname_stripped,
++ dnamelen_stripped, a, rr_type, rr_class, rdatawl, rdatalen);
++ free(dname_stripped);
++}
++
+ void
+-rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen,
+- uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen)
++rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
++ size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl,
++ size_t rdatalen)
+ {
+ size_t policydnamelen;
+ enum rpz_trigger t;
+ enum rpz_action a;
+ uint8_t* policydname;
+
++ if(rpz_type_ignored(rr_type)) {
++ /* this rpz action is not valid, eg. this is the SOA or NS RR */
++ return;
++ }
++ if(!dname_subdomain_c(dname, azname)) {
++ /* not subdomain of the RPZ zone. */
++ return;
++ }
++
+ if(!(policydname = calloc(1, LDNS_MAX_DOMAINLEN + 1)))
+ return;
+
+@@ -1358,13 +1527,28 @@
+ return;
+ }
+ t = rpz_dname_to_trigger(policydname, policydnamelen);
++ if(t == RPZ_INVALID_TRIGGER) {
++ /* skipping invalid trigger */
++ free(policydname);
++ return;
++ }
+ if(t == RPZ_QNAME_TRIGGER) {
+ rpz_remove_qname_trigger(r, policydname, policydnamelen, a,
+ rr_type, rr_class, rdatawl, rdatalen);
+ } else if(t == RPZ_RESPONSE_IP_TRIGGER) {
+ rpz_remove_response_ip_trigger(r, policydname, policydnamelen,
+ a, rr_type, rdatawl, rdatalen);
++ } else if(t == RPZ_CLIENT_IP_TRIGGER) {
++ rpz_remove_clientip_trigger(r, policydname, policydnamelen, a,
++ rr_type, rdatawl, rdatalen);
++ } else if(t == RPZ_NSIP_TRIGGER) {
++ rpz_remove_nsip_trigger(r, policydname, policydnamelen, a,
++ rr_type, rdatawl, rdatalen);
++ } else if(t == RPZ_NSDNAME_TRIGGER) {
++ rpz_remove_nsdname_trigger(r, policydname, policydnamelen, a,
++ rr_type, rr_class, rdatawl, rdatalen);
+ }
++ /* else it was an unsupported trigger, also skipped. */
+ free(policydname);
+ }
+
+@@ -1563,21 +1747,6 @@
+ return 1;
+ }
+
+-static struct local_rrset*
+-rpz_find_synthesized_rrset(uint16_t qtype,
+- struct clientip_synthesized_rr* data)
+-{
+- struct local_rrset* cursor = data->data;
+- while( cursor != NULL) {
+- struct packed_rrset_key* packed_rrset = &cursor->rrset->rk;
+- if(htons(qtype) == packed_rrset->type) {
+- return cursor;
+- }
+- cursor = cursor->next;
+- }
+- return NULL;
+-}
+-
+ /** allocate SOA record ubrrsetkey in region */
+ static struct ub_packed_rrset_key*
+ make_soa_ubrrset(struct auth_zone* auth_zone, struct auth_rrset* soa,
+@@ -1713,7 +1882,8 @@
+ 0, /* ns */
+ 0, /* ar */
+ 0, /* total */
+- sec_status_insecure);
++ sec_status_insecure,
++ LDNS_EDE_NONE);
+ if(msg->rep)
+ msg->rep->authoritative = 1;
+ if(!rpz_add_soa(msg->rep, ms, az))
+@@ -1742,7 +1912,8 @@
+ 0, /* ns */
+ 0, /* ar */
+ 0, /* total */
+- sec_status_insecure);
++ sec_status_insecure,
++ LDNS_EDE_NONE);
+ if(msg->rep)
+ msg->rep->authoritative = 1;
+ if(!rpz_add_soa(msg->rep, ms, az))
+@@ -1772,7 +1943,8 @@
+ 0, /* ns */
+ 0, /* ar */
+ 1, /* total */
+- sec_status_insecure);
++ sec_status_insecure,
++ LDNS_EDE_NONE);
+ if(new_reply_info == NULL) {
+ log_err("out of memory");
+ return NULL;
+@@ -1990,7 +2162,7 @@
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+- ms->respip_action_info->action = respip_truncate;
++ ms->tcp_required = 1;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+@@ -2045,7 +2217,7 @@
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+- ms->respip_action_info->action = respip_truncate;
++ ms->tcp_required = 1;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+@@ -2256,7 +2428,7 @@
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+- ms->respip_action_info->action = respip_truncate;
++ ms->tcp_required = 1;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+@@ -2276,6 +2448,10 @@
+ rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ ret = NULL;
+ }
++ if(r->log)
++ log_rpz_apply("qname", (z?z->name:NULL), NULL,
++ localzone_type_to_rpz_action(lzt),
++ &is->qchase, NULL, ms, r->log_name);
+ lock_rw_unlock(&z->lock);
+ lock_rw_unlock(&a->lock);
+ return ret;
+--- contrib/unbound/services/rpz.h.orig
++++ contrib/unbound/services/rpz.h
+@@ -84,10 +84,11 @@
+ RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/
+ };
+
+-struct clientip_synthesized_rrset{
++struct clientip_synthesized_rrset {
+ struct regional* region;
+ struct rbtree_type entries;
+- lock_rw_type lock; /* lock on the respip tree */
++ /** lock on the entries tree */
++ lock_rw_type lock;
+ };
+
+ struct clientip_synthesized_rr {
+@@ -95,10 +96,6 @@
+ struct addr_tree_node node;
+ /** lock on the node item */
+ lock_rw_type lock;
+- /** tag bitlist */
+- uint8_t* taglist;
+- /** length of the taglist (in bytes) */
+- size_t taglen;
+ /** action for this address span */
+ enum rpz_action action;
+ /** "local data" for this node */
+@@ -152,6 +149,7 @@
+ /**
+ * Delete policy matching RR, used for IXFR.
+ * @param r: the rpz to add the policy to.
++ * @param azname: dname of the auth-zone
+ * @param aznamelen: the length of the auth-zone name
+ * @param dname: dname of the RR
+ * @param dnamelen: length of the dname
+@@ -160,9 +158,9 @@
+ * @param rdatawl: rdata of the RR, prepended with the rdata size
+ * @param rdatalen: length if the RR, including the prepended rdata size
+ */
+-void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname,
+- size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl,
+- size_t rdatalen);
++void rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen,
++ uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class,
++ uint8_t* rdatawl, size_t rdatalen);
+
+ /**
+ * Walk over the RPZ zones to find and apply a QNAME trigger policy.
+--- contrib/unbound/sldns/rrdef.c.orig
++++ contrib/unbound/sldns/rrdef.c
+@@ -702,7 +702,11 @@
+
+ /* TYPEXX representation */
+ if (strlen(name) > 4 && strncasecmp(name, "TYPE", 4) == 0) {
+- return atoi(name + 4);
++ unsigned int a = atoi(name + 4);
++ if (a > LDNS_RR_TYPE_LAST) {
++ return (enum sldns_enum_rr_type)0;
++ }
++ return a;
+ }
+
+ /* Normal types */
+@@ -740,7 +744,11 @@
+
+ /* CLASSXX representation */
+ if (strlen(name) > 5 && strncasecmp(name, "CLASS", 5) == 0) {
+- return atoi(name + 5);
++ unsigned int a = atoi(name + 5);
++ if (a > LDNS_RR_CLASS_LAST) {
++ return (enum sldns_enum_rr_class)0;
++ }
++ return a;
+ }
+
+ /* Normal types */
+--- contrib/unbound/sldns/rrdef.h.orig
++++ contrib/unbound/sldns/rrdef.h
+@@ -433,10 +433,12 @@
+ LDNS_EDNS_DHU = 6, /* RFC6975 */
+ LDNS_EDNS_N3U = 7, /* RFC6975 */
+ LDNS_EDNS_CLIENT_SUBNET = 8, /* RFC7871 */
++ LDNS_EDNS_COOKIE = 10, /* RFC7873 */
+ LDNS_EDNS_KEEPALIVE = 11, /* draft-ietf-dnsop-edns-tcp-keepalive*/
+ LDNS_EDNS_PADDING = 12, /* RFC7830 */
+ LDNS_EDNS_EDE = 15, /* RFC8914 */
+- LDNS_EDNS_CLIENT_TAG = 16 /* draft-bellis-dnsop-edns-tags-01 */
++ LDNS_EDNS_CLIENT_TAG = 16, /* draft-bellis-dnsop-edns-tags-01 */
++ LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST = 65534
+ };
+ typedef enum sldns_enum_edns_option sldns_edns_option;
+
+@@ -482,6 +484,9 @@
+ #define LDNS_TSIG_ERROR_BADNAME 20
+ #define LDNS_TSIG_ERROR_BADALG 21
+
++/** DNS Cookie extended rcode */
++#define LDNS_EXT_RCODE_BADCOOKIE 23
++
+ /**
+ * Contains all information about resource record types.
+ *
+--- contrib/unbound/sldns/str2wire.c.orig
++++ contrib/unbound/sldns/str2wire.c
+@@ -357,7 +357,7 @@
+ break;
+ default : break;
+ }
+- return "\n\t ";
++ return "\n\t ";
+ }
+
+ /* Syntactic sugar for sldns_rr_new_frm_str_internal */
+@@ -448,7 +448,7 @@
+ sldns_buffer_position(strbuf));
+ }
+ hex_data_size = (size_t)atoi(token);
+- if(hex_data_size > LDNS_MAX_RDFLEN ||
++ if(hex_data_size > LDNS_MAX_RDFLEN ||
+ *rr_cur_len + hex_data_size > *rr_len) {
+ return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL,
+ sldns_buffer_position(strbuf));
+@@ -567,7 +567,7 @@
+ /* check if not quoted yet, and we have encountered quotes */
+ if(!*quoted && sldns_rdf_type_maybe_quoted(rdftype) &&
+ slen >= 2 &&
+- (token[0] == '"' || token[0] == '\'') &&
++ (token[0] == '"' || token[0] == '\'') &&
+ (token[slen-1] == '"' || token[slen-1] == '\'')) {
+ /* move token two smaller (quotes) with endnull */
+ memmove(token, token+1, slen-2);
+@@ -698,7 +698,7 @@
+ mandatory = svcparams[i];
+ }
+
+- /* 4. verify that all the SvcParamKeys in mandatory are present */
++ /* Verify that all the SvcParamKeys in mandatory are present */
+ if(mandatory) {
+ /* Divide by sizeof(uint16_t)*/
+ uint16_t mandatory_nkeys = sldns_read_uint16(mandatory + 2) / sizeof(uint16_t);
+@@ -785,7 +785,7 @@
+ token[2]=='\t')) {
+ was_unknown_rr_format = 1;
+ if((status=rrinternal_parse_unknown(strbuf, token,
+- token_len, rr, rr_len, &rr_cur_len,
++ token_len, rr, rr_len, &rr_cur_len,
+ pre_data_pos)) != 0)
+ return status;
+ } else if(token_strlen > 0 || quoted) {
+@@ -844,7 +844,7 @@
+ if (rr_type == LDNS_RR_TYPE_SVCB || rr_type == LDNS_RR_TYPE_HTTPS) {
+ size_t rdata_len = rr_cur_len - dname_len - 10;
+ uint8_t *rdata = rr+dname_len + 10;
+-
++
+ /* skip 1st rdata field SvcPriority (uint16_t) */
+ if (rdata_len < sizeof(uint16_t))
+ return LDNS_WIREPARSE_ERR_OK;
+@@ -1123,36 +1123,40 @@
+ return key_value;
+
+ } else switch (key_len) {
+- case sizeof("mandatory")-1:
+- if (!strncmp(key, "mandatory", sizeof("mandatory")-1))
+- return SVCB_KEY_MANDATORY;
+- if (!strncmp(key, "echconfig", sizeof("echconfig")-1))
+- return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */
++ case 3:
++ if (!strncmp(key, "ech", key_len))
++ return SVCB_KEY_ECH;
+ break;
+
+- case sizeof("alpn")-1:
+- if (!strncmp(key, "alpn", sizeof("alpn")-1))
++ case 4:
++ if (!strncmp(key, "alpn", key_len))
+ return SVCB_KEY_ALPN;
+- if (!strncmp(key, "port", sizeof("port")-1))
++ if (!strncmp(key, "port", key_len))
+ return SVCB_KEY_PORT;
+ break;
+
+- case sizeof("no-default-alpn")-1:
+- if (!strncmp( key , "no-default-alpn"
+- , sizeof("no-default-alpn")-1))
+- return SVCB_KEY_NO_DEFAULT_ALPN;
++ case 7:
++ if (!strncmp(key, "dohpath", key_len))
++ return SVCB_KEY_DOHPATH;
+ break;
+
+- case sizeof("ipv4hint")-1:
+- if (!strncmp(key, "ipv4hint", sizeof("ipv4hint")-1))
++ case 8:
++ if (!strncmp(key, "ipv4hint", key_len))
+ return SVCB_KEY_IPV4HINT;
+- if (!strncmp(key, "ipv6hint", sizeof("ipv6hint")-1))
++ if (!strncmp(key, "ipv6hint", key_len))
+ return SVCB_KEY_IPV6HINT;
+ break;
+
+- case sizeof("ech")-1:
+- if (!strncmp(key, "ech", sizeof("ech")-1))
+- return SVCB_KEY_ECH;
++ case 9:
++ if (!strncmp(key, "mandatory", key_len))
++ return SVCB_KEY_MANDATORY;
++ if (!strncmp(key, "echconfig", key_len))
++ return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */
++ break;
++
++ case 15:
++ if (!strncmp(key, "no-default-alpn", key_len))
++ return SVCB_KEY_NO_DEFAULT_ALPN;
+ break;
+
+ default:
+@@ -1477,7 +1481,7 @@
+ size_t str_len;
+ size_t dst_len;
+ size_t val_len;
+-
++
+ val_len = strlen(val);
+
+ if (val_len > sizeof(unescaped_dst)) {
+@@ -1511,7 +1515,34 @@
+ sldns_write_uint16(rd + 2, dst_len);
+ memcpy(rd + 4, unescaped_dst, dst_len);
+ *rd_len = 4 + dst_len;
+-
++
++ return LDNS_WIREPARSE_ERR_OK;
++}
++
++static int
++sldns_str2wire_svcbparam_dohpath_value(const char* val,
++ uint8_t* rd, size_t* rd_len)
++{
++ size_t val_len;
++
++ /* RFC6570#section-2.1
++ * "The characters outside of expressions in a URI Template string are
++ * intended to be copied literally"
++ * Practically this means we do not have to look for "double escapes"
++ * like in the alpn value list.
++ */
++
++ val_len = strlen(val);
++
++ if (*rd_len < 4 + val_len) {
++ return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
++ }
++
++ sldns_write_uint16(rd, SVCB_KEY_DOHPATH);
++ sldns_write_uint16(rd + 2, val_len);
++ memcpy(rd + 4, val, val_len);
++ *rd_len = 4 + val_len;
++
+ return LDNS_WIREPARSE_ERR_OK;
+ }
+
+@@ -1535,6 +1566,7 @@
+ case SVCB_KEY_PORT:
+ case SVCB_KEY_IPV4HINT:
+ case SVCB_KEY_IPV6HINT:
++ case SVCB_KEY_DOHPATH:
+ return LDNS_WIREPARSE_ERR_SVCB_MISSING_PARAM;
+ #endif
+ default:
+@@ -1566,6 +1598,8 @@
+ return sldns_str2wire_svcbparam_ech_value(val, rd, rd_len);
+ case SVCB_KEY_ALPN:
+ return sldns_str2wire_svcbparam_alpn_value(val, rd, rd_len);
++ case SVCB_KEY_DOHPATH:
++ return sldns_str2wire_svcbparam_dohpath_value(val, rd, rd_len);
+ default:
+ str_len = strlen(val);
+ if (*rd_len < 4 + str_len)
+@@ -1593,7 +1627,7 @@
+ /* case: key=value */
+ if (eq_pos != NULL && eq_pos[1]) {
+ val_in = eq_pos + 1;
+-
++
+ /* unescape characters and "" blocks */
+ if (*val_in == '"') {
+ val_in++;
+@@ -1610,11 +1644,11 @@
+ }
+ *val_out = 0;
+
+- return sldns_str2wire_svcparam_value(str, eq_pos - str,
+- unescaped_val[0] ? unescaped_val : NULL, rd, rd_len);
++ return sldns_str2wire_svcparam_value(str, eq_pos - str,
++ unescaped_val[0] ? unescaped_val : NULL, rd, rd_len);
+ }
+ /* case: key= */
+- else if (eq_pos != NULL && !(eq_pos[1])) {
++ else if (eq_pos != NULL && !(eq_pos[1])) {
+ return sldns_str2wire_svcparam_value(str, eq_pos - str, NULL, rd, rd_len);
+ }
+ /* case: key */
+@@ -2425,12 +2459,13 @@
+ (void)strlcpy(proto_str, token, sizeof(proto_str));
+ } else {
+ int serv_port;
+- struct servent *serv = getservbyname(token, proto_str);
+- if(serv) serv_port=(int)ntohs((uint16_t)serv->s_port);
++ if(atoi(token) != 0) serv_port=atoi(token);
++ else if(strcmp(token, "0") == 0) serv_port=0;
+ else if(strcasecmp(token, "domain")==0) serv_port=53;
+ else {
+- serv_port = atoi(token);
+- if(serv_port == 0 && strcmp(token, "0") != 0) {
++ struct servent *serv = getservbyname(token, proto_str);
++ if(serv) serv_port=(int)ntohs((uint16_t)serv->s_port);
++ else {
+ #ifdef HAVE_ENDSERVENT
+ endservent();
+ #endif
+@@ -2440,16 +2475,16 @@
+ return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX,
+ sldns_buffer_position(&strbuf));
+ }
+- if(serv_port < 0 || serv_port > 65535) {
++ }
++ if(serv_port < 0 || serv_port > 65535) {
+ #ifdef HAVE_ENDSERVENT
+- endservent();
++ endservent();
+ #endif
+ #ifdef HAVE_ENDPROTOENT
+- endprotoent();
++ endprotoent();
+ #endif
+- return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX,
+- sldns_buffer_position(&strbuf));
+- }
++ return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX,
++ sldns_buffer_position(&strbuf));
+ }
+ if(rd_len < 1+serv_port/8+1) {
+ /* bitmap is larger, init new bytes at 0 */
+--- contrib/unbound/sldns/str2wire.h.orig
++++ contrib/unbound/sldns/str2wire.h
+@@ -38,7 +38,8 @@
+ #define SVCB_KEY_IPV4HINT 4
+ #define SVCB_KEY_ECH 5
+ #define SVCB_KEY_IPV6HINT 6
+-#define SVCPARAMKEY_COUNT 7
++#define SVCB_KEY_DOHPATH 7
++#define SVCPARAMKEY_COUNT 8
+
+ #define MAX_NUMBER_OF_SVCPARAMS 64
+
+@@ -236,6 +237,7 @@
+ #define LDNS_WIREPARSE_ERR_SVCB_NO_DEFAULT_ALPN_VALUE 385
+ #define LDNS_WIREPARSE_ERR_SVCPARAM_BROKEN_RDATA 386
+
++
+ /**
+ * Get reference to a constant string for the (parse) error.
+ * @param e: error return value
+--- contrib/unbound/sldns/wire2str.c.orig
++++ contrib/unbound/sldns/wire2str.c
+@@ -159,7 +159,7 @@
+ "Mandatory SvcParamKey is missing"},
+ { LDNS_WIREPARSE_ERR_SVCB_MANDATORY_DUPLICATE_KEY,
+ "Keys in SvcParam mandatory MUST be unique" },
+- { LDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY,
++ { LDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY,
+ "mandatory MUST not be included as mandatory parameter" },
+ { LDNS_WIREPARSE_ERR_SVCB_PORT_VALUE_SYNTAX,
+ "Could not parse port SvcParamValue" },
+@@ -192,6 +192,7 @@
+ { 6, "DHU" },
+ { 7, "N3U" },
+ { 8, "edns-client-subnet" },
++ { 10, "COOKIE" },
+ { 11, "edns-tcp-keepalive"},
+ { 12, "Padding" },
+ { 15, "EDE"},
+@@ -199,6 +200,38 @@
+ };
+ sldns_lookup_table* sldns_edns_options = sldns_edns_options_data;
+
++/* From RFC8914 5.2 Table 3, the "Extended DNS Error Codes" registry. */
++static sldns_lookup_table sldns_edns_ede_codes_data[] = {
++ { LDNS_EDE_NONE, "None" },
++ { LDNS_EDE_OTHER, "Other Error" },
++ { LDNS_EDE_UNSUPPORTED_DNSKEY_ALG, "Unsupported DNSKEY Algorithm" },
++ { LDNS_EDE_UNSUPPORTED_DS_DIGEST, "Unsupported DS Digest Type" },
++ { LDNS_EDE_STALE_ANSWER, "Stale Answer" },
++ { LDNS_EDE_FORGED_ANSWER, "Forged Answer" },
++ { LDNS_EDE_DNSSEC_INDETERMINATE, "DNSSEC Indeterminate" },
++ { LDNS_EDE_DNSSEC_BOGUS, "DNSSEC Bogus" },
++ { LDNS_EDE_SIGNATURE_EXPIRED, "Signature Expired" },
++ { LDNS_EDE_SIGNATURE_NOT_YET_VALID, "Signature Not Yet Valid" },
++ { LDNS_EDE_DNSKEY_MISSING, "DNSKEY Missing" },
++ { LDNS_EDE_RRSIGS_MISSING, "RRSIGs Missing" },
++ { LDNS_EDE_NO_ZONE_KEY_BIT_SET, "No Zone Key Bit Set" },
++ { LDNS_EDE_NSEC_MISSING, "NSEC Missing" },
++ { LDNS_EDE_CACHED_ERROR, "Cached Error" },
++ { LDNS_EDE_NOT_READY, "Not Ready" },
++ { LDNS_EDE_BLOCKED, "Blocked" },
++ { LDNS_EDE_CENSORED, "Censored" },
++ { LDNS_EDE_FILTERED, "Filtered" },
++ { LDNS_EDE_PROHIBITED, "Prohibited" },
++ { LDNS_EDE_STALE_NXDOMAIN_ANSWER, "Stale NXDOMAIN Answer" },
++ { LDNS_EDE_NOT_AUTHORITATIVE, "Not Authoritative" },
++ { LDNS_EDE_NOT_SUPPORTED, "Not Supported" },
++ { LDNS_EDE_NO_REACHABLE_AUTHORITY, "No Reachable Authority" },
++ { LDNS_EDE_NETWORK_ERROR, "Network Error" },
++ { LDNS_EDE_INVALID_DATA, "Invalid Data" },
++ { 0, NULL}
++};
++sldns_lookup_table* sldns_edns_ede_codes = sldns_edns_ede_codes_data;
++
+ static sldns_lookup_table sldns_tsig_errors_data[] = {
+ { LDNS_TSIG_ERROR_NOERROR, "NOERROR" },
+ { LDNS_RCODE_FORMERR, "FORMERR" },
+@@ -224,7 +257,7 @@
+ /* draft-ietf-dnsop-svcb-https-06: 6. Initial SvcParamKeys */
+ const char *svcparamkey_strs[] = {
+ "mandatory", "alpn", "no-default-alpn", "port",
+- "ipv4hint", "ech", "ipv6hint"
++ "ipv4hint", "ech", "ipv6hint", "dohpath"
+ };
+
+ char* sldns_wire2str_pkt(uint8_t* data, size_t len)
+@@ -487,7 +520,7 @@
+ uint8_t* rr = *d;
+ size_t rrlen = *dlen, dname_off, rdlen, ordlen;
+ uint16_t rrtype = 0;
+-
++
+ if(*dlen >= 3 && (*d)[0]==0 &&
+ sldns_read_uint16((*d)+1)==LDNS_RR_TYPE_OPT) {
+ /* perform EDNS OPT processing */
+@@ -1119,7 +1152,7 @@
+ w += sldns_str_print(s, slen, "%s", ",");
+ }
+ w += sldns_str_print(s, slen, "\"");
+-
++
+ return w;
+ }
+
+@@ -1139,7 +1172,7 @@
+ (*s) += size;
+ (*slen) -= size;
+
+- w += sldns_str_print(s, slen, "\"");
++ w += sldns_str_print(s, slen, "\"");
+
+ return w + size;
+ }
+@@ -1162,7 +1195,7 @@
+
+ /* verify that we have data_len data */
+ if (data_len > *dlen)
+- return -1;
++ return -1;
+
+ written_chars += sldns_print_svcparamkey(s, slen, svcparamkey);
+ if (!data_len) {
+@@ -1174,6 +1207,7 @@
+ case SVCB_KEY_IPV4HINT:
+ case SVCB_KEY_IPV6HINT:
+ case SVCB_KEY_MANDATORY:
++ case SVCB_KEY_DOHPATH:
+ return -1;
+ default:
+ return written_chars;
+@@ -1201,6 +1235,8 @@
+ case SVCB_KEY_ECH:
+ r = sldns_wire2str_svcparam_ech2str(s, slen, data_len, *d);
+ break;
++ case SVCB_KEY_DOHPATH:
++ /* fallthrough */
+ default:
+ r = sldns_str_print(s, slen, "=\"");
+
+@@ -1222,7 +1258,7 @@
+ }
+ if (r <= 0)
+ return -1; /* wireformat error */
+-
++
+ written_chars += r;
+ *d += data_len;
+ *dlen -= data_len;
+@@ -1551,7 +1587,7 @@
+ unsigned i, bit, window, block_len;
+ uint16_t t;
+ int w = 0;
+-
++
+ /* check for errors */
+ while(pl) {
+ if(pl < 2) return -1;
+@@ -2231,6 +2267,52 @@
+ return w;
+ }
+
++int sldns_wire2str_edns_ede_print(char** s, size_t* sl,
++ uint8_t* data, size_t len)
++{
++ uint16_t ede_code;
++ int w = 0;
++ sldns_lookup_table *lt;
++ size_t i;
++ int printable;
++
++ if(len < 2) {
++ w += sldns_str_print(s, sl, "malformed ede ");
++ w += print_hex_buf(s, sl, data, len);
++ return w;
++ }
++
++ ede_code = sldns_read_uint16(data);
++ lt = sldns_lookup_by_id(sldns_edns_ede_codes, (int)ede_code);
++ if(lt && lt->name)
++ w += sldns_str_print(s, sl, "%s", lt->name);
++ else w += sldns_str_print(s, sl, "%d", (int)ede_code);
++
++ if(len == 2)
++ return w;
++
++ w += sldns_str_print(s, sl, " ");
++
++ /* If it looks like text, show it as text. */
++ printable=1;
++ for(i=2; i<len; i++) {
++ if(isprint((unsigned char)data[i]) || data[i] == '\t')
++ continue;
++ printable = 0;
++ break;
++ }
++ if(printable) {
++ w += sldns_str_print(s, sl, "\"");
++ for(i=2; i<len; i++) {
++ w += str_char_print(s, sl, data[i]);
++ }
++ w += sldns_str_print(s, sl, "\"");
++ } else {
++ w += print_hex_buf(s, sl, data+2, len-2);
++ }
++ return w;
++}
++
+ int sldns_wire2str_edns_option_print(char** s, size_t* sl,
+ uint16_t option_code, uint8_t* optdata, size_t optlen)
+ {
+@@ -2265,6 +2347,9 @@
+ case LDNS_EDNS_PADDING:
+ w += print_hex_buf(s, sl, optdata, optlen);
+ break;
++ case LDNS_EDNS_EDE:
++ w += sldns_wire2str_edns_ede_print(s, sl, optdata, optlen);
++ break;
+ default:
+ /* unknown option code */
+ w += print_hex_buf(s, sl, optdata, optlen);
+--- contrib/unbound/sldns/wire2str.h.orig
++++ contrib/unbound/sldns/wire2str.h
+@@ -36,6 +36,8 @@
+ extern struct sldns_struct_lookup_table* sldns_edns_flags;
+ /** EDNS option codes */
+ extern struct sldns_struct_lookup_table* sldns_edns_options;
++/** EDNS EDE codes */
++extern struct sldns_struct_lookup_table* sldns_edns_ede_codes;
+ /** error string from wireparse */
+ extern struct sldns_struct_lookup_table* sldns_wireparse_errors;
+ /** tsig errors are the rcodes with extra (higher) values */
+@@ -1020,6 +1022,17 @@
+ int sldns_wire2str_edns_subnet_print(char** str, size_t* str_len,
+ uint8_t* option_data, size_t option_len);
+
++/**
++ * Print EDNS EDE option data to string. User buffers, moves string pointers.
++ * @param str: string buffer.
++ * @param str_len: length of string buffer.
++ * @param option_data: buffer with EDNS option code data.
++ * @param option_len: length of the data for this option.
++ * @return number of characters (except null) needed to print.
++ */
++int sldns_wire2str_edns_ede_print(char** str, size_t* str_len,
++ uint8_t* option_data, size_t option_len);
++
+ /**
+ * Print an EDNS option as OPT: VALUE. User buffers, moves string pointers.
+ * @param str: string buffer.
+--- contrib/unbound/smallapp/unbound-anchor.c.orig
++++ contrib/unbound/smallapp/unbound-anchor.c
+@@ -1582,8 +1582,7 @@
+
+ /**
+ * Perform XML parsing of the root-anchors file
+- * Its format description can be read here
+- * https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.txt
++ * Its format description can be found in RFC 7958.
+ * It uses libexpat.
+ * @param xml: BIO with xml data.
+ * @param now: the current time for checking DS validity periods.
+--- contrib/unbound/smallapp/unbound-checkconf.c.orig
++++ contrib/unbound/smallapp/unbound-checkconf.c
+@@ -707,6 +707,23 @@
+ cfg->auto_trust_anchor_file_list, cfg->chrootdir, cfg);
+ check_chroot_filelist_wild("trusted-keys-file",
+ cfg->trusted_keys_file_list, cfg->chrootdir, cfg);
++ if(cfg->disable_edns_do && strstr(cfg->module_conf, "validator")
++ && (cfg->trust_anchor_file_list
++ || cfg->trust_anchor_list
++ || cfg->auto_trust_anchor_file_list
++ || cfg->trusted_keys_file_list)) {
++ char* key = NULL;
++ if(cfg->auto_trust_anchor_file_list)
++ key = cfg->auto_trust_anchor_file_list->str;
++ if(!key && cfg->trust_anchor_file_list)
++ key = cfg->trust_anchor_file_list->str;
++ if(!key && cfg->trust_anchor_list)
++ key = cfg->trust_anchor_list->str;
++ if(!key && cfg->trusted_keys_file_list)
++ key = cfg->trusted_keys_file_list->str;
++ if(!key) key = "";
++ fatal_exit("disable-edns-do does not allow DNSSEC to work, but the validator module uses a trust anchor %s, turn off disable-edns-do or disable validation", key);
++ }
+ #ifdef USE_IPSECMOD
+ if(cfg->ipsecmod_enabled && strstr(cfg->module_conf, "ipsecmod")) {
+ /* only check hook if enabled */
+@@ -714,7 +731,7 @@
+ cfg->chrootdir, cfg);
+ }
+ #endif
+- /* remove chroot setting so that modules are not stripping pathnames*/
++ /* remove chroot setting so that modules are not stripping pathnames */
+ free(cfg->chrootdir);
+ cfg->chrootdir = NULL;
+
+--- contrib/unbound/smallapp/unbound-control.c.orig
++++ contrib/unbound/smallapp/unbound-control.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -59,6 +59,7 @@
+ #include "util/locks.h"
+ #include "util/net_help.h"
+ #include "util/shm_side/shm_main.h"
++#include "util/timeval_func.h"
+ #include "daemon/stats.h"
+ #include "sldns/wire2str.h"
+ #include "sldns/pkthdr.h"
+@@ -186,31 +187,6 @@
+ #ifdef HAVE_SHMGET
+ /** what to put on statistics lines between var and value, ": " or "=" */
+ #define SQ "="
+-/** divide sum of timers to get average */
+-static void
+-timeval_divide(struct timeval* avg, const struct timeval* sum, long long d)
+-{
+-#ifndef S_SPLINT_S
+- size_t leftover;
+- if(d <= 0) {
+- avg->tv_sec = 0;
+- avg->tv_usec = 0;
+- return;
+- }
+- avg->tv_sec = sum->tv_sec / d;
+- avg->tv_usec = sum->tv_usec / d;
+- /* handle fraction from seconds divide */
+- leftover = sum->tv_sec - avg->tv_sec*d;
+- if(leftover <= 0)
+- leftover = 0;
+- avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
+- if(avg->tv_sec < 0)
+- avg->tv_sec = 0;
+- if(avg->tv_usec < 0)
+- avg->tv_usec = 0;
+-#endif
+-}
+-
+ /** print unsigned long stats value */
+ #define PR_UL_NM(str, var) printf("%s."str SQ"%lu\n", nm, (unsigned long)(var));
+ #define PR_UL(str, var) printf(str SQ"%lu\n", (unsigned long)(var));
+@@ -226,12 +202,20 @@
+ {
+ struct timeval sumwait, avg;
+ PR_UL_NM("num.queries", s->svr.num_queries);
+- PR_UL_NM("num.queries_ip_ratelimited",
++ PR_UL_NM("num.queries_ip_ratelimited",
+ s->svr.num_queries_ip_ratelimited);
++ PR_UL_NM("num.queries_cookie_valid",
++ s->svr.num_queries_cookie_valid);
++ PR_UL_NM("num.queries_cookie_client",
++ s->svr.num_queries_cookie_client);
++ PR_UL_NM("num.queries_cookie_invalid",
++ s->svr.num_queries_cookie_invalid);
+ PR_UL_NM("num.cachehits",
+ s->svr.num_queries - s->svr.num_queries_missed_cache);
+ PR_UL_NM("num.cachemiss", s->svr.num_queries_missed_cache);
+ PR_UL_NM("num.prefetch", s->svr.num_queries_prefetch);
++ PR_UL_NM("num.queries_timed_out", s->svr.num_queries_timed_out);
++ PR_UL_NM("query.queue_time_us.max", s->svr.max_query_time_us);
+ PR_UL_NM("num.expired", s->svr.ans_expired);
+ PR_UL_NM("num.recursivereplies", s->mesh_replies_sent);
+ #ifdef USE_DNSCRYPT
+@@ -403,6 +387,9 @@
+ PR_UL("rrset.cache.count", s->svr.rrset_cache_count);
+ PR_UL("infra.cache.count", s->svr.infra_cache_count);
+ PR_UL("key.cache.count", s->svr.key_cache_count);
++ /* max collisions */
++ PR_UL("msg.cache.max_collisions", s->svr.msg_cache_max_collisions);
++ PR_UL("rrset.cache.max_collisions", s->svr.rrset_cache_max_collisions);
+ /* applied RPZ actions */
+ for(i=0; i<UB_STATS_RPZ_ACTION_NUM; i++) {
+ if(i == RPZ_NO_OVERRIDE_ACTION)
+@@ -426,6 +413,9 @@
+ PR_UL("num.query.subnet", s->svr.num_query_subnet);
+ PR_UL("num.query.subnet_cache", s->svr.num_query_subnet_cache);
+ #endif
++#ifdef USE_CACHEDB
++ PR_UL("num.query.cachedb", s->svr.num_query_cachedb);
++#endif
+ }
+
+ /** print statistics out of memory structures */
+@@ -989,7 +979,7 @@
+ fatal_exit("could not exec unbound: %s",
+ strerror(ENOSYS));
+ #else
+- if(execlp("unbound", "unbound", "-c", cfgfile,
++ if(execlp("unbound", "unbound", "-c", cfgfile,
+ (char*)NULL) < 0) {
+ fatal_exit("could not exec unbound: %s",
+ strerror(errno));
+--- contrib/unbound/smallapp/unbound-host.c.orig
++++ contrib/unbound/smallapp/unbound-host.c
+@@ -482,6 +482,7 @@
+ case '?':
+ case 'h':
+ default:
++ ub_ctx_delete(ctx);
+ usage();
+ }
+ }
+@@ -495,8 +496,10 @@
+ }
+ argc -= optind;
+ argv += optind;
+- if(argc != 1)
++ if(argc != 1) {
++ ub_ctx_delete(ctx);
+ usage();
++ }
+
+ #ifdef HAVE_SSL
+ #ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
+--- /dev/null
++++ contrib/unbound/testdata/00-lint.tdir/00-lint.pre
+@@ -0,0 +1,14 @@
++# #-- 00-lint.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++. ../common.sh
++PRE="../.."
++
++if test -f $PRE/unbound_test_00-lint ; then
++ echo test enabled
++else
++ skip_test "test skipped; clang linter preferred over splint"
++fi
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_cached_ede.crpl
+@@ -0,0 +1,91 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: no
++ minimal-responses: no
++ module-config: "cachedb validator iterator"
++ trust-anchor-signaling: no
++ verbosity: 4
++ ede: yes
++ val-log-level: 2
++ trust-anchor: "example.nl. DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29B22446B1"
++
++
++cachedb:
++ backend: "testframe"
++ secret-seed: "testvalue"
++
++stub-zone:
++ name: "example.nl"
++ stub-addr: 193.0.14.129
++CONFIG_END
++
++SCENARIO_BEGIN Test cachedb support for caching EDEs.
++
++RANGE_BEGIN 0 10
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN DNSKEY
++SECTION ANSWER
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN A
++SECTION ANSWER
++example.nl. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; get the entry in cache.
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ FF FE ; option code = 65534 (LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST)
++ 00 00 ; option length
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; get the answer for it
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++; query again for the cached entry
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ FF FE ; option code = 65534 (LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST)
++ 00 00 ; option length
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; this must be a cached answer since stub is not answering in this range
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.conf
+@@ -0,0 +1,29 @@
++server:
++ verbosity: 4
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ module-config: "cachedb iterator"
++ do-not-query-localhost: no
++ qname-minimisation: no
++
++forward-zone:
++ name: "."
++ forward-addr: 127.0.0.1@@TOPORT@
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 127.0.0.1@@TOPORT@
++
++remote-control:
++ control-enable: yes
++ control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
++ control-use-cert: no
++
++cachedb:
++ backend: "testframe"
++ secret-seed: "testvalue"
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.dsc
+@@ -0,0 +1,16 @@
++BaseName: cachedb_no_store
++Version: 1.0
++Description: cachedb test the cachedb-no-store option
++CreationDate: Wed 11 Oct 11:00:00 CEST 2023
++Maintainer: dr. W.C.A. Wijngaards
++Category:
++Component:
++CmdDepends:
++Depends:
++Help:
++Pre: cachedb_no_store.pre
++Post: cachedb_no_store.post
++Test: cachedb_no_store.test
++AuxFiles:
++Passed:
++Failure:
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.post
+@@ -0,0 +1,20 @@
++# #-- cachedb_no_store.post --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# source the test var file when it's there
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++#
++# do your teardown here
++PRE="../.."
++. ../common.sh
++
++echo "> cat logfiles"
++cat fwd.log
++if test -f fwd2.log; then cat fwd2.log; else echo "no fwd2.log"; fi
++if test -f fwd3.log; then cat fwd3.log; else echo "no fwd3.log"; fi
++if test -f fwd4.log; then cat fwd4.log; else echo "no fwd4.log"; fi
++cat unbound.log
++if test -f unbound2.log; then cat unbound2.log; else echo "no unbound2.log"; fi
++kill_pid $FWD_PID
++kill_pid `cat unbound.pid`
++rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.pre
+@@ -0,0 +1,36 @@
++# #-- cachedb_no_store.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++if grep "define USE_CACHEDB 1" $PRE/config.h; then echo test enabled; else skip_test "test skipped"; fi
++
++get_random_port 2
++UNBOUND_PORT=$RND_PORT
++FWD_PORT=$(($RND_PORT + 1))
++echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
++echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
++
++# start forwarder
++get_ldns_testns
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.testns >fwd.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++
++# make config file
++CONTROL_PATH=/tmp
++CONTROL_PID=$$
++sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < cachedb_no_store.conf > ub.conf
++# start unbound in the background
++$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
++echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
++
++cat .tpkg.var.test
++wait_ldns_testns_up fwd.log
++wait_unbound_up unbound.log
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.servfail.testns
+@@ -0,0 +1,8 @@
++ENTRY_BEGIN
++MATCH opcode
++ADJUST copy_id copy_query
++REPLY QR AA SERVFAIL
++SECTION QUESTION
++txt1.example.com. IN TXT
++SECTION ANSWER
++ENTRY_END
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.test
+@@ -0,0 +1,132 @@
++# #-- cachedb_no_store.test --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++
++# do the test
++get_ldns_testns
++
++# query for a text record that is stored by unbound's cache and cachedb
++# in the testframe cache.
++echo "> dig txt1.example.com."
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# stop the forwarder with servfail, to check the answer came from the cache
++echo "> stop ldns-testns"
++kill_pid $FWD_PID
++echo "> start ldns-testns with servfails"
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.servfail.testns >fwd2.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++wait_ldns_testns_up fwd2.log
++
++echo "> dig txt1.example.com. from unbound cache"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# clear the cache of unbound, but not cachedb testframe cache
++echo "> unbound-control flush"
++$PRE/unbound-control -c ub.conf flush_type txt1.example.com. TXT
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++else
++ echo "exit value: OK"
++fi
++
++echo "> dig txt1.example.com. from cachedb"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# start the forwarder again.
++echo "> stop ldns-testns"
++kill_pid $FWD_PID
++echo "> start ldns-testns"
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.testns >fwd3.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++wait_ldns_testns_up fwd3.log
++
++# stop unbound to flush the cachedb cache
++echo "> stop unbound"
++kill_pid `cat unbound.pid`
++
++echo ""
++echo "> config unbound with cachedb-no-store: yes"
++echo "cachedb: cachedb-no-store: yes" >> ub.conf
++
++# start unbound again.
++echo "> start unbound"
++$PRE/unbound -d -c ub.conf >unbound2.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++wait_unbound_up unbound2.log
++
++echo ""
++echo "> dig txt1.example.com."
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# stop the forwarder with servfail, to check the answer came from the cache
++echo "> stop ldns-testns"
++kill_pid $FWD_PID
++echo "> start ldns-testns with servfails"
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.servfail.testns >fwd4.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++wait_ldns_testns_up fwd4.log
++
++echo "> dig txt1.example.com. from unbound cache"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# clear the cache of unbound, but not cachedb testframe cache
++echo "> unbound-control flush"
++$PRE/unbound-control -c ub.conf flush_type txt1.example.com. TXT
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++else
++ echo "exit value: OK"
++fi
++
++echo "> dig txt1.example.com. from cachedb, but that has no message stored"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "SERVFAIL" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++exit 0
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.testns
+@@ -0,0 +1,9 @@
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++txt1.example.com. IN TXT
++SECTION ANSWER
++txt1.example.com. IN TXT "example text message"
++ENTRY_END
+--- /dev/null
++++ contrib/unbound/testdata/disable_edns_do.rpl
+@@ -0,0 +1,164 @@
++; config options
++; The island of trust is at example.com
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ trust-anchor-signaling: no
++ minimal-responses: no
++ disable-edns-do: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test lookup with disable-edns-do
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; 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.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response to query of interest, when sent with EDNS DO
++ENTRY_BEGIN
++MATCH opcode qtype qname DO
++ADJUST copy_id
++REPLY QR AA DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN A 10.20.30.40
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
++ENTRY_END
++
++; response to query of interest, when sent without DO
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA 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 DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; recursion happens here.
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/edns_downstream_cookies.rpl
+@@ -0,0 +1,235 @@
++; config options
++server:
++ answer-cookie: yes
++ cookie-secret: "000102030405060708090a0b0c0d0e0f"
++ access-control: 127.0.0.1 allow_cookie
++ access-control: 1.2.3.4 allow
++ local-data: "test. TXT test"
++
++CONFIG_END
++
++SCENARIO_BEGIN Test downstream DNS Cookies
++
++; Note: When a valid hash was required, it was generated by running this test
++; with an invalid one and checking the output for the valid one.
++; Actual hash generation is tested with unit tests.
++
++; Query without a client cookie ...
++STEP 0 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++; ... get TC and refused
++STEP 1 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA TC REFUSED
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query without a client cookie on TCP ...
++STEP 10 QUERY
++ENTRY_BEGIN
++REPLY RD
++MATCH TCP
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++; ... get an answer
++STEP 11 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with only a client cookie ...
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 08 ; Length 8
++ 31 32 33 34 35 36 37 38 ; Random bits
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get BADCOOKIE and a new cookie
++STEP 21 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query with an invalid cookie ...
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 02 00 00 00 ; wrong version
++ 00 00 00 00 ; Timestamp
++ 31 32 33 34 35 36 37 38 ; wrong hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get BADCOOKIE and a new cookie
++STEP 31 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query with an invalid cookie from a non-cookie protected address ...
++STEP 40 QUERY ADDRESS 1.2.3.4
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 02 00 00 00 ; wrong version
++ 00 00 00 00 ; Timestamp
++ 31 32 33 34 35 36 37 38 ; wrong hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get answer and a cookie
++STEP 41 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with a valid cookie ...
++STEP 50 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 00 00 ; Timestamp
++ 38 52 7b a8 c6 a4 ea 96 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get answer and the cookie
++STEP 51 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with a valid >30 minutes old cookie ...
++STEP 59 TIME_PASSES ELAPSE 1801
++STEP 60 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 00 00 ; Timestamp
++ 38 52 7b a8 c6 a4 ea 96 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... Get answer and a refreshed cookie
++; (we don't check the re-freshness here; it has its own unit test)
++STEP 61 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with a hash-valid >60 minutes old cookie ...
++STEP 69 TIME_PASSES ELAPSE 3601
++STEP 70 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 07 09 ; Timestamp (1801)
++ 77 81 38 e3 8f aa 72 86 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get BADCOOKIE and a new cookie
++STEP 71 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query with a valid future (<5 minutes) cookie ...
++STEP 80 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 16 45 ; Timestamp (1801 + 3601 + 299)
++ 4a f5 0f df f0 e8 c7 09 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get an answer
++STEP 81 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.conf
+@@ -0,0 +1,28 @@
++server:
++ verbosity: 5
++ # num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: .
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ local-data: "test. IN TXT localdata"
++
++ ip-ratelimit: 1
++ ip-ratelimit-cookie: 0
++ ip-ratelimit-factor: 0
++ ip-ratelimit-backoff: yes
++ answer-cookie: yes
++ access-control: 127.0.0.0/8 allow_cookie
++
++remote-control:
++ control-enable: yes
++ control-interface: 127.0.0.1
++ # control-interface: ::1
++ control-port: @CONTROL_PORT@
++ server-key-file: "unbound_server.key"
++ server-cert-file: "unbound_server.pem"
++ control-key-file: "unbound_control.key"
++ control-cert-file: "unbound_control.pem"
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.dsc
+@@ -0,0 +1,16 @@
++BaseName: ip_ratelimit
++Version: 1.0
++Description: Test IP source ratelimit.
++CreationDate: Tue Aug 8 00:00:00 CET 2023
++Maintainer: Yorgos Thessalonikefs
++Category:
++Component:
++CmdDepends:
++Depends:
++Help:
++Pre: ip_ratelimit.pre
++Post: ip_ratelimit.post
++Test: ip_ratelimit.test
++AuxFiles:
++Passed:
++Failure:
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.post
+@@ -0,0 +1,13 @@
++# #-- ip_ratelimit.post --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# source the test var file when it's there
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++#
++# do your teardown here
++. ../common.sh
++kill_pid $UNBOUND_PID
++if test -f unbound.log; then
++ echo ">>> unbound log"
++ cat unbound.log
++fi
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.pre
+@@ -0,0 +1,24 @@
++# #-- ip_ratelimit.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++get_random_port 2
++UNBOUND_PORT=$RND_PORT
++CONTROL_PORT=$(($RND_PORT + 1))
++echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
++echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test
++
++# make config file
++sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < ip_ratelimit.conf > ub.conf
++# start unbound in the background
++$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++
++wait_unbound_up unbound.log
++
++cat .tpkg.var.test
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.test
+@@ -0,0 +1,165 @@
++# #-- ip_ratelimit.test --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++
++get_make
++(cd $PRE; $MAKE streamtcp)
++
++# These tests rely on second time precision. To combat false negatives the
++# tests run multiple times and we allow 1/3 of the runs to fail.
++total_runs=6
++success_threshold=4 # 2/3*total_runs
++
++if dig -h 2>&1 | grep "cookie" >/dev/null; then
++ nocookie="+nocookie"
++else
++ nocookie=""
++fi
++
++echo "> First get a valid cookie"
++dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:0102030405060708 $nocookie +tcp +retry=0 +time=1 test. TXT >outfile 2>&1
++if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++fi
++if test `grep "COOKIE: " outfile | wc -l` -ne 1; then
++ echo "Could not get cookie"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++fi
++cookie=`grep "COOKIE: " outfile | cut -d ' ' -f 3`
++
++successes=0
++echo "> Three parallel queries with backoff and cookie"
++# For this test we send three parallel queries. The ratelimit should be reached
++# for that second. We send a query to verify that there is no reply.
++# Then for the next second we again send three parallel queries and we expect
++# none of them to be allowed through because of the backoff logic that keeps
++# rolling the RATE_WINDOW based on demand.
++# Again we send another query but with a valid cookie and we expect to receive
++# an answer.
++for i in $(seq 1 $total_runs); do
++ # Try to hit limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect no answer because of limit
++ dig @127.0.0.1 -p $UNBOUND_PORT $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -eq 0; then
++ continue
++ fi
++ # Try to keep limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect answer because of DNS cookie
++ dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$cookie $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -ne 0; then
++ continue
++ fi
++ ((successes++))
++ # We don't have to wait for all the runs to complete if we know
++ # we passed the threshold.
++ if test $successes -ge $success_threshold; then
++ break
++ fi
++done
++
++if test $successes -ge $success_threshold; then
++ echo "Three parallel queries with backoff and cookie OK"
++else
++ echo "Three parallel queries with backoff and cookie NOT OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Three parallel queries with backoff and cookie NOT OK"
++ exit 1
++fi
++
++echo "> Activating ip-ratelimit-cookie"
++echo "$PRE/unbound-control -c ub.conf set_option ip-ratelimit-cookie: 1"
++$PRE/unbound-control -c ub.conf set_option ip-ratelimit-cookie: 1
++if test $? -ne 0; then
++ echo "wrong exit value after success"
++ exit 1
++fi
++
++successes=0
++echo "> Three parallel queries with backoff and cookie with ip-ratelimit-cookie"
++# This is the exact same test as above with the exception that we don't expect
++# an answer on the last query because ip-ratelimit-cookie is now enabled.
++for i in $(seq 1 $total_runs); do
++ # Try to hit limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect no answer because of limit
++ dig @127.0.0.1 -p $UNBOUND_PORT $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -eq 0; then
++ continue
++ fi
++ # Try to keep limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect no answer because of ip-ratelimit-cookie
++ dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$cookie $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -eq 0; then
++ continue
++ fi
++ ((successes++))
++ # We don't have to wait for all the runs to complete if we know
++ # we passed the threshold.
++ if test $successes -ge $success_threshold; then
++ break
++ fi
++done
++
++if test $successes -ge $success_threshold; then
++ echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie OK"
++else
++ echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie NOT OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie NOT OK"
++ exit 1
++fi
++
++exit 0
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_control.key
+@@ -0,0 +1,39 @@
++-----BEGIN RSA PRIVATE KEY-----
++MIIG4gIBAAKCAYEAstEp+Pyh8XGrtZ77A4FhYjvbeB3dMa7Q2rGWxobzlA9przhA
++1aChAvUtCOAuM+rB6NTNB8YWfZJbQHawyMNpmC77cg6vXLYCGUQHZyAqidN049RJ
++F5T7j4N8Vniv17LiRdr0S6swy4PRvEnIPPV43EQHZqC5jVvHsKkhIfmBF/Dj5TXR
++ypeawWV/m5jeU6/4HRYMfytBZdO1mPXuWLh0lgbQ4SCbgrOUVD3rniMk1yZIbQOm
++vlDHYqekjDb/vOW2KxUQLG04aZMJ1mWfdbwG0CKQkSjISEDZ1l76vhM6mTM0fwXb
++IvyFZ9yPPCle1mF5aSlxS2cmGuGVSRQaw8XF9fe3a9ACJJTr33HdSpyaZkKRAUzL
++cKqLCl323daKv3NwwAT03Tj4iQM416ASMoiyfFa/2GWTKQVjddu8Crar7tGaf5xr
++lig4DBmrBvdYA3njy72/RD71hLwmlRoCGU7dRuDr9O6KASUm1Ri91ONZ/qdjMvov
++15l2vj4GV+KXR00dAgMBAAECggGAHepIL1N0dEQkCdpy+/8lH54L9WhpnOo2HqAf
++LU9eaKK7d4jdr9+TkD8cLaPzltPrZNxVALvu/0sA4SP6J1wpyj/x6P7z73qzly5+
++Xo5PD4fEwmi9YaiW/UduAblnEZrnp/AddptJKoL/D5T4XtpiQddPtael4zQ7kB57
++YIexRSQTvEDovA/o3/nvA0TrzOxfgd4ycQP3iOWGN/TMzyLsvjydrUwbOB567iz9
++whL3Etdgvnwh5Sz2blbFfH+nAR8ctvFFz+osPvuIVR21VMEI6wm7kTpSNnQ6sh/c
++lrLb/bTADn4g7z/LpIZJ+MrLvyEcoqValrLYeFBhM9CV8woPxvkO2P3pU47HVGax
++tC7GV6a/kt5RoKFd/TNdiA3OC7NGZtaeXv9VkPf4fVwBtSO9d5ZZXTGEynDD/rUQ
++U4KFJe6OD23APjse08HiiKqTPhsOneOONU67iqoaTdIkT2R4EdlkVEDpXVtWb+G9
++Q+IqYzVljlzuyHrhWXLJw/FMa2aBAoHBAOnZbi4gGpH+P6886WDWVgIlTccuXoyc
++Mg9QQYk9UDeXxL0AizR5bZy49Sduegz9vkHpAiZARQsUnizHjZ8YlRcrmn4t6tx3
++ahTIKAjdprnxJfYINM580j8CGbXvX5LhIlm3O267D0Op+co3+7Ujy+cjsIuFQrP+
++1MqMgXSeBjzC1APivmps7HeFE+4w0k2PfN5wSMDNCzLo99PZuUG5XZ93OVOS5dpN
++b+WskdcD8NOoJy/X/5A08veEI/jYO/DyqQKBwQDDwUQCOWf41ecvJLtBHKmEnHDz
++ftzHino9DRKG8a9XaN4rmetnoWEaM2vHGX3pf3mwH+dAe8vJdAQueDhBKYeEpm6C
++TYNOpou1+Zs5s99BilCTNYo8fkMOAyqwRwmz9zgHS6QxXuPwsghKefLJGt6o6RFF
++tfWVTfLlYJ+I3GQe3ySsk3wjVz4oUTKiyiq5+KzD+HhEkS7u+RQ7Z0ZI2xd2cF8Y
++aN2hjKDpcOiFf3CDoqka5D1qMNLgIHO52AHww1UCgcA1h7o7AMpURRka6hyaODY0
++A4oMYEbwdQjYjIyT998W+rzkbu1us6UtzQEBZ760npkgyU/epbOoV63lnkCC/MOU
++LD0PST+L/CHiY/cWIHb79YG1EifUZKpUFg0Aoq0EGFkepF0MefGCkbRGYA5UZr9U
++R80wAu9D+L+JJiS0J0BSRF74DL196zUuHt5zFeXuLzxsRtPAnq9DliS08BACRYZy
++7H3I7cWD9Vn5/0jbKWHFcaaWwyETR6uekTcSzZzbCRECgcBeoE3/xUA9SSk34Mmj
++7/cB4522Ft0imA3+9RK/qJTZ7Bd5fC4PKjOGNtUiqW/0L2rjeIiQ40bfWvWqgPKw
++jSK1PL6uvkl6+4cNsFsYyZpiVDoe7wKju2UuoNlB3RUTqa2r2STFuNj2wRjA57I1
++BIgdnox65jqQsd14g/yaa+75/WP9CE45xzKEyrtvdcqxm0Pod3OrsYK+gikFjiar
++kT0GQ8u0QPzh2tjt/2ZnIfOBrl+QYERP0MofDZDjhUdq2wECgcB0Lu841+yP5cdR
++qbJhXO4zJNh7oWNcJlOuQp3ZMNFrA1oHpe9pmLukiROOy01k9WxIMQDzU5GSqRv3
++VLkYOIcbhJ3kClKAcM3j95SkKbU2H5/RENb3Ck52xtl4pNU1x/3PnVFZfDVuuHO9
++MZ9YBcIeK98MyP2jr5JtFKnOyPE7xKq0IHIhXadpbc2wjje5FtZ1cUtMyEECCXNa
++C1TpXebHGyXGpY9WdWXhjdE/1jPvfS+uO5WyuDpYPr339gsdq1g=
++-----END RSA PRIVATE KEY-----
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_control.pem
+@@ -0,0 +1,22 @@
++-----BEGIN CERTIFICATE-----
++MIIDszCCAhsCFGD5193whHQ2bVdzbaQfdf1gc4SkMA0GCSqGSIb3DQEBCwUAMBIx
++EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjMwWhcNNDAwMzI1MTMzMjMw
++WjAaMRgwFgYDVQQDDA91bmJvdW5kLWNvbnRyb2wwggGiMA0GCSqGSIb3DQEBAQUA
++A4IBjwAwggGKAoIBgQCy0Sn4/KHxcau1nvsDgWFiO9t4Hd0xrtDasZbGhvOUD2mv
++OEDVoKEC9S0I4C4z6sHo1M0HxhZ9kltAdrDIw2mYLvtyDq9ctgIZRAdnICqJ03Tj
++1EkXlPuPg3xWeK/XsuJF2vRLqzDLg9G8Scg89XjcRAdmoLmNW8ewqSEh+YEX8OPl
++NdHKl5rBZX+bmN5Tr/gdFgx/K0Fl07WY9e5YuHSWBtDhIJuCs5RUPeueIyTXJkht
++A6a+UMdip6SMNv+85bYrFRAsbThpkwnWZZ91vAbQIpCRKMhIQNnWXvq+EzqZMzR/
++Bdsi/IVn3I88KV7WYXlpKXFLZyYa4ZVJFBrDxcX197dr0AIklOvfcd1KnJpmQpEB
++TMtwqosKXfbd1oq/c3DABPTdOPiJAzjXoBIyiLJ8Vr/YZZMpBWN127wKtqvu0Zp/
++nGuWKDgMGasG91gDeePLvb9EPvWEvCaVGgIZTt1G4Ov07ooBJSbVGL3U41n+p2My
+++i/XmXa+PgZX4pdHTR0CAwEAATANBgkqhkiG9w0BAQsFAAOCAYEAd++Wen6l8Ifj
++4h3p/y16PhSsWJWuJ4wdNYy3/GM84S26wGjzlEEwiW76HpH6VJzPOiBAeWnFKE83
++hFyetEIxgJeIPbcs9ZP/Uoh8GZH9tRISBSN9Hgk2Slr9llo4t1H0g/XTgA5HqMQU
++9YydlBh43G7Vw3FVwh09OM6poNOGQKNc/tq2/QdKeUMtyBbLWpRmjH5XcCT35fbn
++ZiVOUldqSHD4kKrFO4nJYXZyipRbcXybsLiX9GP0GLemc3IgIvOXyJ2RPp06o/SJ
++pzlMlkcAfLJaSuEW57xRakhuNK7m051TKKzJzIEX+NFYOVdafFHS8VwGrYsdrFvD
++72tMfu+Fu55y3awdWWGc6YlaGogZiuMnJkvQphwgn+5qE/7CGEckoKEsH601rqIZ
++muaIc85+nEcHJeijd/ZlBN9zeltjFoMuqTUENgmv8+tUAdVm/UMY9Vjme6b43ydP
++uv6DS02+k9z8toxXworLiPr94BGaiGV1NxgwZKLZigYJt/Fi2Qte
++-----END CERTIFICATE-----
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_server.key
+@@ -0,0 +1,39 @@
++-----BEGIN RSA PRIVATE KEY-----
++MIIG5AIBAAKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI
++0x41iG32a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+Nqq
++GRS7XVQ24vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Z
++uh9MDgotaBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8K
++WaBe1ca4TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5
++FzUReSXZuTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xP
++q6O9UPj4+nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XL
++A5UoZgRzXgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP
++7kFZSngxdy1+A/bNAgMBAAECggGBALpTOIqQwVg4CFBylL/a8K1IWJTI/I65sklf
++XxYL7G7SB2HlEJ//z+E+F0+S4Vlao1vyLQ5QkgE82pAUB8FoMWvY1qF0Y8A5wtm6
++iZSGk4OLK488ZbT8Ii9i+AGKgPe2XbVxsJwj8N4k7Zooqec9hz73Up8ATEWJkRz7
++2u7oMGG4z91E0PULA64dOi3l/vOQe5w/Aa+CwVbAWtI05o7kMvQEBMDJn6C7CByo
++MB5op9wueJMnz7PM7hns+U7Dy6oE4ljuolJUy51bDzFWwoM54cRoQqLFNHd8JVQj
++WxldCkbfF43iyprlsEcUrTyUjtdA+ZeiG39vg/mtdmgNpGmdupHJZQvSuG8IcVlz
++O+eMSeQS1QXPD6Ik8UK4SU0h+zOl8xIWtRrsxQuh4fnTN40udm/YUWl/6gOebsBI
++IrVLlKGqJSfB3tMjpCRqdTzJ0dA9keVpkqm2ugZkxEf1+/efq/rFIQ2pUBLCqNTN
++qpNqruK8y8FphP30I2uI4Ej2UIB8AQKBwQDd2Yptj2FyDyaXCycsyde0wYkNyzGU
++dRnzdibfHnMZwjgTjwAwgIUBVIS8H0/z7ZJQKN7osJfddMrtjJtYYUk9g/dCpHXs
++bNh2QSoWah3FdzNGuWd0iRf9+LFxhjAAMo/FS8zFJAJKrFsBdCGTfFUMdsLC0bjr
++YjiWBuvV72uKf8XIZX5KIZruKdWBBcWukcb21R1UDyFYyXRBsly5XHaIYKZql3km
++7pV7MKWO0IYgHbHIqGUqPQlzZ/lkunS1jKECgcEA23wHffD6Ou9/x3okPx2AWpTr
++gh8rgqbyo6hQkBW5Y90Wz824cqaYebZDaBR/xlVx/YwjKkohv8Bde2lpH/ZxRZ1Z
++5Sk2s6GJ/vU0L9RsJZgCgj4L6Coal1NMxuZtCXAlnOpiCdxSZgfqbshbTVz30KsG
++ZJG361Cua1ScdAHxlZBxT52/1Sm0zRC2hnxL7h4qo7Idmtzs40LAJvYOKekR0pPN
++oWeJfra7vgx/jVNvMFWoOoSLpidVO4g+ot4ery6tAoHAdW3rCic1C2zdnmH28Iw+
++s50l8Lk3mz+I5wgJd1zkzCO0DxZIoWPGA3g7cmCYr6N3KRsZMs4W9NAXgjpFGDkW
++zYsG3K21BdpvkdjYcFjnPVjlOXB2RIc0vehf9Jl02wXoeCSxVUDEPcaRvWk9RJYx
++ZpGOchUU7vNkxHURbIJ4yCzuAi9G8/Jp0dsu+kaV5tufF5SjG5WOrzKjaQsCbdN1
++oqaWMCHRrTvov/Z2C+xwsptFOdN5CSyZzg6hQiI4GMlBAoHAXyb6KINcOEi0YMp3
++BFXJ23tMTnEs78tozcKeipigcsbaqORK3omS+NEnj+uzKUzJyl4CsMbKstK2tFYS
++mSTCHqgE3PBtIpsZtEqhgUraR8IK9GPpzZDTTl9ynZgwFTNlWw3RyuyVXF56J+T8
++kCGJ3hEHCHqT/ZRQyX85BKIDFhA0z4tYKxWVqIFiYBNq56R0X9tMMmMs36mEnF93
++7Ht6mowxTZQRa7nU0qOgeKh/P7ki4Zus3y+WJ+T9IqahLtlRAoHBAIhqMrcxSAB8
++RpB9jukJlAnidw2jCMPgrFE8tP0khhVvGrXMldxAUsMKntDIo8dGCnG1KTcWDI0O
++jepvSPHSsxVLFugL79h0eVIS5z4huW48i9xgU8VlHdgAcgEPIAOFcOw2BCu/s0Vp
++O+MM/EyUOdo3NsibB3qc/GJI6iNBYS7AljYEVo6rXo5V/MZvZUF4vClen6Obzsre
++MTTb+4sJjfqleWuvr1XNMeu2mBfXBQkWGZP1byBK0MvD/aQ2PWq92A==
++-----END RSA PRIVATE KEY-----
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_server.pem
+@@ -0,0 +1,22 @@
++-----BEGIN CERTIFICATE-----
++MIIDqzCCAhMCFBHWXeQ6ZIa9QcQbXLFfC6tj+KA+MA0GCSqGSIb3DQEBCwUAMBIx
++EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjI5WhcNNDAwMzI1MTMzMjI5
++WjASMRAwDgYDVQQDDAd1bmJvdW5kMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
++igKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI0x41iG32
++a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+NqqGRS7XVQ2
++4vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Zuh9MDgot
++aBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8KWaBe1ca4
++TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5FzUReSXZ
++uTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xPq6O9UPj4
+++nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XLA5UoZgRz
++XgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP7kFZSngx
++dy1+A/bNAgMBAAEwDQYJKoZIhvcNAQELBQADggGBABunf93MKaCUHiZgnoOTinsW
++84/EgInrgtKzAyH+BhnKkJOhhR0kkIAx5d9BpDlaSiRTACFon9moWCgDIIsK/Ar7
++JE0Kln9cV//wiiNoFU0O4mnzyGUIMvlaEX6QHMJJQYvL05+w/3AAcf5XmMJtR5ca
++fJ8FqvGC34b2WxX9lTQoyT52sRt+1KnQikiMEnEyAdKktMG+MwKsFDdOwDXyZhZg
++XZhRrfX3/NVJolqB6EahjWIGXDeKuSSKZVtCyib6LskyeMzN5lcRfvubKDdlqFVF
++qlD7rHBsKhQUWK/IO64mGf7y/de+CgHtED5vDvr/p2uj/9sABATfbrOQR3W/Of25
++sLBj4OEfrJ7lX8hQgFaxkMI3x6VFT3W8dTCp7xnQgb6bgROWB5fNEZ9jk/gjSRmD
++yIU+r0UbKe5kBk/CmZVFXL2TyJ92V5NYEQh8V4DGy19qZ6u/XKYyNJL4ocs35GGe
++CA8SBuyrmdhx38h1RHErR2Skzadi1S7MwGf1y431fQ==
++-----END CERTIFICATE-----
+--- /dev/null
++++ contrib/unbound/testdata/iter_cname_minimise_nx.rpl
+@@ -0,0 +1,246 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: yes
++ module-config: "validator iterator"
++ trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b"
++ val-override-date: "20070916134226"
++ fake-sha1: yes
++ trust-anchor-signaling: no
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test cname chain resolution of nxdomain with qname minimisation.
++; the qtype CNAME lookup has NXDOMAIN.
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.44
++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.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++ns.example.com. IN A
++SECTION ANSWER
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++ns.example.com. IN AAAA
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NXDOMAIN
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. 300 IN SOA a. b. 1 2 3 4 300
++example.com. 300 IN RRSIG SOA 3 2 300 20070926134150 20070829134150 2854 example.com. AFPx1ZhcHixnxfB90ha4zgp7A+EdM8L63tUnVdlI5B14NiRIXONPDB4=
++v.example.com. IN NSEC x.example.com. A AAAA RRSIG NSEC
++v.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. AFT0Ao01lUN8Ppa9QPayQIN9ZtNIj4TzyhUQV31+FhNRK5uSQhiVwMc=
++example.com. 3600 IN NSEC abc.example.com. NS SOA RRSIG NSEC DNSKEY
++example.com. 3600 IN RRSIG NSEC 3 2 3600 20070926134150 20070829134150 2854 example.com. ABEOu6iietfjKY1MS0TutZZxUtRYA6XKsC1rMTrenwBF2darY3/Emco=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NXDOMAIN
++SECTION QUESTION
++c.example.com. IN A
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++SECTION AUTHORITY
++example.com. 300 IN SOA a. b. 1 2 3 4 300
++example.com. 300 IN RRSIG SOA 3 2 300 20070926134150 20070829134150 2854 example.com. AFPx1ZhcHixnxfB90ha4zgp7A+EdM8L63tUnVdlI5B14NiRIXONPDB4=
++v.example.com. IN NSEC x.example.com. A AAAA RRSIG NSEC
++v.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. AFT0Ao01lUN8Ppa9QPayQIN9ZtNIj4TzyhUQV31+FhNRK5uSQhiVwMc=
++example.com. 3600 IN NSEC abc.example.com. NS SOA RRSIG NSEC DNSKEY
++example.com. 3600 IN RRSIG NSEC 3 2 3600 20070926134150 20070829134150 2854 example.com. ABEOu6iietfjKY1MS0TutZZxUtRYA6XKsC1rMTrenwBF2darY3/Emco=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++c.example.com. IN CNAME
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++c.example.com. IN CNAME
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++c.example.com. IN CNAME
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++ENTRY_END
++
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++c.example.com. IN CNAME
++ENTRY_END
++
++STEP 40 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++c.example.com. IN CNAME
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++ENTRY_END
++
++STEP 50 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++c.example.com. IN A
++ENTRY_END
++
++STEP 60 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NXDOMAIN
++SECTION QUESTION
++c.example.com. IN A
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++SECTION AUTHORITY
++example.com. 300 IN SOA a. b. 1 2 3 4 300
++example.com. 300 IN RRSIG SOA 3 2 300 20070926134150 20070829134150 2854 example.com. AFPx1ZhcHixnxfB90ha4zgp7A+EdM8L63tUnVdlI5B14NiRIXONPDB4=
++v.example.com. IN NSEC x.example.com. A AAAA RRSIG NSEC
++v.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. AFT0Ao01lUN8Ppa9QPayQIN9ZtNIj4TzyhUQV31+FhNRK5uSQhiVwMc=
++example.com. 3600 IN NSEC abc.example.com. NS SOA RRSIG NSEC DNSKEY
++example.com. 3600 IN RRSIG NSEC 3 2 3600 20070926134150 20070829134150 2854 example.com. ABEOu6iietfjKY1MS0TutZZxUtRYA6XKsC1rMTrenwBF2darY3/Emco=
++ENTRY_END
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_failreply.rpl
+@@ -0,0 +1,132 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ minimal-responses: no
++ log-servfail: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test iterator fail_reply report
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. IN NS ns2.example.net.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. IN AAAA ::1
++ns2.example.net. IN AAAA ::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example.net. IN A
++SECTION ANSWER
++ns2.example.net. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example.net. IN AAAA
++SECTION ANSWER
++ns2.example.net. IN AAAA ::1
++ENTRY_END
++
++RANGE_END
++
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.4
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR SERVFAIL
++SECTION QUESTION
++ns.example.com. IN A
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR SERVFAIL
++SECTION QUESTION
++ns.example.com. IN AAAA
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 20 CHECK_OUT_QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 21 TIMEOUT
++STEP 22 TIMEOUT
++STEP 23 TIMEOUT
++STEP 24 TIMEOUT
++STEP 25 TIMEOUT
++
++STEP 31 TIMEOUT
++STEP 32 TIMEOUT
++STEP 33 TIMEOUT
++STEP 34 TIMEOUT
++
++; recursion happens here.
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_ignore_empty.rpl
+@@ -0,0 +1,248 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ minimal-responses: no
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test ignore of an empty response.
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. IN NS ns2.example2.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example2.com. IN NS
++SECTION AUTHORITY
++example2.com. IN NS ns2.example2.com.
++SECTION ADDITIONAL
++ns2.example2.com. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++foo.com. IN NS
++SECTION AUTHORITY
++foo.com. IN NS ns.foo.com.
++SECTION ADDITIONAL
++ns.foo.com. IN A 1.2.3.5
++ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.4
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION ANSWER
++example.com. IN NS ns.example.com.
++example.com. IN NS ns2.example.net.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.example.com. IN A
++SECTION ANSWER
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.example.com. IN AAAA
++SECTION AUTHORITY
++example.com. IN SOA ns root 4 14400 3600 604800 3600
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++SECTION AUTHORITY
++SECTION ADDITIONAL
++ENTRY_END
++RANGE_END
++
++; ns2.example2.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.5
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example2.com. IN NS
++SECTION ANSWER
++example2.com. IN NS ns2.example2.com.
++SECTION ADDITIONAL
++ns2.example2.com. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example2.com. IN A
++SECTION ANSWER
++ns2.example2.com. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example2.com. IN AAAA
++SECTION AUTHORITY
++example2.com. IN SOA ns2 root 4 14400 3600 604800 3600
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN A 10.20.30.40
++ENTRY_END
++
++; foo.com
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++www.foo.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.foo.com. IN AAAA
++SECTION ANSWER
++SECTION AUTHORITY
++;foo.com. IN SOA ns2.foo.com root.foo.com 4 14400 3600 604800 3600
++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 all
++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
++
++; wait for pending nameserver lookups.
++STEP 20 TRAFFIC
++
++; Test that a nodata stays a nodata.
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.foo.com. IN A
++ENTRY_END
++
++STEP 40 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.foo.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_nat64.rpl
+@@ -0,0 +1,117 @@
++; config options
++server:
++ do-nat64: yes
++ target-fetch-policy: "0 0 0 0 0"
++
++stub-zone:
++ name: "."
++ stub-addr: 2001:db8::1
++CONFIG_END
++
++SCENARIO_BEGIN Test NAT64 transport for a v4-only server.
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8::1
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS FAKE.ROOT.
++SECTION ADDITIONAL
++FAKE.ROOT. IN AAAA 2001:db8::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++; replies from NS over "NAT64"
++
++RANGE_BEGIN 0 100
++ ADDRESS 64:ff9b::c000:0201
++
++; A over NAT64
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN A
++SECTION ANSWER
++ns.v4only. IN A 192.0.2.1
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++ENTRY_END
++
++; no AAAA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN AAAA
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION ANSWER
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test.v4only. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_nat64_prefix.rpl
+@@ -0,0 +1,119 @@
++; config options
++server:
++ do-nat64: yes
++ nat64-prefix: 2001:db8:1234::/96
++ target-fetch-policy: "0 0 0 0 0"
++ do-ip4: no
++
++stub-zone:
++ name: "."
++ stub-addr: 2001:db8::1
++CONFIG_END
++
++SCENARIO_BEGIN Test NAT64 transport for a v4-only server, custom NAT64 prefix.
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8::1
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS FAKE.ROOT.
++SECTION ADDITIONAL
++FAKE.ROOT. IN AAAA 2001:db8::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++; replies from NS over "NAT64"
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8:1234::c000:0201
++
++; A over NAT64
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN A
++SECTION ANSWER
++ns.v4only. IN A 192.0.2.1
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++ENTRY_END
++
++; no AAAA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN AAAA
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION ANSWER
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test.v4only. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_nat64_prefix48.rpl
+@@ -0,0 +1,118 @@
++; config options
++server:
++ do-nat64: yes
++ nat64-prefix: 2001:db8:2345::/48
++ target-fetch-policy: "0 0 0 0 0"
++
++stub-zone:
++ name: "."
++ stub-addr: 2001:db8::1
++CONFIG_END
++
++SCENARIO_BEGIN Test NAT64 transport, this time with /48 NAT64 prefix.
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8::1
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS FAKE.ROOT.
++SECTION ADDITIONAL
++FAKE.ROOT. IN AAAA 2001:db8::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++; replies from NS over "NAT64"
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8:2345:c000:0002:0100::
++
++; A over NAT64
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN A
++SECTION ANSWER
++ns.v4only. IN A 192.0.2.1
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++ENTRY_END
++
++; no AAAA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN AAAA
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION ANSWER
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test.v4only. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_scrub_rr_length.rpl
+@@ -0,0 +1,298 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ minimal-responses: no
++ rrset-roundrobin: no
++ ede: yes
++ log-servfail: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test scrub of RRs of inappropriate length
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 200
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 200
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 200
++ 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 AA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN A 10.20.30.40
++www.example.com. IN A \# 3 030405
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++www.example.com. IN AAAA
++SECTION ANSWER
++www.example.com. IN AAAA 2001:db8::1234
++www.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++broken1.example.com. IN A
++SECTION ANSWER
++broken1.example.com. IN A \# 3 030405
++broken1.example.com. IN A \# 3 030406
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++broken1.example.com. IN AAAA
++SECTION ANSWER
++broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F
++broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E30
++broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E31
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++broken2.example.com. IN A
++SECTION ANSWER
++broken2.example.com. IN A 1.2.3.4
++broken2.example.com. IN A \# 3 030405
++broken2.example.com. IN A 1.2.3.5
++broken2.example.com. IN A \# 3 030406
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A \# 3 030407
++ns.example.com. IN A 1.2.3.6
++ns.example.com. IN A \# 3 030408
++ns.example.com. IN A \# 3 030409
++ns.example.com. IN A 1.2.3.7
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN AAAA
++ENTRY_END
++
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN AAAA
++SECTION ANSWER
++www.example.com. IN AAAA 2001:db8::1234
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++STEP 40 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++broken1.example.com. IN A
++ENTRY_END
++
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++broken1.example.com. IN A
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++STEP 60 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++broken1.example.com. IN AAAA
++ENTRY_END
++
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++broken1.example.com. IN AAAA
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++STEP 80 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++broken2.example.com. IN A
++ENTRY_END
++
++STEP 90 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++broken2.example.com. IN A
++SECTION ANSWER
++broken2.example.com. IN A 1.2.3.4
++broken2.example.com. IN A 1.2.3.5
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.6
++ns.example.com. IN A 1.2.3.7
++ENTRY_END
++
++STEP 100 QUERY
++ENTRY_BEGIN
++REPLY RD CD DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 110 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=0
++REPLY QR RD CD RA DO 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.6
++ns.example.com. IN A 1.2.3.7
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.conf
+@@ -0,0 +1,34 @@
++server:
++ verbosity: 7
++ # num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ do-not-query-localhost: no
++ # for the test, so that DNSSEC verification works.
++ #val-override-date: 20230929090000
++ trust-anchor: ". DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"
++
++remote-control:
++ control-enable: yes
++ control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
++ control-use-cert: no
++
++# for the test, an upstream server in the test setup.
++stub-zone:
++ name: "."
++ stub-addr: 127.0.0.1@@TOPORT@
++
++# hyperlocal root zone
++auth-zone:
++ name: "."
++ fallback-enabled: yes
++ for-downstream: no
++ for-upstream: yes
++ zonefile: "root.zone"
++ zonemd-check: yes
++ zonemd-reject-absence: yes
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.dsc
+@@ -0,0 +1,16 @@
++BaseName: root_zonemd
++Version: 1.0
++Description: ZONEMD check for root zone
++CreationDate: Fri 29 Sep 09:00:00 CEST 2023
++Maintainer: dr. W.C.A. Wijngaards
++Category:
++Component:
++CmdDepends:
++Depends:
++Help:
++Pre: root_zonemd.pre
++Post: root_zonemd.post
++Test: root_zonemd.test
++AuxFiles:
++Passed:
++Failure:
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.post
+@@ -0,0 +1,14 @@
++# #-- root_zonemd.post --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# source the test var file when it's there
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++#
++# do your teardown here
++. ../common.sh
++echo "> cat logfiles"
++cat fwd.log
++cat unbound.log
++kill_pid $FWD_PID
++kill_pid $UNBOUND_PID
++rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.pre
+@@ -0,0 +1,50 @@
++# #-- root_zonemd.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++. ../common.sh
++
++# attempt to download the root zone
++from=k.root-servers.net
++dig @$from . AXFR > root.txt
++if test $? -ne 0; then
++ echo "could not fetch root zone"
++ skip_test "could not fetch root zone"
++fi
++grep " SOA " root.txt | head -1 > root.soa
++cat root.soa >> root.zone
++grep -v " SOA " root.txt >> root.zone
++echo "fetched root.zone"
++ls -l root.zone
++cat root.soa
++
++get_random_port 2
++UNBOUND_PORT=$RND_PORT
++FWD_PORT=$(($RND_PORT + 1))
++echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
++echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
++
++# start forwarder
++get_ldns_testns
++$LDNS_TESTNS -p $FWD_PORT root_zonemd.testns >fwd.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++
++# make config file
++CONTROL_PATH=/tmp
++CONTROL_PID=$$
++sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < root_zonemd.conf > ub.conf
++# start unbound in the background
++PRE="../.."
++$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
++echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
++
++cat .tpkg.var.test
++wait_ldns_testns_up fwd.log
++wait_unbound_up unbound.log
++
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.test
+@@ -0,0 +1,51 @@
++# #-- root_zonemd.test --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++# do the test
++echo "> dig www.example.com."
++dig @localhost -p $UNBOUND_PORT . SOA | tee outfile
++echo "> check answer"
++if grep root-servers outfile | grep "nstld.verisign-grs.com"; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++echo "> unbound-control status"
++$PRE/unbound-control -c ub.conf status
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++else
++ echo "exit value: OK"
++fi
++
++# This is the output when an unsupported algorithm is used.
++if grep "auth zone . ZONEMD unsupported algorithm" unbound.log; then
++ echo "OK"
++else
++ echo "ZONEMD verification not OK"
++ exit 1
++fi
++
++echo "> unbound-control auth_zone_reload ."
++$PRE/unbound-control -c ub.conf auth_zone_reload . 2>&1 | tee outfile
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++fi
++# The output of the reload can be checked.
++#echo "> check unbound-control output"
++#if grep "example.com: ZONEMD verification successful" outfile; then
++ #echo "OK"
++#else
++ #echo "Not OK"
++ #exit 1
++#fi
++
++exit 0
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.testns
+@@ -0,0 +1,9 @@
++# reply to everything
++ENTRY_BEGIN
++MATCH opcode
++ADJUST copy_id copy_query
++REPLY QR SERVFAIL
++SECTION QUESTION
++example.com. IN SOA
++SECTION ANSWER
++ENTRY_END
+--- /dev/null
++++ contrib/unbound/testdata/rpz_cached_cname.rpl
+@@ -0,0 +1,122 @@
++; config options
++server:
++ module-config: "respip validator iterator"
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: no
++ rrset-roundrobin: no
++ access-control: 192.0.0.0/8 allow
++
++rpz:
++ name: "rpz.example.com"
++ rpz-log: yes
++ rpz-log-name: "rpz.example.com"
++ zonefile:
++TEMPFILE_NAME rpz.example.com
++TEMPFILE_CONTENTS rpz.example.com
++rpz.example.com. 3600 IN SOA ns.rpz.example.com. hostmaster.rpz.example.com. 1 3600 900 86400 3600
++rpz.example.com. 3600 IN NS ns.rpz.example.net.
++a.foo.rpz.example.com. 120 IN A 10.99.99.99
++TEMPFILE_END
++
++stub-zone:
++ name: "."
++ stub-addr: 10.20.30.40
++
++CONFIG_END
++
++SCENARIO_BEGIN Test RPZ with cached CNAME to A record
++
++RANGE_BEGIN 0 100
++ ADDRESS 10.20.30.40
++
++ENTRY_BEGIN
++MATCH opcode qname qtype
++ADJUST copy_id
++REPLY QR NOERROR AA
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS ns.
++SECTION ADDITIONAL
++ns. IN NS 10.20.30.40
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qname qtype
++ADJUST copy_id
++REPLY QR NOERROR AA
++SECTION QUESTION
++b.foo. IN A
++SECTION ANSWER
++b.foo. 30 CNAME a.foo.
++a.foo. 30 A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qname qtype
++ADJUST copy_id
++REPLY QR NOERROR AA
++SECTION QUESTION
++a.foo. IN A
++SECTION ANSWER
++a.foo. A 1.2.3.4
++ENTRY_END
++
++RANGE_END
++
++STEP 10 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++a.foo. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++a.foo. IN A
++SECTION ANSWER
++a.foo. 120 A 10.99.99.99
++ENTRY_END
++
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++b.foo. IN A
++ENTRY_END
++
++STEP 40 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++b.foo. IN A
++SECTION ANSWER
++b.foo. 30 CNAME a.foo.
++a.foo. 120 A 10.99.99.99
++ENTRY_END
++
++STEP 50 TIME_PASSES ELAPSE 3
++
++STEP 60 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++b.foo. IN A
++ENTRY_END
++
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++b.foo. IN A
++SECTION ANSWER
++b.foo. 30 CNAME a.foo.
++a.foo. 120 A 10.99.99.99
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_0ttl_nodata.rpl
+@@ -0,0 +1,154 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with NXDOMAIN followed by 0 TTL
++; Scenario overview:
++; - query for 0ttl.example.com. IN A
++; - answer from upstream is NODATA; will be cached for the SOA negative TTL.
++; - check that the client gets the NODATA; also cached
++; - query again right after the TTL expired
++; - this time the server answers with a 0 TTL RRset
++; - check that we get the correct answer
++
++; ns.example.com.
++RANGE_BEGIN 0 20
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 30 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the NODATA (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NODATA
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Wait for the NXDOMAIN to expire
++STEP 31 TIME_PASSES ELAPSE 32
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NODATA
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 60 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we got the correct answer
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_0ttl_nxdomain.rpl
+@@ -0,0 +1,154 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with NXDOMAIN followed by 0 TTL
++; Scenario overview:
++; - query for 0ttl.example.com. IN A
++; - answer from upstream is NXDOMAIN; will be cached for the SOA negative TTL.
++; - check that the client gets the NXDOMAIN; also cached
++; - query again right after the TTL expired
++; - this time the server answers with a 0 TTL RRset
++; - check that we get the correct answer
++
++; ns.example.com.
++RANGE_BEGIN 0 20
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 30 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NXDOMAIN
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Wait for the NXDOMAIN to expire
++STEP 31 TIME_PASSES ELAPSE 32
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NXDOMAIN
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 60 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we got the correct answer
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_0ttl_servfail.rpl
+@@ -0,0 +1,129 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with SERVFAIL followed by 0 TTL
++; Scenario overview:
++; - query for 0ttl.example.com. IN A
++; - answer from upstream is SERVFAIL; will be cached for NORR_TTL(5)
++; - check that the client gets the SERVFAIL; also cached
++; - query again right after the TTL expired
++; - this time the server answers with a 0 TTL RRset
++; - check that we get the correct answer
++
++; ns.example.com.
++RANGE_BEGIN 0 20
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA SERVFAIL
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 30 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached SERVFAIL
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Wait for the SERVFAIL to expire
++STEP 31 TIME_PASSES ELAPSE 32
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we got the correct answer
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_cached_servfail_refresh.rpl
+@@ -0,0 +1,145 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ serve-expired-reply-ttl: 123
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with client-timeout and a SERVFAIL upstream reply
++; Scenario overview:
++; - query for example.com. IN A
++; - answer from upstream is SERVFAIL; will be cached for NORR_TTL(5)
++; - check that the client gets the SERVFAIL; also cached
++; - query again right after the TTL expired
++; - cached SERVFAIL should be ignored and upstream queried
++; - answer from upstream is still SERVFAIL; the cached error response will be
++; refreshed for another NORR_TTL(5)
++; - check that the client gets the SERVFAIL
++; - query again; the upstream now has the answer available
++; - check that we get the refreshed cached response instead
++
++; ns.example.com.
++RANGE_BEGIN 0 50
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 60 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. IN A
++ SECTION ANSWER
++ example.com. 10 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the cached SERVFAIL
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Wait for the SERVFAIL to expire
++STEP 31 TIME_PASSES ELAPSE 6
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be refreshed)
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Query again, upstream has the real answer available
++STEP 60 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the refreshed cached SERVFAIL
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/stat_values.tdir/stat_values_cachedb.conf
+@@ -0,0 +1,36 @@
++server:
++ verbosity: 5
++ module-config: "cachedb iterator"
++ serve-expired: yes
++ num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ do-not-query-localhost: no
++ extended-statistics: yes
++ identity: "stat_values"
++ outbound-msg-retry: 0
++ root-key-sentinel: no
++ trust-anchor-signaling: no
++
++ local-zone: local.zone static
++ local-data: "www.local.zone A 192.0.2.1"
++remote-control:
++ control-enable: yes
++ control-interface: 127.0.0.1
++ # control-interface: ::1
++ control-port: @CONTROL_PORT@
++ server-key-file: "unbound_server.key"
++ server-cert-file: "unbound_server.pem"
++ control-key-file: "unbound_control.key"
++ control-cert-file: "unbound_control.pem"
++forward-zone:
++ name: "."
++ forward-addr: "127.0.0.1@@TOPORT@"
++forward-zone:
++ name: "expired."
++ forward-addr: "127.0.0.1@@EXPIREDPORT@"
+--- /dev/null
++++ contrib/unbound/testdata/stat_values.tdir/stat_values_downstream_cookies.conf
+@@ -0,0 +1,32 @@
++server:
++ verbosity: 5
++ module-config: "iterator"
++ num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ extended-statistics: yes
++ identity: "stat_values"
++ outbound-msg-retry: 0
++ root-key-sentinel: no
++ trust-anchor-signaling: no
++
++ local-zone: local.zone static
++ local-data: "www.local.zone A 192.0.2.1"
++
++ answer-cookie: yes
++ access-control: 127.0.0.1 allow_cookie
++
++remote-control:
++ control-enable: yes
++ control-interface: 127.0.0.1
++ # control-interface: ::1
++ control-port: @CONTROL_PORT@
++ server-key-file: "unbound_server.key"
++ server-cert-file: "unbound_server.pem"
++ control-key-file: "unbound_control.key"
++ control-cert-file: "unbound_control.pem"
+--- /dev/null
++++ contrib/unbound/testdata/subnet_cached_ede.crpl
+@@ -0,0 +1,114 @@
++; Ask the same question twice. Check to see second is answered
++; from cache
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ send-client-subnet: 1.2.3.4
++ max-client-subnet-ipv4: 17
++ module-config: "subnetcache validator iterator"
++ verbosity: 3
++ qname-minimisation: no
++ minimal-responses: no
++ ede: yes
++ val-log-level: 2
++ trust-anchor: "example.nl. DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29B22446B1"
++
++stub-zone:
++ name: "example.nl"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test subnetcache support for caching EDEs.
++
++; ns.example.com.
++RANGE_BEGIN 0 10
++ ADDRESS 1.2.3.4
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN DNSKEY
++SECTION ANSWER
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 11 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN A
++SECTION ANSWER
++example.nl. IN A 1.2.3.4
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 11 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ENTRY_END
++RANGE_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; get the entry in cache.
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ 00 08 00 07 ; OPC, optlen
++ 00 01 11 00 ; ip4, scope 17, source 0
++ 7f 00 00 ; 127.0.0.0/17
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; get the answer for it
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++; query again for the cached entry
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ 00 08 00 07 ; OPC, optlen
++ 00 01 11 00 ; ip4, scope 17, source 0
++ 7f 00 00 ; 127.0.0.0/17
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; this must be a cached answer since stub is not answering in this range
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_global_prefetch.crpl
+@@ -0,0 +1,236 @@
++; Check if the prefetch option works properly for messages stored in the global
++; cache for non-ECS clients. The prefetch query needs to result in an ECS
++; outgoing query based on the client's IP.
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ send-client-subnet: 1.2.3.4
++ max-client-subnet-ipv4: 21
++ module-config: "subnetcache iterator"
++ verbosity: 3
++ access-control: 127.0.0.1 allow_snoop
++ qname-minimisation: no
++ minimal-responses: no
++ prefetch: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test prefetch option for global cache with ECS enabled
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ . IN NS
++ SECTION ANSWER
++ . IN NS K.ROOT-SERVERS.NET.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ com. IN NS
++ SECTION ANSWER
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 10
++ 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 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
++
++; ns.example.com.
++RANGE_BEGIN 11 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id copy_ednsdata_assume_clientsubnet
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 IN A 10.20.30.40
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 15 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ 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
++
++; This answer should be in the global cache (because no ECS from upstream)
++STEP 2 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++; Try to trigger a prefetch
++STEP 3 TIME_PASSES ELAPSE 9
++
++STEP 11 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the global cache and a prefetch was triggered.
++STEP 12 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 1 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3591 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3591 IN A 1.2.3.4
++ENTRY_END
++
++; Allow time to pass so that the global cache record is expired.
++STEP 13 TIME_PASSES ELAPSE 2
++
++; Query again to verify that the record was prefetched and stored in the ECS
++; cache.
++STEP 15 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the ECS cache.
++STEP 16 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 8 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3598 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3598 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_global_prefetch_always_forward.crpl
+@@ -0,0 +1,167 @@
++; Check if the prefetch option works properly when serve-expired is combined
++; with client-subnet-always-forward for non-ECS clients. The prefetch query
++; needs to result in an outgoing query without ECS.
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ serve-expired: yes
++ client-subnet-always-forward: yes
++ module-config: "subnetcache iterator"
++ verbosity: 3
++ access-control: 127.0.0.1 allow_snoop
++ qname-minimisation: no
++ minimal-responses: no
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired and client-subnet-always-forward without ECS in the request
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ . IN NS
++ SECTION ANSWER
++ . IN NS K.ROOT-SERVERS.NET.
++ SECTION ADDITIONAL
++ K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ com. IN NS
++ SECTION ANSWER
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; 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
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 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
++
++; This answer should be in the global cache
++STEP 2 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++; Wait for the TTL to expire
++STEP 3 TIME_PASSES ELAPSE 20
++
++STEP 11 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the global cache and a prefetch was triggered
++STEP 12 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 30 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3580 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3580 IN A 1.2.3.4
++ENTRY_END
++
++STEP 13 CHECK_OUT_QUERY
++ENTRY_BEGIN
++ MATCH all
++ REPLY NOERROR DO
++ SECTION QUESTION
++ www.example.com. IN A
++ENTRY_END
++
++STEP 14 TRAFFIC
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_global_prefetch_expired.crpl
+@@ -0,0 +1,241 @@
++; Check if the prefetch option works properly for messages stored in the global
++; cache for non-ECS clients. The prefetch query needs to result in an ECS
++; outgoing query based on the client's IP.
++; Prefetch initiated via serve-expired.
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ send-client-subnet: 1.2.3.4
++ max-client-subnet-ipv4: 21
++ module-config: "subnetcache iterator"
++ verbosity: 3
++ access-control: 127.0.0.1 allow_snoop
++ qname-minimisation: no
++ minimal-responses: no
++ serve-expired: yes
++ serve-expired-ttl: 1
++ prefetch: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test prefetch option for global cache with ECS enabled (initiated via serve-expired)
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ . IN NS
++ SECTION ANSWER
++ . IN NS K.ROOT-SERVERS.NET.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ com. IN NS
++ SECTION ANSWER
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 10
++ 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 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
++
++; ns.example.com.
++RANGE_BEGIN 11 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id copy_ednsdata_assume_clientsubnet
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 IN A 10.20.30.40
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 15 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ 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
++
++; This answer should be in the global cache (because no ECS from upstream)
++STEP 2 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++; Try to trigger a prefetch with expired data
++STEP 3 TIME_PASSES ELAPSE 11
++
++STEP 11 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This expired record came from the global cache and a prefetch is triggered.
++STEP 12 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 30 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3589 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3589 IN A 1.2.3.4
++ENTRY_END
++
++;STEP 13 TRAFFIC
++; Allow enough time to pass so that the expired record from the global cache
++; cannot be used anymore.
++STEP 14 TIME_PASSES ELAPSE 1
++
++; Query again to verify that the record was prefetched and stored in the ECS
++; cache.
++STEP 15 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the ECS cache.
++STEP 16 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 9 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3599 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3599 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_prezero.crpl
+@@ -0,0 +1,155 @@
++; subnet unit test
++server:
++ trust-anchor-signaling: no
++ send-client-subnet: 1.2.3.4
++ send-client-subnet: 1.2.3.5
++ target-fetch-policy: "0 0 0 0 0"
++ module-config: "subnetcache validator iterator"
++ qname-minimisation: no
++ minimal-responses: no
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test subnetcache source prefix zero from client.
++; In RFC7871 section-7.1.2 (para. 2).
++; It says that the recursor must send no EDNS subnet or its own address
++; in the EDNS subnet to the upstream server. And use that answer for the
++; source prefix length zero query. That type of query is for privacy.
++; The authority server is then going to use the resolver's IP, if any, to
++; tailor the answer to the query source address.
++
++; ns.example.com
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.4
++
++; reply with 0.0.0.0/0 in reply
++; For the test the answers for 0.0.0.0/0 queries are SERVFAIL, the normal
++; answers are NOERROR.
++ENTRY_BEGIN
++MATCH opcode qtype qname ednsdata
++ADJUST copy_id
++REPLY QR AA DO SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN CNAME star.c10r.example.com.
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++; reply without subnet
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN CNAME star.c10r.example.com.
++ENTRY_END
++
++; delegation answer for c10r.example.com, with subnet /0
++ENTRY_BEGIN
++MATCH opcode subdomain ednsdata
++ADJUST copy_id copy_query
++REPLY QR DO SERVFAIL
++SECTION QUESTION
++c10r.example.com. IN NS
++SECTION AUTHORITY
++c10r.example.com. IN NS ns.c10r.example.com.
++SECTION ADDITIONAL
++ns.c10r.example.com. IN A 1.2.3.5
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++; delegation answer for c10r.example.com, without subnet
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR DO NOERROR
++SECTION QUESTION
++c10r.example.com. IN NS
++SECTION AUTHORITY
++c10r.example.com. IN NS ns.c10r.example.com.
++SECTION ADDITIONAL
++ns.c10r.example.com. IN A 1.2.3.5
++ENTRY_END
++RANGE_END
++
++; ns.c10r.example.com
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.5
++
++; reply with 0.0.0.0/0 in reply
++ENTRY_BEGIN
++MATCH opcode qtype qname ednsdata
++ADJUST copy_id
++REPLY QR AA DO SERVFAIL
++SECTION QUESTION
++star.c10r.example.com. IN A
++SECTION ANSWER
++star.c10r.example.com. IN A 1.2.3.6
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++; reply without subnet
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA DO NOERROR
++SECTION QUESTION
++star.c10r.example.com. IN A
++SECTION ANSWER
++star.c10r.example.com. IN A 1.2.3.6
++ENTRY_END
++RANGE_END
++
++; ask for www.example.com
++; server answers with CNAME to a delegation, that then
++; returns a /24 answer.
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++www.example.com. IN A
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ednsdata
++REPLY QR RD RA DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN CNAME star.c10r.example.com.
++star.c10r.example.com. IN A 1.2.3.6
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/val_any_negcache.rpl
+@@ -0,0 +1,243 @@
++; config options
++; The island of trust is at example.com
++server:
++ trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b"
++ val-override-date: "20070916134226"
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ fake-sha1: yes
++ trust-anchor-signaling: no
++ rrset-roundrobin: no
++ aggressive-nsec: yes
++ harden-unknown-additional: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test validator with response to qtype ANY and negative cache.
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; 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.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response with NODATA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN LOC
++SECTION AUTHORITY
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++ENTRY_END
++
++; response to query of interest
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN ANY
++SECTION ANSWER
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJIIs70j+sDS/UT2QRp61SE7S3EEXopNXoFE73JLRmvpi/UrOO/Vz4Se6wXv/CYCKjGw06U4WRgRYXcpEhJROyNapmdIKSxhOzfLVE1gqA0PweZR8dtY3aNQSRn3sPpwJr6Mi/PqQKAMMrZ9ckJpf1+bQMOOvxgzz2U1GS18b3yZKcgTMEaJzd/GZYzi/BN2DzQ0MsrSwYXfsNLFOBbs8PJMW4LYIxeeOe6rUgkWOF7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFHq7BNVAeLW+Uw/rkjVS08lrMDk/AhR+bvChHfiE4jLb6uoyE54/irCuqA== ;{id = 2854}
++example.com. 600 IN NAPTR 20 0 "s" "SIP+D2U" "" _sip._udp.example.com.
++example.com. 600 IN RRSIG NAPTR 3 2 600 20070926134150 20070829134150 2854 example.com. MC0CFE8qs66bzuOyKmTIacamrmqabMRzAhUAn0MujX1LB0UpTHuLMgdgMgJJlq4= ;{id = 2854}
++example.com. 86400 IN AAAA 2001:7b8:206:1::1
++example.com. 86400 IN RRSIG AAAA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFEqS4WHyqhUkv7t42TsBZJk/Q9paAhUAtTZ8GaXGpot0PmsM0oGzQU+2iw4= ;{id = 2854}
++example.com. 86400 IN TXT "Stichting NLnet Labs"
++example.com. 86400 IN RRSIG TXT 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH3otn2u8zXczBS8L0VKpyAYZGSkAhQLGaQclkzMAzlB5j73opFjdkh8TA== ;{id = 2854}
++example.com. 86400 IN MX 100 v.net.example.
++example.com. 86400 IN MX 50 open.example.com.
++example.com. 86400 IN RRSIG MX 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFEKh3jeqh69zcOqWWv3GNKlMECPyAhR9HJkcPLqlyVWUccWDFJfGGcQfdg== ;{id = 2854}
++example.com. 86400 IN NS v.net.example.
++example.com. 86400 IN NS open.example.com.
++example.com. 86400 IN NS ns7.domain-registry.example.
++example.com. 86400 IN RRSIG NS 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCaRn30X4neKW7KYoTa2kcsoOLgfgIURvKEyDczLypWlx99KpxzMxRYhEc= ;{id = 2854}
++example.com. 86400 IN A 213.154.224.1
++example.com. 86400 IN RRSIG A 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH8kSLxmRTwzlGDxvF1e4y/gM+5dAhQkzyQ2a6Gf+CMaHzVScaUvTt9HhQ== ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++SECTION AUTHORITY
++SECTION ADDITIONAL
++ns7.domain-registry.example. 80173 IN A 62.4.86.230
++open.example.com. 600 IN A 213.154.224.1
++open.example.com. 600 IN AAAA 2001:7b8:206:1::53
++open.example.com. 600 IN AAAA 2001:7b8:206:1::1
++v.net.example. 28800 IN A 213.154.224.17
++v.net.example. 28800 IN AAAA 2001:7b8:206:1:200:39ff:fe59:b187
++johnny.example.com. 600 IN A 213.154.224.44
++open.example.com. 600 IN RRSIG A 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCh8bja923UJmg1+sYXMK8WIE4dpgIUQe9sZa0GOcUYSgb2rXoogF8af+Y= ;{id = 2854}
++open.example.com. 600 IN RRSIG AAAA 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCRGJgIS6kEVG7aJfovuG/q3cgOWwIUYEIFCnfRQlMIYWF7BKMQoMbdkE0= ;{id = 2854}
++johnny.example.com. 600 IN RRSIG A 3 3 600 20070926134150 20070829134150 2854 example.com. MCwCFAh0/zSpCd/9eMNz7AyfnuGQFD1ZAhQEpNFNw4XByNEcbi/vsVeii9kp7g== ;{id = 2854}
++_sip._udp.example.com. 600 IN RRSIG SRV 3 4 600 20070926134150 20070829134150 2854 example.com. MCwCFFSRVgOcq1ihVuO6MhCuzWs6SxpVAhRPHHCKy0JxymVkYeFOxTkbVSWMMw== ;{id = 2854}
++_sip._udp.example.com. 600 IN SRV 0 0 5060 johnny.example.com.
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++MATCH TCP
++REPLY RD DO
++SECTION QUESTION
++example.com. IN LOC
++ENTRY_END
++
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++example.com. IN LOC
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++ENTRY_END
++
++STEP 20 QUERY
++ENTRY_BEGIN
++MATCH TCP
++REPLY RD DO
++SECTION QUESTION
++example.com. IN ANY
++ENTRY_END
++
++; Allow validation resuming for the RRSIGs
++STEP 21 TIME_PASSES ELAPSE 0.05
++
++; recursion happens here.
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++example.com. IN ANY
++SECTION ANSWER
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJIIs70j+sDS/UT2QRp61SE7S3EEXopNXoFE73JLRmvpi/UrOO/Vz4Se6wXv/CYCKjGw06U4WRgRYXcpEhJROyNapmdIKSxhOzfLVE1gqA0PweZR8dtY3aNQSRn3sPpwJr6Mi/PqQKAMMrZ9ckJpf1+bQMOOvxgzz2U1GS18b3yZKcgTMEaJzd/GZYzi/BN2DzQ0MsrSwYXfsNLFOBbs8PJMW4LYIxeeOe6rUgkWOF7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFHq7BNVAeLW+Uw/rkjVS08lrMDk/AhR+bvChHfiE4jLb6uoyE54/irCuqA== ;{id = 2854}
++example.com. 600 IN NAPTR 20 0 "s" "SIP+D2U" "" _sip._udp.example.com.
++example.com. 600 IN RRSIG NAPTR 3 2 600 20070926134150 20070829134150 2854 example.com. MC0CFE8qs66bzuOyKmTIacamrmqabMRzAhUAn0MujX1LB0UpTHuLMgdgMgJJlq4= ;{id = 2854}
++example.com. 86400 IN AAAA 2001:7b8:206:1::1
++example.com. 86400 IN RRSIG AAAA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFEqS4WHyqhUkv7t42TsBZJk/Q9paAhUAtTZ8GaXGpot0PmsM0oGzQU+2iw4= ;{id = 2854}
++example.com. 86400 IN TXT "Stichting NLnet Labs"
++example.com. 86400 IN RRSIG TXT 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH3otn2u8zXczBS8L0VKpyAYZGSkAhQLGaQclkzMAzlB5j73opFjdkh8TA== ;{id = 2854}
++example.com. 86400 IN MX 100 v.net.example.
++example.com. 86400 IN MX 50 open.example.com.
++example.com. 86400 IN RRSIG MX 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFEKh3jeqh69zcOqWWv3GNKlMECPyAhR9HJkcPLqlyVWUccWDFJfGGcQfdg== ;{id = 2854}
++example.com. 86400 IN NS v.net.example.
++example.com. 86400 IN NS open.example.com.
++example.com. 86400 IN NS ns7.domain-registry.example.
++example.com. 86400 IN RRSIG NS 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCaRn30X4neKW7KYoTa2kcsoOLgfgIURvKEyDczLypWlx99KpxzMxRYhEc= ;{id = 2854}
++example.com. 86400 IN A 213.154.224.1
++example.com. 86400 IN RRSIG A 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH8kSLxmRTwzlGDxvF1e4y/gM+5dAhQkzyQ2a6Gf+CMaHzVScaUvTt9HhQ== ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++SECTION AUTHORITY
++SECTION ADDITIONAL
++open.example.com. 600 IN A 213.154.224.1
++open.example.com. 600 IN AAAA 2001:7b8:206:1::53
++open.example.com. 600 IN AAAA 2001:7b8:206:1::1
++open.example.com. 600 IN RRSIG A 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCh8bja923UJmg1+sYXMK8WIE4dpgIUQe9sZa0GOcUYSgb2rXoogF8af+Y= ;{id = 2854}
++open.example.com. 600 IN RRSIG AAAA 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCRGJgIS6kEVG7aJfovuG/q3cgOWwIUYEIFCnfRQlMIYWF7BKMQoMbdkE0= ;{id = 2854}
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/val_scrub_rr_length.rpl
+@@ -0,0 +1,164 @@
++; config options
++; The island of trust is at example.com
++server:
++ trust-anchor: "example.com. IN DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
++ val-override-date: "20070916134226"
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ trust-anchor-signaling: no
++ minimal-responses: no
++ rrset-roundrobin: no
++ ede: yes
++ log-servfail: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test validator with scrub of RR for inappropriate length
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; 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.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. IN DNSKEY 256 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55566 (zsk), size = 1024b}
++example.com. 3600 IN RRSIG DNSKEY 8 2 3600 20070926134150 20070829134150 55566 example.com. Ni7Q17l2dzKcAnHdU3Mycpdwo0I6qgGxRvBhBNI43xIUFHJpgKpbeMFxKvVTkbwHyMPMIuHmOaC82IBhOpGD10SExVh4erQhWS3Hvl+m4Cwl3WI9N+AW6CTB9yj+d4xzX3bHjjBt6MSk4bU8ABR7qIoAjgjY7zdtUDWQlaM+d18=
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.example.com. IN AAAA
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++
++; response to query of interest
++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
++www.example.com. IN A \# 5 0102030405
++; RRSIG includes the malformed record.
++www.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. W4WFu9B81uRvp3Dj8uLIscypznKWuLuKrZqVg1on5/45/3/xyjHvj3TjTL3gruWFXPiQpldvOstXLZ5eN3OpqILdkVey0eqVATujpHwIruY6GWztVx5WptmFfK6E6zzshZ3RmAARqq/czQ+IZli2A9xixdY2H0o1dSU6gohEjjE=
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; recursion happens here.
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=0
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++SCENARIO_END
+--- contrib/unbound/util/config_file.c.orig
++++ contrib/unbound/util/config_file.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -55,6 +55,7 @@
+ #include "util/regional.h"
+ #include "util/fptr_wlist.h"
+ #include "util/data/dname.h"
++#include "util/random.h"
+ #include "util/rtt.h"
+ #include "services/cache/infra.h"
+ #include "sldns/wire2str.h"
+@@ -87,7 +88,10 @@
+ /** init ports possible for use */
+ static void init_outgoing_availports(int* array, int num);
+
+-struct config_file*
++/** init cookie with random data */
++static void init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len);
++
++struct config_file*
+ config_create(void)
+ {
+ struct config_file* cfg;
+@@ -116,6 +120,7 @@
+ cfg->tcp_auth_query_timeout = 3 * 1000; /* 3s in millisecs */
+ cfg->do_tcp_keepalive = 0;
+ cfg->tcp_keepalive_timeout = 120 * 1000; /* 120s in millisecs */
++ cfg->sock_queue_timeout = 0; /* do not check timeout */
+ cfg->ssl_service_key = NULL;
+ cfg->ssl_service_pem = NULL;
+ cfg->ssl_port = UNBOUND_DNS_OVER_TLS_PORT;
+@@ -153,7 +158,7 @@
+ cfg->outgoing_num_ports = 48; /* windows is limited in num fds */
+ cfg->num_queries_per_thread = 24;
+ cfg->outgoing_num_tcp = 2; /* leaves 64-52=12 for: 4if,1stop,thread4 */
+- cfg->incoming_num_tcp = 2;
++ cfg->incoming_num_tcp = 2;
+ #endif
+ cfg->stream_wait_size = 4 * 1024 * 1024;
+ cfg->edns_buffer_size = 1232; /* from DNS flagday recommendation */
+@@ -233,6 +238,7 @@
+ cfg->harden_below_nxdomain = 1;
+ cfg->harden_referral_path = 0;
+ cfg->harden_algo_downgrade = 0;
++ cfg->harden_unknown_additional = 0;
+ cfg->use_caps_bits_for_id = 0;
+ cfg->caps_whitelist = NULL;
+ cfg->private_address = NULL;
+@@ -265,6 +271,7 @@
+ cfg->val_permissive_mode = 0;
+ cfg->aggressive_nsec = 1;
+ cfg->ignore_cd = 0;
++ cfg->disable_edns_do = 0;
+ cfg->serve_expired = 0;
+ cfg->serve_expired_ttl = 0;
+ cfg->serve_expired_ttl_reset = 0;
+@@ -300,14 +307,14 @@
+ cfg->minimal_responses = 1;
+ cfg->rrset_roundrobin = 1;
+ cfg->unknown_server_time_limit = 376;
+- cfg->max_udp_size = 4096;
+- if(!(cfg->server_key_file = strdup(RUN_DIR"/unbound_server.key")))
++ cfg->max_udp_size = 1232; /* value taken from edns_buffer_size */
++ if(!(cfg->server_key_file = strdup(RUN_DIR"/unbound_server.key")))
+ goto error_exit;
+- if(!(cfg->server_cert_file = strdup(RUN_DIR"/unbound_server.pem")))
++ if(!(cfg->server_cert_file = strdup(RUN_DIR"/unbound_server.pem")))
+ goto error_exit;
+- if(!(cfg->control_key_file = strdup(RUN_DIR"/unbound_control.key")))
++ if(!(cfg->control_key_file = strdup(RUN_DIR"/unbound_control.key")))
+ goto error_exit;
+- if(!(cfg->control_cert_file = strdup(RUN_DIR"/unbound_control.pem")))
++ if(!(cfg->control_cert_file = strdup(RUN_DIR"/unbound_control.pem")))
+ goto error_exit;
+
+ #ifdef CLIENT_SUBNET
+@@ -315,7 +322,7 @@
+ #else
+ if(!(cfg->module_conf = strdup("validator iterator"))) goto error_exit;
+ #endif
+- if(!(cfg->val_nsec3_key_iterations =
++ if(!(cfg->val_nsec3_key_iterations =
+ strdup("1024 150 2048 150 4096 150"))) goto error_exit;
+ #if defined(DNSTAP_SOCKET_PATH)
+ if(!(cfg->dnstap_socket_path = strdup(DNSTAP_SOCKET_PATH)))
+@@ -324,6 +331,7 @@
+ cfg->dnstap_bidirectional = 1;
+ cfg->dnstap_tls = 1;
+ cfg->disable_dnssec_lame_check = 0;
++ cfg->ip_ratelimit_cookie = 0;
+ cfg->ip_ratelimit = 0;
+ cfg->ratelimit = 0;
+ cfg->ip_ratelimit_slabs = 4;
+@@ -367,14 +375,22 @@
+ cfg->ipsecmod_whitelist = NULL;
+ cfg->ipsecmod_strict = 0;
+ #endif
++ cfg->do_answer_cookie = 0;
++ memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret));
++ cfg->cookie_secret_len = 16;
++ init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len);
+ #ifdef USE_CACHEDB
+ if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
+ if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
++ cfg->cachedb_no_store = 0;
+ #ifdef USE_REDIS
+ if(!(cfg->redis_server_host = strdup("127.0.0.1"))) goto error_exit;
++ cfg->redis_server_path = NULL;
++ cfg->redis_server_password = NULL;
+ cfg->redis_timeout = 100;
+ cfg->redis_server_port = 6379;
+ cfg->redis_expire_records = 0;
++ cfg->redis_logical_db = 0;
+ #endif /* USE_REDIS */
+ #endif /* USE_CACHEDB */
+ #ifdef USE_IPSET
+@@ -487,10 +503,10 @@
+ /* not supported, library must have 1 thread in bgworker */
+ return 0;
+ } else if(strcmp(opt, "outgoing-port-permit:") == 0) {
+- return cfg_mark_ports(val, 1,
++ return cfg_mark_ports(val, 1,
+ cfg->outgoing_avail_ports, 65536);
+ } else if(strcmp(opt, "outgoing-port-avoid:") == 0) {
+- return cfg_mark_ports(val, 0,
++ return cfg_mark_ports(val, 0,
+ cfg->outgoing_avail_ports, 65536);
+ } else if(strcmp(opt, "local-zone:") == 0) {
+ return cfg_parse_local_zone(cfg, val);
+@@ -504,7 +520,7 @@
+ if(atoi(val) == 0) return 0;
+ cfg->val_date_override = (uint32_t)atoi(val);
+ }
+- } else if(strcmp(opt, "local-data-ptr:") == 0) {
++ } else if(strcmp(opt, "local-data-ptr:") == 0) {
+ char* ptr = cfg_ptr_reverse((char*)opt);
+ return cfg_strlist_insert(&cfg->local_data, ptr);
+ } else if(strcmp(opt, "logfile:") == 0) {
+@@ -540,6 +556,7 @@
+ else S_NUMBER_NONZERO("tcp-reuse-timeout:", tcp_reuse_timeout)
+ else S_YNO("edns-tcp-keepalive:", do_tcp_keepalive)
+ else S_NUMBER_NONZERO("edns-tcp-keepalive-timeout:", tcp_keepalive_timeout)
++ else S_NUMBER_OR_ZERO("sock-queue-timeout:", sock_queue_timeout)
+ else S_YNO("ssl-upstream:", ssl_upstream)
+ else S_YNO("tls-upstream:", ssl_upstream)
+ else S_STR("ssl-service-key:", ssl_service_key)
+@@ -649,6 +666,7 @@
+ else S_YNO("harden-below-nxdomain:", harden_below_nxdomain)
+ else S_YNO("harden-referral-path:", harden_referral_path)
+ else S_YNO("harden-algo-downgrade:", harden_algo_downgrade)
++ else S_YNO("harden-unknown-additional:", harden_unknown_additional)
+ else S_YNO("use-caps-for-id:", use_caps_bits_for_id)
+ else S_STRLIST("caps-whitelist:", caps_whitelist)
+ else S_SIZET_OR_ZERO("unwanted-reply-threshold:", unwanted_threshold)
+@@ -675,6 +693,7 @@
+ else S_YNO("val-permissive-mode:", val_permissive_mode)
+ else S_YNO("aggressive-nsec:", aggressive_nsec)
+ else S_YNO("ignore-cd-flag:", ignore_cd)
++ else S_YNO("disable-edns-do:", disable_edns_do)
+ else if(strcmp(opt, "serve-expired:") == 0)
+ { IS_YES_OR_NO; cfg->serve_expired = (strcmp(val, "yes") == 0);
+ SERVE_EXPIRED = cfg->serve_expired; }
+@@ -684,7 +703,7 @@
+ else if(strcmp(opt, "serve-expired-reply-ttl:") == 0)
+ { IS_NUMBER_OR_ZERO; cfg->serve_expired_reply_ttl = atoi(val); SERVE_EXPIRED_REPLY_TTL=(time_t)cfg->serve_expired_reply_ttl;}
+ else S_NUMBER_OR_ZERO("serve-expired-client-timeout:", serve_expired_client_timeout)
+- else S_YNO("ede:", ede)
++ else S_YNO("ede:", ede)
+ else S_YNO("ede-serve-expired:", ede_serve_expired)
+ else S_YNO("serve-original-ttl:", serve_original_ttl)
+ else S_STR("val-nsec3-keysize-iterations:", val_nsec3_key_iterations)
+@@ -765,6 +784,10 @@
+ else S_POW2("dnscrypt-nonce-cache-slabs:",
+ dnscrypt_nonce_cache_slabs)
+ #endif
++ else if(strcmp(opt, "ip-ratelimit-cookie:") == 0) {
++ IS_NUMBER_OR_ZERO; cfg->ip_ratelimit_cookie = atoi(val);
++ infra_ip_ratelimit_cookie=cfg->ip_ratelimit_cookie;
++ }
+ else if(strcmp(opt, "ip-ratelimit:") == 0) {
+ IS_NUMBER_OR_ZERO; cfg->ip_ratelimit = atoi(val);
+ infra_ip_ratelimit=cfg->ip_ratelimit;
+@@ -800,6 +823,9 @@
+ { IS_NUMBER_OR_ZERO; cfg->ipsecmod_max_ttl = atoi(val); }
+ else S_YNO("ipsecmod-strict:", ipsecmod_strict)
+ #endif
++#ifdef USE_CACHEDB
++ else S_YNO("cachedb-no-store:", cachedb_no_store)
++#endif /* USE_CACHEDB */
+ else if(strcmp(opt, "define-tag:") ==0) {
+ return config_add_tag(cfg, val);
+ /* val_sig_skew_min, max and val_max_restart are copied into val_env
+@@ -812,7 +838,7 @@
+ { IS_NUMBER_OR_ZERO; cfg->val_max_restart = (int32_t)atoi(val); }
+ else if (strcmp(opt, "outgoing-interface:") == 0) {
+ char* d = strdup(val);
+- char** oi =
++ char** oi =
+ (char**)reallocarray(NULL, (size_t)cfg->num_out_ifs+1, sizeof(char*));
+ if(!d || !oi) { free(d); free(oi); return -1; }
+ if(cfg->out_ifs && cfg->num_out_ifs) {
+@@ -907,7 +933,7 @@
+ for(s=list; s; s=s->next)
+ total += strlen(s->str) + 1; /* len + newline */
+ left = total+1; /* one extra for nul at end */
+- r = malloc(left);
++ r = malloc(left);
+ if(!r)
+ return NULL;
+ w = r;
+@@ -986,7 +1012,7 @@
+ }
+
+ int
+-config_get_option(struct config_file* cfg, const char* opt,
++config_get_option(struct config_file* cfg, const char* opt,
+ void (*func)(char*,void*), void* arg)
+ {
+ char buf[1024], nopt[64];
+@@ -1062,6 +1088,7 @@
+ else O_DEC(opt, "tcp-reuse-timeout", tcp_reuse_timeout)
+ else O_YNO(opt, "edns-tcp-keepalive", do_tcp_keepalive)
+ else O_DEC(opt, "edns-tcp-keepalive-timeout", tcp_keepalive_timeout)
++ else O_DEC(opt, "sock-queue-timeout", sock_queue_timeout)
+ else O_YNO(opt, "ssl-upstream", ssl_upstream)
+ else O_YNO(opt, "tls-upstream", ssl_upstream)
+ else O_STR(opt, "ssl-service-key", ssl_service_key)
+@@ -1117,6 +1144,7 @@
+ else O_YNO(opt, "harden-below-nxdomain", harden_below_nxdomain)
+ else O_YNO(opt, "harden-referral-path", harden_referral_path)
+ else O_YNO(opt, "harden-algo-downgrade", harden_algo_downgrade)
++ else O_YNO(opt, "harden-unknown-additional", harden_unknown_additional)
+ else O_YNO(opt, "use-caps-for-id", use_caps_bits_for_id)
+ else O_LST(opt, "caps-whitelist", caps_whitelist)
+ else O_DEC(opt, "unwanted-reply-threshold", unwanted_threshold)
+@@ -1128,6 +1156,7 @@
+ else O_YNO(opt, "val-permissive-mode", val_permissive_mode)
+ else O_YNO(opt, "aggressive-nsec", aggressive_nsec)
+ else O_YNO(opt, "ignore-cd-flag", ignore_cd)
++ else O_YNO(opt, "disable-edns-do", disable_edns_do)
+ else O_YNO(opt, "serve-expired", serve_expired)
+ else O_DEC(opt, "serve-expired-ttl", serve_expired_ttl)
+ else O_YNO(opt, "serve-expired-ttl-reset", serve_expired_ttl_reset)
+@@ -1232,6 +1261,7 @@
+ else O_LST(opt, "python-script", python_script)
+ else O_LST(opt, "dynlib-file", dynlib_file)
+ else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check)
++ else O_DEC(opt, "ip-ratelimit-cookie", ip_ratelimit_cookie)
+ else O_DEC(opt, "ip-ratelimit", ip_ratelimit)
+ else O_DEC(opt, "ratelimit", ratelimit)
+ else O_MEM(opt, "ip-ratelimit-size", ip_ratelimit_size)
+@@ -1284,11 +1314,15 @@
+ #ifdef USE_CACHEDB
+ else O_STR(opt, "backend", cachedb_backend)
+ else O_STR(opt, "secret-seed", cachedb_secret)
++ else O_YNO(opt, "cachedb-no-store", cachedb_no_store)
+ #ifdef USE_REDIS
+ else O_STR(opt, "redis-server-host", redis_server_host)
+ else O_DEC(opt, "redis-server-port", redis_server_port)
++ else O_STR(opt, "redis-server-path", redis_server_path)
++ else O_STR(opt, "redis-server-password", redis_server_password)
+ else O_DEC(opt, "redis-timeout", redis_timeout)
+ else O_YNO(opt, "redis-expire-records", redis_expire_records)
++ else O_DEC(opt, "redis-logical-db", redis_logical_db)
+ #endif /* USE_REDIS */
+ #endif /* USE_CACHEDB */
+ #ifdef USE_IPSET
+@@ -1322,7 +1356,7 @@
+ init_cfg_parse();
+ }
+
+-int
++int
+ config_read(struct config_file* cfg, const char* filename, const char* chroot)
+ {
+ FILE *in;
+@@ -1362,7 +1396,7 @@
+ if(r == GLOB_NOMATCH) {
+ verbose(VERB_QUERY, "include: "
+ "no matches for %s", fname);
+- return 1;
++ return 1;
+ } else if(r == GLOB_NOSPACE) {
+ log_err("include: %s: "
+ "fnametern out of memory", fname);
+@@ -1561,7 +1595,7 @@
+ }
+ }
+
+-void
++void
+ config_delete(struct config_file* cfg)
+ {
+ if(!cfg) return;
+@@ -1638,6 +1672,7 @@
+ free(cfg->server_cert_file);
+ free(cfg->control_key_file);
+ free(cfg->control_cert_file);
++ free(cfg->nat64_prefix);
+ free(cfg->dns64_prefix);
+ config_delstrlist(cfg->dns64_ignore_aaaa);
+ free(cfg->dnstap_socket_path);
+@@ -1663,6 +1698,8 @@
+ free(cfg->cachedb_secret);
+ #ifdef USE_REDIS
+ free(cfg->redis_server_host);
++ free(cfg->redis_server_path);
++ free(cfg->redis_server_password);
+ #endif /* USE_REDIS */
+ #endif /* USE_CACHEDB */
+ #ifdef USE_IPSET
+@@ -1672,7 +1709,21 @@
+ free(cfg);
+ }
+
+-static void
++static void
++init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len)
++{
++ struct ub_randstate *rand = ub_initstate(NULL);
++
++ if (!rand)
++ fatal_exit("could not init random generator");
++ while (cookie_secret_len) {
++ *cookie_secret++ = (uint8_t)ub_random(rand);
++ cookie_secret_len--;
++ }
++ ub_randfree(rand);
++}
++
++static void
+ init_outgoing_availports(int* a, int num)
+ {
+ /* generated with make iana_update */
+@@ -1685,7 +1736,7 @@
+ for(i=1024; i<num; i++) {
+ a[i] = i;
+ }
+- /* create empty spot at 49152 to keep ephemeral ports available
++ /* create empty spot at 49152 to keep ephemeral ports available
+ * to other programs */
+ for(i=49152; i<49152+256; i++)
+ a[i] = 0;
+@@ -1696,7 +1747,7 @@
+ }
+ }
+
+-int
++int
+ cfg_mark_ports(const char* str, int allow, int* avail, int num)
+ {
+ char* mid = strchr(str, '-');
+@@ -1741,7 +1792,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_scan_ports(int* avail, int num)
+ {
+ int i;
+@@ -1858,7 +1909,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_region_strlist_insert(struct regional* region,
+ struct config_strlist** head, char* item)
+ {
+@@ -1891,7 +1942,7 @@
+ return NULL;
+ }
+
+-int
++int
+ cfg_strlist_insert(struct config_strlist** head, char* item)
+ {
+ struct config_strlist *s;
+@@ -1921,7 +1972,7 @@
+ return 0;
+ s->str = item;
+ s->next = NULL;
+-
++
+ if (*head==NULL) {
+ *head = s;
+ } else {
+@@ -1931,11 +1982,11 @@
+ }
+ last->next = s;
+ }
+-
+- return 1;
++
++ return 1;
+ }
+
+-int
++int
+ cfg_str2list_insert(struct config_str2list** head, char* item, char* i2)
+ {
+ struct config_str2list *s;
+@@ -1957,7 +2008,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_str3list_insert(struct config_str3list** head, char* item, char* i2,
+ char* i3)
+ {
+@@ -1993,7 +2044,7 @@
+ return 1;
+ }
+
+-time_t
++time_t
+ cfg_convert_timeval(const char* str)
+ {
+ time_t t;
+@@ -2001,7 +2052,7 @@
+ memset(&tm, 0, sizeof(tm));
+ if(strlen(str) < 14)
+ return 0;
+- if(sscanf(str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon,
++ if(sscanf(str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon,
+ &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
+ return 0;
+ tm.tm_year -= 1900;
+@@ -2018,7 +2069,7 @@
+ return t;
+ }
+
+-int
++int
+ cfg_count_numbers(const char* s)
+ {
+ /* format ::= (sp num)+ sp */
+@@ -2053,7 +2104,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_parse_memsize(const char* str, size_t* res)
+ {
+ size_t len;
+@@ -2069,11 +2120,11 @@
+ /* check appended num */
+ while(len>0 && str[len-1]==' ')
+ len--;
+- if(len > 1 && str[len-1] == 'b')
++ if(len > 1 && str[len-1] == 'b')
+ len--;
+- else if(len > 1 && str[len-1] == 'B')
++ else if(len > 1 && str[len-1] == 'B')
+ len--;
+-
++
+ if(len > 1 && tolower((unsigned char)str[len-1]) == 'g')
+ mult = 1024*1024*1024;
+ else if(len > 1 && tolower((unsigned char)str[len-1]) == 'm')
+@@ -2160,7 +2211,7 @@
+ log_err("out of memory");
+ return 0;
+ }
+-
++
+ /* parse */
+ s = str;
+ while((p=strsep(&s, " \t\n")) != NULL) {
+@@ -2246,7 +2297,7 @@
+ return 0;
+ }
+
+-void
++void
+ config_apply(struct config_file* config)
+ {
+ MAX_TTL = (time_t)config->max_ttl;
+@@ -2288,7 +2339,7 @@
+ #endif
+ }
+
+-/**
++/**
+ * Calculate string length of full pathname in original filesys
+ * @param fname: the path name to convert.
+ * Must not be null or empty.
+@@ -2302,7 +2353,7 @@
+ {
+ size_t len = 0;
+ int slashit = 0;
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
+ strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
+ /* already full pathname, return it */
+ return strlen(fname);
+@@ -2325,8 +2376,8 @@
+ /* prepend chdir */
+ if(slashit && cfg->directory[0] != '/')
+ len++;
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
+- strncmp(cfg->chrootdir, cfg->directory,
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
++ strncmp(cfg->chrootdir, cfg->directory,
+ strlen(cfg->chrootdir)) == 0)
+ len += strlen(cfg->directory)-strlen(cfg->chrootdir);
+ else len += strlen(cfg->directory);
+@@ -2349,7 +2400,7 @@
+ return NULL;
+ buf[0] = 0;
+ /* is fname already in chroot ? */
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
+ strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
+ /* already full pathname, return it */
+ (void)strlcpy(buf, fname, len);
+@@ -2375,10 +2426,10 @@
+ if(slashit && cfg->directory[0] != '/')
+ (void)strlcat(buf, "/", len);
+ /* is the directory already in the chroot? */
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
+- strncmp(cfg->chrootdir, cfg->directory,
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
++ strncmp(cfg->chrootdir, cfg->directory,
+ strlen(cfg->chrootdir)) == 0)
+- (void)strlcat(buf, cfg->directory+strlen(cfg->chrootdir),
++ (void)strlcat(buf, cfg->directory+strlen(cfg->chrootdir),
+ len);
+ else (void)strlcat(buf, cfg->directory, len);
+ slashit = 1;
+@@ -2415,7 +2466,7 @@
+ return (sp>tab)?sp:tab;
+ }
+
+-int
++int
+ cfg_parse_local_zone(struct config_file* cfg, const char* val)
+ {
+ const char *type, *name_end, *name;
+@@ -2450,11 +2501,11 @@
+ }
+
+ if(strcmp(type, "nodefault")==0) {
+- return cfg_strlist_insert(&cfg->local_zones_nodefault,
++ return cfg_strlist_insert(&cfg->local_zones_nodefault,
+ strdup(name));
+ #ifdef USE_IPSET
+ } else if(strcmp(type, "ipset")==0) {
+- return cfg_strlist_insert(&cfg->local_zones_ipset,
++ return cfg_strlist_insert(&cfg->local_zones_ipset,
+ strdup(name));
+ #endif
+ } else {
+@@ -2509,7 +2560,7 @@
+ const char* hex = "0123456789abcdef";
+ char *p = buf;
+ int i;
+- memmove(ad, &((struct sockaddr_in6*)&addr)->sin6_addr,
++ memmove(ad, &((struct sockaddr_in6*)&addr)->sin6_addr,
+ sizeof(ad));
+ for(i=15; i>=0; i--) {
+ uint8_t b = ad[i];
+@@ -2521,7 +2572,7 @@
+ snprintf(buf+16*4, sizeof(buf)-16*4, "ip6.arpa. ");
+ } else {
+ uint8_t ad[4];
+- memmove(ad, &((struct sockaddr_in*)&addr)->sin_addr,
++ memmove(ad, &((struct sockaddr_in*)&addr)->sin_addr,
+ sizeof(ad));
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u.in-addr.arpa. ",
+ (unsigned)ad[3], (unsigned)ad[2],
+--- contrib/unbound/util/config_file.h.orig
++++ contrib/unbound/util/config_file.h
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -88,6 +88,8 @@
+ int do_ip4;
+ /** do ip6 query support. */
+ int do_ip6;
++ /** do nat64 on queries */
++ int do_nat64;
+ /** prefer ip4 upstream queries. */
+ int prefer_ip4;
+ /** prefer ip6 upstream queries. */
+@@ -116,6 +118,8 @@
+ int do_tcp_keepalive;
+ /** tcp keepalive timeout, in msec */
+ int tcp_keepalive_timeout;
++ /** timeout of packets sitting in the socket queue */
++ int sock_queue_timeout;
+ /** proxy protocol ports */
+ struct config_strlist* proxy_protocol_port;
+
+@@ -232,7 +236,7 @@
+ /** interface description strings (IP addresses) */
+ char **ifs;
+
+- /** number of outgoing interfaces to open.
++ /** number of outgoing interfaces to open.
+ * If 0 default all interfaces. */
+ int num_out_ifs;
+ /** outgoing interface description strings (IP addresses) */
+@@ -251,7 +255,7 @@
+ /** list of donotquery addresses, linked list */
+ struct config_strlist* donotqueryaddrs;
+ #ifdef CLIENT_SUBNET
+- /** list of servers we send edns-client-subnet option to and
++ /** list of servers we send edns-client-subnet option to and
+ * accept option from, linked list */
+ struct config_strlist* client_subnet;
+ /** list of zones we send edns-client-subnet option for */
+@@ -292,6 +296,9 @@
+ int harden_referral_path;
+ /** harden against algorithm downgrade */
+ int harden_algo_downgrade;
++ /** harden against unknown records in the authority section and in
++ * the additional section */
++ int harden_unknown_additional;
+ /** use 0x20 bits in query as random ID bits */
+ int use_caps_bits_for_id;
+ /** 0x20 whitelist, domains that do not use capsforid */
+@@ -364,7 +371,7 @@
+
+ /** the module configuration string */
+ char* module_conf;
+-
++
+ /** files with trusted DS and DNSKEYs in zonefile format, list */
+ struct config_strlist* trust_anchor_file_list;
+ /** list of trustanchor keys, linked list */
+@@ -389,7 +396,7 @@
+ /** max number of query restarts, number of IPs to probe */
+ int32_t val_max_restart;
+ /** this value sets the number of seconds before revalidating bogus */
+- int bogus_ttl;
++ int bogus_ttl;
+ /** should validator clean additional section for secure msgs */
+ int val_clean_additional;
+ /** log bogus messages by the validator */
+@@ -402,6 +409,8 @@
+ int aggressive_nsec;
+ /** ignore the CD flag in incoming queries and refuse them bogus data */
+ int ignore_cd;
++ /** disable EDNS DO flag in outgoing requests */
++ int disable_edns_do;
+ /** serve expired entries and prefetch them */
+ int serve_expired;
+ /** serve expired entries until TTL after expiration */
+@@ -535,6 +544,9 @@
+ /** ignore AAAAs for these domain names and use A record anyway */
+ struct config_strlist* dns64_ignore_aaaa;
+
++ /* NAT64 prefix; if unset defaults to dns64_prefix */
++ char* nat64_prefix;
++
+ /** true to enable dnstap support */
+ int dnstap;
+ /** using bidirectional frame streams if true */
+@@ -580,6 +592,9 @@
+
+ /** ratelimit for ip addresses. 0 is off, otherwise qps (unless overridden) */
+ int ip_ratelimit;
++ /** ratelimit for ip addresses with a valid DNS Cookie. 0 is off,
++ * otherwise qps (unless overridden) */
++ int ip_ratelimit_cookie;
+ /** number of slabs for ip_ratelimit cache */
+ size_t ip_ratelimit_slabs;
+ /** memory size in bytes for ip_ratelimit cache */
+@@ -686,17 +701,32 @@
+ char* cachedb_backend;
+ /** secret seed for hash key calculation */
+ char* cachedb_secret;
++ /** cachedb that does not store, but only reads from database, if on */
++ int cachedb_no_store;
+ #ifdef USE_REDIS
+ /** redis server's IP address or host name */
+ char* redis_server_host;
+ /** redis server's TCP port */
+ int redis_server_port;
++ /** redis server's unix path. Or "", NULL if unused */
++ char* redis_server_path;
++ /** redis server's AUTH password. Or "", NULL if unused */
++ char* redis_server_password;
+ /** timeout (in ms) for communication with the redis server */
+ int redis_timeout;
+ /** set timeout on redis records based on DNS response ttl */
+ int redis_expire_records;
++ /** set the redis logical database upon connection */
++ int redis_logical_db;
+ #endif
+ #endif
++ /** Downstream DNS Cookies */
++ /** do answer with server cookie when request contained cookie option */
++ int do_answer_cookie;
++ /** cookie secret */
++ uint8_t cookie_secret[40];
++ /** cookie secret length */
++ size_t cookie_secret_len;
+
+ /* ipset module */
+ #ifdef USE_IPSET
+@@ -809,7 +839,7 @@
+ struct config_strlist* local_zones_ipset;
+ #endif
+ /** Fallback to global local_zones when there is no match in the view
+- * view specific tree. 1 for yes, 0 for no */
++ * view specific tree. 1 for yes, 0 for no */
+ int isfirst;
+ /** predefined actions for particular IP address responses */
+ struct config_str2list* respip_actions;
+@@ -884,7 +914,7 @@
+ * @param config: where options are stored into, must be freshly created.
+ * @param filename: name of configfile. If NULL nothing is done.
+ * @param chroot: if not NULL, the chroot dir currently in use (for include).
+- * @return: false on error. In that case errno is set, ENOENT means
++ * @return: false on error. In that case errno is set, ENOENT means
+ * file not found.
+ */
+ int config_read(struct config_file* config, const char* filename,
+@@ -919,16 +949,16 @@
+ int config_set_option(struct config_file* config, const char* option,
+ const char* value);
+
+-/**
++/**
+ * Call print routine for the given option.
+ * @param cfg: config.
+- * @param opt: option name without trailing :.
++ * @param opt: option name without trailing :.
+ * This is different from config_set_option.
+ * @param func: print func, called as (str, arg) for every data element.
+ * @param arg: user argument for print func.
+ * @return false if the option name is not supported (syntax error).
+ */
+-int config_get_option(struct config_file* cfg, const char* opt,
++int config_get_option(struct config_file* cfg, const char* opt,
+ void (*func)(char*,void*), void* arg);
+
+ /**
+@@ -948,7 +978,7 @@
+ * @param str: string. malloced, caller must free it.
+ * @return 0=OK, 1=syntax error, 2=malloc failed.
+ */
+-int config_get_option_collate(struct config_file* cfg, const char* opt,
++int config_get_option_collate(struct config_file* cfg, const char* opt,
+ char** str);
+
+ /**
+@@ -1143,7 +1173,7 @@
+ * k=1024, m=1024*1024, g=1024*1024*1024.
+ * @param str: string
+ * @param res: result is stored here, size in bytes.
+- * @return: true if parsed correctly, or 0 on a parse error (and an error
++ * @return: true if parsed correctly, or 0 on a parse error (and an error
+ * is logged).
+ */
+ int cfg_parse_memsize(const char* str, size_t* res);
+@@ -1177,7 +1207,7 @@
+ /**
+ * parse taglist from string into bytestring with bitlist.
+ * @param cfg: the config structure (with tagnames)
+- * @param str: the string to parse. Parse puts 0 bytes in string.
++ * @param str: the string to parse. Parse puts 0 bytes in string.
+ * @param listlen: returns length of in bytes.
+ * @return malloced bytes with a bitlist of the tags. or NULL on parse error
+ * or malloc failure.
+@@ -1220,7 +1250,7 @@
+ * @param allow: give true if this range is permitted.
+ * @param avail: the array from cfg.
+ * @param num: size of the array (65536).
+- * @return: true if parsed correctly, or 0 on a parse error (and an error
++ * @return: true if parsed correctly, or 0 on a parse error (and an error
+ * is logged).
+ */
+ int cfg_mark_ports(const char* str, int allow, int* avail, int num);
+@@ -1248,7 +1278,7 @@
+ */
+ int cfg_scan_ports(int* avail, int num);
+
+-/**
++/**
+ * Convert a filename to full pathname in original filesys
+ * @param fname: the path name to convert.
+ * Must not be null or empty.
+@@ -1257,7 +1287,7 @@
+ * @return pointer to malloced buffer which is: [chroot][chdir]fname
+ * or NULL on malloc failure.
+ */
+-char* fname_after_chroot(const char* fname, struct config_file* cfg,
++char* fname_after_chroot(const char* fname, struct config_file* cfg,
+ int use_chdir);
+
+ /**
+@@ -1342,4 +1372,3 @@
+ #endif
+
+ #endif /* UTIL_CONFIG_FILE_H */
+-
+--- contrib/unbound/util/configlexer.lex.orig
++++ contrib/unbound/util/configlexer.lex
+@@ -210,9 +210,9 @@
+ %x quotedstring singlequotedstr include include_quoted val include_toplevel include_toplevel_quoted
+
+ %%
+-<INITIAL,val>{SPACE}* {
++<INITIAL,val>{SPACE}* {
+ LEXOUT(("SP ")); /* ignore */ }
+-<INITIAL,val>{SPACE}*{COMMENT}.* {
++<INITIAL,val>{SPACE}*{COMMENT}.* {
+ /* note that flex makes the longest match and '.' is any but not nl */
+ LEXOUT(("comment(%s) ", ub_c_text)); /* ignore */ }
+ server{COLON} { YDVAR(0, VAR_SERVER) }
+@@ -228,6 +228,7 @@
+ incoming-num-tcp{COLON} { YDVAR(1, VAR_INCOMING_NUM_TCP) }
+ do-ip4{COLON} { YDVAR(1, VAR_DO_IP4) }
+ do-ip6{COLON} { YDVAR(1, VAR_DO_IP6) }
++do-nat64{COLON} { YDVAR(1, VAR_DO_NAT64) }
+ prefer-ip4{COLON} { YDVAR(1, VAR_PREFER_IP4) }
+ prefer-ip6{COLON} { YDVAR(1, VAR_PREFER_IP6) }
+ do-udp{COLON} { YDVAR(1, VAR_DO_UDP) }
+@@ -241,6 +242,7 @@
+ tcp-auth-query-timeout{COLON} { YDVAR(1, VAR_TCP_AUTH_QUERY_TIMEOUT) }
+ edns-tcp-keepalive{COLON} { YDVAR(1, VAR_EDNS_TCP_KEEPALIVE) }
+ edns-tcp-keepalive-timeout{COLON} { YDVAR(1, VAR_EDNS_TCP_KEEPALIVE_TIMEOUT) }
++sock-queue-timeout{COLON} { YDVAR(1, VAR_SOCK_QUEUE_TIMEOUT) }
+ ssl-upstream{COLON} { YDVAR(1, VAR_SSL_UPSTREAM) }
+ tls-upstream{COLON} { YDVAR(1, VAR_SSL_UPSTREAM) }
+ ssl-service-key{COLON} { YDVAR(1, VAR_SSL_SERVICE_KEY) }
+@@ -317,6 +319,7 @@
+ harden-below-nxdomain{COLON} { YDVAR(1, VAR_HARDEN_BELOW_NXDOMAIN) }
+ harden-referral-path{COLON} { YDVAR(1, VAR_HARDEN_REFERRAL_PATH) }
+ harden-algo-downgrade{COLON} { YDVAR(1, VAR_HARDEN_ALGO_DOWNGRADE) }
++harden-unknown-additional{COLON} { YDVAR(1, VAR_HARDEN_UNKNOWN_ADDITIONAL) }
+ use-caps-for-id{COLON} { YDVAR(1, VAR_USE_CAPS_FOR_ID) }
+ caps-whitelist{COLON} { YDVAR(1, VAR_CAPS_WHITELIST) }
+ caps-exempt{COLON} { YDVAR(1, VAR_CAPS_WHITELIST) }
+@@ -401,6 +404,7 @@
+ val-permissive-mode{COLON} { YDVAR(1, VAR_VAL_PERMISSIVE_MODE) }
+ aggressive-nsec{COLON} { YDVAR(1, VAR_AGGRESSIVE_NSEC) }
+ ignore-cd-flag{COLON} { YDVAR(1, VAR_IGNORE_CD_FLAG) }
++disable-edns-do{COLON} { YDVAR(1, VAR_DISABLE_EDNS_DO) }
+ serve-expired{COLON} { YDVAR(1, VAR_SERVE_EXPIRED) }
+ serve-expired-ttl{COLON} { YDVAR(1, VAR_SERVE_EXPIRED_TTL) }
+ serve-expired-ttl-reset{COLON} { YDVAR(1, VAR_SERVE_EXPIRED_TTL_RESET) }
+@@ -414,7 +418,7 @@
+ key-cache-size{COLON} { YDVAR(1, VAR_KEY_CACHE_SIZE) }
+ key-cache-slabs{COLON} { YDVAR(1, VAR_KEY_CACHE_SLABS) }
+ neg-cache-size{COLON} { YDVAR(1, VAR_NEG_CACHE_SIZE) }
+-val-nsec3-keysize-iterations{COLON} {
++val-nsec3-keysize-iterations{COLON} {
+ YDVAR(1, VAR_VAL_NSEC3_KEYSIZE_ITERATIONS) }
+ zonemd-permissive-mode{COLON} { YDVAR(1, VAR_ZONEMD_PERMISSIVE_MODE) }
+ zonemd-check{COLON} { YDVAR(1, VAR_ZONEMD_CHECK) }
+@@ -463,6 +467,7 @@
+ dns64-prefix{COLON} { YDVAR(1, VAR_DNS64_PREFIX) }
+ dns64-synthall{COLON} { YDVAR(1, VAR_DNS64_SYNTHALL) }
+ dns64-ignore-aaaa{COLON} { YDVAR(1, VAR_DNS64_IGNORE_AAAA) }
++nat64-prefix{COLON} { YDVAR(1, VAR_NAT64_PREFIX) }
+ define-tag{COLON} { YDVAR(1, VAR_DEFINE_TAG) }
+ local-zone-tag{COLON} { YDVAR(2, VAR_LOCAL_ZONE_TAG) }
+ access-control-tag{COLON} { YDVAR(2, VAR_ACCESS_CONTROL_TAG) }
+@@ -504,6 +509,7 @@
+ YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES) }
+ disable-dnssec-lame-check{COLON} { YDVAR(1, VAR_DISABLE_DNSSEC_LAME_CHECK) }
+ ip-ratelimit{COLON} { YDVAR(1, VAR_IP_RATELIMIT) }
++ip-ratelimit-cookie{COLON} { YDVAR(1, VAR_IP_RATELIMIT_COOKIE) }
+ ratelimit{COLON} { YDVAR(1, VAR_RATELIMIT) }
+ ip-ratelimit-slabs{COLON} { YDVAR(1, VAR_IP_RATELIMIT_SLABS) }
+ ratelimit-slabs{COLON} { YDVAR(1, VAR_RATELIMIT_SLABS) }
+@@ -553,15 +559,21 @@
+ cachedb{COLON} { YDVAR(0, VAR_CACHEDB) }
+ backend{COLON} { YDVAR(1, VAR_CACHEDB_BACKEND) }
+ secret-seed{COLON} { YDVAR(1, VAR_CACHEDB_SECRETSEED) }
++cachedb-no-store{COLON} { YDVAR(1, VAR_CACHEDB_NO_STORE) }
+ redis-server-host{COLON} { YDVAR(1, VAR_CACHEDB_REDISHOST) }
+ redis-server-port{COLON} { YDVAR(1, VAR_CACHEDB_REDISPORT) }
++redis-server-path{COLON} { YDVAR(1, VAR_CACHEDB_REDISPATH) }
++redis-server-password{COLON} { YDVAR(1, VAR_CACHEDB_REDISPASSWORD) }
+ redis-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) }
+ redis-expire-records{COLON} { YDVAR(1, VAR_CACHEDB_REDISEXPIRERECORDS) }
++redis-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) }
+ ipset{COLON} { YDVAR(0, VAR_IPSET) }
+ name-v4{COLON} { YDVAR(1, VAR_IPSET_NAME_V4) }
+ name-v6{COLON} { YDVAR(1, VAR_IPSET_NAME_V6) }
+ udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM) }
+ tcp-connection-limit{COLON} { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) }
++answer-cookie{COLON} { YDVAR(1, VAR_ANSWER_COOKIE ) }
++cookie-secret{COLON} { YDVAR(1, VAR_COOKIE_SECRET) }
+ edns-client-string{COLON} { YDVAR(2, VAR_EDNS_CLIENT_STRING) }
+ edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
+ nsid{COLON} { YDVAR(1, VAR_NSID ) }
+--- contrib/unbound/util/configparser.y.orig
++++ contrib/unbound/util/configparser.y
+@@ -47,6 +47,7 @@
+ #include "util/configyyrename.h"
+ #include "util/config_file.h"
+ #include "util/net_help.h"
++#include "sldns/str2wire.h"
+
+ int ub_c_lex(void);
+ void ub_c_error(const char *message);
+@@ -73,9 +74,10 @@
+ %token VAR_FORCE_TOPLEVEL
+ %token VAR_SERVER VAR_VERBOSITY VAR_NUM_THREADS VAR_PORT
+ %token VAR_OUTGOING_RANGE VAR_INTERFACE VAR_PREFER_IP4
+-%token VAR_DO_IP4 VAR_DO_IP6 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP
++%token VAR_DO_IP4 VAR_DO_IP6 VAR_DO_NAT64 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP
+ %token VAR_TCP_MSS VAR_OUTGOING_TCP_MSS VAR_TCP_IDLE_TIMEOUT
+ %token VAR_EDNS_TCP_KEEPALIVE VAR_EDNS_TCP_KEEPALIVE_TIMEOUT
++%token VAR_SOCK_QUEUE_TIMEOUT
+ %token VAR_CHROOT VAR_USERNAME VAR_DIRECTORY VAR_LOGFILE VAR_PIDFILE
+ %token VAR_MSG_CACHE_SIZE VAR_MSG_CACHE_SLABS VAR_NUM_QUERIES_PER_THREAD
+ %token VAR_RRSET_CACHE_SIZE VAR_RRSET_CACHE_SLABS VAR_OUTGOING_NUM_TCP
+@@ -123,6 +125,7 @@
+ %token VAR_UNBLOCK_LAN_ZONES VAR_INSECURE_LAN_ZONES
+ %token VAR_INFRA_CACHE_MIN_RTT VAR_INFRA_CACHE_MAX_RTT VAR_INFRA_KEEP_PROBING
+ %token VAR_DNS64_PREFIX VAR_DNS64_SYNTHALL VAR_DNS64_IGNORE_AAAA
++%token VAR_NAT64_PREFIX
+ %token VAR_DNSTAP VAR_DNSTAP_ENABLE VAR_DNSTAP_SOCKET_PATH VAR_DNSTAP_IP
+ %token VAR_DNSTAP_TLS VAR_DNSTAP_TLS_SERVER_NAME VAR_DNSTAP_TLS_CERT_BUNDLE
+ %token VAR_DNSTAP_TLS_CLIENT_KEY_FILE VAR_DNSTAP_TLS_CLIENT_CERT_FILE
+@@ -175,12 +178,14 @@
+ %token VAR_IPSECMOD_MAX_TTL VAR_IPSECMOD_WHITELIST VAR_IPSECMOD_STRICT
+ %token VAR_CACHEDB VAR_CACHEDB_BACKEND VAR_CACHEDB_SECRETSEED
+ %token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISTIMEOUT
+-%token VAR_CACHEDB_REDISEXPIRERECORDS
++%token VAR_CACHEDB_REDISEXPIRERECORDS VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISPASSWORD
++%token VAR_CACHEDB_REDISLOGICALDB
+ %token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM
+ %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM
+ %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
+ %token VAR_FAST_SERVER_PERMIL VAR_FAST_SERVER_NUM
+ %token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
++%token VAR_ANSWER_COOKIE VAR_COOKIE_SECRET VAR_IP_RATELIMIT_COOKIE
+ %token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
+ %token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
+ %token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES VAR_TLS_USE_SNI
+@@ -194,6 +199,7 @@
+ %token VAR_INTERFACE_ACTION VAR_INTERFACE_VIEW VAR_INTERFACE_TAG
+ %token VAR_INTERFACE_TAG_ACTION VAR_INTERFACE_TAG_DATA
+ %token VAR_PROXY_PROTOCOL_PORT VAR_STATISTICS_INHIBIT_ZERO
++%token VAR_HARDEN_UNKNOWN_ADDITIONAL VAR_DISABLE_EDNS_DO VAR_CACHEDB_NO_STORE
+
+ %%
+ toplevelvars: /* empty */ | toplevelvars toplevelvar ;
+@@ -222,10 +228,11 @@
+ | ;
+ content_server: server_num_threads | server_verbosity | server_port |
+ server_outgoing_range | server_do_ip4 |
+- server_do_ip6 | server_prefer_ip4 | server_prefer_ip6 |
+- server_do_udp | server_do_tcp |
++ server_do_ip6 | server_do_nat64 | server_prefer_ip4 |
++ server_prefer_ip6 | server_do_udp | server_do_tcp |
+ server_tcp_mss | server_outgoing_tcp_mss | server_tcp_idle_timeout |
+ server_tcp_keepalive | server_tcp_keepalive_timeout |
++ server_sock_queue_timeout |
+ server_interface | server_chroot | server_username |
+ server_directory | server_logfile | server_pidfile |
+ server_msg_cache_size | server_msg_cache_slabs |
+@@ -273,6 +280,7 @@
+ server_so_reuseport | server_delay_close | server_udp_connect |
+ server_unblock_lan_zones | server_insecure_lan_zones |
+ server_dns64_prefix | server_dns64_synthall | server_dns64_ignore_aaaa |
++ server_nat64_prefix |
+ server_infra_cache_min_rtt | server_infra_cache_max_rtt | server_harden_algo_downgrade |
+ server_ip_transparent | server_ip_ratelimit | server_ratelimit |
+ server_ip_dscp | server_infra_keep_probing |
+@@ -302,7 +310,7 @@
+ server_serve_expired |
+ server_serve_expired_ttl | server_serve_expired_ttl_reset |
+ server_serve_expired_reply_ttl | server_serve_expired_client_timeout |
+- server_ede_serve_expired | server_serve_original_ttl | server_fake_dsa |
++ server_ede_serve_expired | server_serve_original_ttl | server_fake_dsa |
+ server_log_identity | server_use_systemd |
+ server_response_ip_tag | server_response_ip | server_response_ip_data |
+ server_shm_enable | server_shm_key | server_fake_sha1 |
+@@ -318,12 +326,14 @@
+ server_unknown_server_time_limit | server_log_tag_queryreply |
+ server_stream_wait_size | server_tls_ciphers |
+ server_tls_ciphersuites | server_tls_session_ticket_keys |
++ server_answer_cookie | server_cookie_secret | server_ip_ratelimit_cookie |
+ server_tls_use_sni | server_edns_client_string |
+ server_edns_client_string_opcode | server_nsid |
+ server_zonemd_permissive_mode | server_max_reuse_tcp_queries |
+ server_tcp_reuse_timeout | server_tcp_auth_query_timeout |
+ server_interface_automatic_ports | server_ede |
+- server_proxy_protocol_port | server_statistics_inhibit_zero
++ server_proxy_protocol_port | server_statistics_inhibit_zero |
++ server_harden_unknown_additional | server_disable_edns_do
+ ;
+ stubstart: VAR_STUB_ZONE
+ {
+@@ -486,7 +496,7 @@
+ rpzstart: VAR_RPZ
+ {
+ struct config_auth* s;
+- OUTYY(("\nP(rpz:)\n"));
++ OUTYY(("\nP(rpz:)\n"));
+ cfg_parser->started_toplevel = 1;
+ s = (struct config_auth*)calloc(1, sizeof(struct config_auth));
+ if(s) {
+@@ -502,7 +512,7 @@
+ }
+ }
+ ;
+-contents_rpz: contents_rpz content_rpz
++contents_rpz: contents_rpz content_rpz
+ | ;
+ content_rpz: auth_name | auth_zonefile | rpz_tag | auth_master | auth_url |
+ auth_allow_notify | rpz_action_override | rpz_cname_override |
+@@ -850,6 +860,15 @@
+ free($2);
+ }
+ ;
++server_do_nat64: VAR_DO_NAT64 STRING_ARG
++ {
++ OUTYY(("P(server_do_nat64:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->do_nat64 = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
+ server_do_udp: VAR_DO_UDP STRING_ARG
+ {
+ OUTYY(("P(server_do_udp:%s)\n", $2));
+@@ -972,6 +991,19 @@
+ free($2);
+ }
+ ;
++server_sock_queue_timeout: VAR_SOCK_QUEUE_TIMEOUT STRING_ARG
++ {
++ OUTYY(("P(server_sock_queue_timeout:%s)\n", $2));
++ if(atoi($2) == 0 && strcmp($2, "0") != 0)
++ yyerror("number expected");
++ else if (atoi($2) > 6553500)
++ cfg_parser->cfg->sock_queue_timeout = 6553500;
++ else if (atoi($2) < 1)
++ cfg_parser->cfg->sock_queue_timeout = 0;
++ else cfg_parser->cfg->sock_queue_timeout = atoi($2);
++ free($2);
++ }
++ ;
+ server_tcp_upstream: VAR_TCP_UPSTREAM STRING_ARG
+ {
+ OUTYY(("P(server_tcp_upstream:%s)\n", $2));
+@@ -1132,7 +1164,7 @@
+ yyerror("expected yes or no.");
+ else cfg_parser->cfg->http_nodelay = (strcmp($2, "yes")==0);
+ free($2);
+- }
++ };
+ server_http_notls_downstream: VAR_HTTP_NOTLS_DOWNSTREAM STRING_ARG
+ {
+ OUTYY(("P(server_http_notls_downstream:%s)\n", $2));
+@@ -1778,6 +1810,16 @@
+ free($2);
+ }
+ ;
++server_harden_unknown_additional: VAR_HARDEN_UNKNOWN_ADDITIONAL STRING_ARG
++ {
++ OUTYY(("P(server_harden_unknown_additional:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->harden_unknown_additional =
++ (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
+ server_use_caps_for_id: VAR_USE_CAPS_FOR_ID STRING_ARG
+ {
+ OUTYY(("P(server_use_caps_for_id:%s)\n", $2));
+@@ -2019,6 +2061,15 @@
+ free($2);
+ }
+ ;
++server_disable_edns_do: VAR_DISABLE_EDNS_DO STRING_ARG
++ {
++ OUTYY(("P(server_disable_edns_do:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->disable_edns_do = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
+ server_serve_expired: VAR_SERVE_EXPIRED STRING_ARG
+ {
+ OUTYY(("P(server_serve_expired:%s)\n", $2));
+@@ -2169,6 +2220,7 @@
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
++ ;
+ server_key_cache_size: VAR_KEY_CACHE_SIZE STRING_ARG
+ {
+ OUTYY(("P(server_key_cache_size:%s)\n", $2));
+@@ -2206,6 +2258,7 @@
+ strcmp($3, "transparent")!=0 && strcmp($3, "nodefault")!=0
+ && strcmp($3, "typetransparent")!=0
+ && strcmp($3, "always_transparent")!=0
++ && strcmp($3, "block_a")!=0
+ && strcmp($3, "always_refuse")!=0
+ && strcmp($3, "always_nxdomain")!=0
+ && strcmp($3, "always_nodata")!=0
+@@ -2218,7 +2271,7 @@
+ yyerror("local-zone type: expected static, deny, "
+ "refuse, redirect, transparent, "
+ "typetransparent, inform, inform_deny, "
+- "inform_redirect, always_transparent, "
++ "inform_redirect, always_transparent, block_a,"
+ "always_refuse, always_nxdomain, "
+ "always_nodata, always_deny, always_null, "
+ "noview, nodefault or ipset");
+@@ -2333,6 +2386,13 @@
+ fatal_exit("out of memory adding dns64-ignore-aaaa");
+ }
+ ;
++server_nat64_prefix: VAR_NAT64_PREFIX STRING_ARG
++ {
++ OUTYY(("P(nat64_prefix:%s)\n", $2));
++ free(cfg_parser->cfg->nat64_prefix);
++ cfg_parser->cfg->nat64_prefix = $2;
++ }
++ ;
+ server_define_tag: VAR_DEFINE_TAG STRING_ARG
+ {
+ char* p, *s = $2;
+@@ -2518,6 +2578,15 @@
+ free($2);
+ }
+ ;
++server_ip_ratelimit_cookie: VAR_IP_RATELIMIT_COOKIE STRING_ARG
++ {
++ OUTYY(("P(server_ip_ratelimit_cookie:%s)\n", $2));
++ if(atoi($2) == 0 && strcmp($2, "0") != 0)
++ yyerror("number expected");
++ else cfg_parser->cfg->ip_ratelimit_cookie = atoi($2);
++ free($2);
++ }
++ ;
+ server_ratelimit: VAR_RATELIMIT STRING_ARG
+ {
+ OUTYY(("P(server_ratelimit:%s)\n", $2));
+@@ -2713,7 +2782,7 @@
+ OUTYY(("P(server_pad_responses:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+- else cfg_parser->cfg->pad_responses =
++ else cfg_parser->cfg->pad_responses =
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
+@@ -2732,7 +2801,7 @@
+ OUTYY(("P(server_pad_queries:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+- else cfg_parser->cfg->pad_queries =
++ else cfg_parser->cfg->pad_queries =
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
+@@ -3471,9 +3540,10 @@
+ if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, $2))
+ yyerror("out of memory");
+ }
++ ;
+ dynlibstart: VAR_DYNLIB
+- {
+- OUTYY(("\nP(dynlib:)\n"));
++ {
++ OUTYY(("\nP(dynlib:)\n"));
+ cfg_parser->started_toplevel = 1;
+ }
+ ;
+@@ -3487,6 +3557,7 @@
+ if(!cfg_strlist_append_ex(&cfg_parser->cfg->dynlib_file, $2))
+ yyerror("out of memory");
+ }
++ ;
+ server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG
+ {
+ OUTYY(("P(disable_dnssec_lame_check:%s)\n", $2));
+@@ -3547,7 +3618,6 @@
+ free($2);
+ }
+ ;
+-
+ dnsc_dnscrypt_port: VAR_DNSCRYPT_PORT STRING_ARG
+ {
+ OUTYY(("P(dnsc_dnscrypt_port:%s)\n", $2));
+@@ -3641,7 +3711,8 @@
+ | ;
+ content_cachedb: cachedb_backend_name | cachedb_secret_seed |
+ redis_server_host | redis_server_port | redis_timeout |
+- redis_expire_records
++ redis_expire_records | redis_server_path | redis_server_password |
++ cachedb_no_store | redis_logical_db
+ ;
+ cachedb_backend_name: VAR_CACHEDB_BACKEND STRING_ARG
+ {
+@@ -3667,6 +3738,19 @@
+ #endif
+ }
+ ;
++cachedb_no_store: VAR_CACHEDB_NO_STORE STRING_ARG
++ {
++ #ifdef USE_CACHEDB
++ OUTYY(("P(cachedb_no_store:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->cachedb_no_store = (strcmp($2, "yes")==0);
++ #else
++ OUTYY(("P(Compiled without cachedb, ignoring)\n"));
++ #endif
++ free($2);
++ }
++ ;
+ redis_server_host: VAR_CACHEDB_REDISHOST STRING_ARG
+ {
+ #if defined(USE_CACHEDB) && defined(USE_REDIS)
+@@ -3694,6 +3778,30 @@
+ free($2);
+ }
+ ;
++redis_server_path: VAR_CACHEDB_REDISPATH STRING_ARG
++ {
++ #if defined(USE_CACHEDB) && defined(USE_REDIS)
++ OUTYY(("P(redis_server_path:%s)\n", $2));
++ free(cfg_parser->cfg->redis_server_path);
++ cfg_parser->cfg->redis_server_path = $2;
++ #else
++ OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
++ free($2);
++ #endif
++ }
++ ;
++redis_server_password: VAR_CACHEDB_REDISPASSWORD STRING_ARG
++ {
++ #if defined(USE_CACHEDB) && defined(USE_REDIS)
++ OUTYY(("P(redis_server_password:%s)\n", $2));
++ free(cfg_parser->cfg->redis_server_password);
++ cfg_parser->cfg->redis_server_password = $2;
++ #else
++ OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
++ free($2);
++ #endif
++ }
++ ;
+ redis_timeout: VAR_CACHEDB_REDISTIMEOUT STRING_ARG
+ {
+ #if defined(USE_CACHEDB) && defined(USE_REDIS)
+@@ -3720,6 +3828,21 @@
+ free($2);
+ }
+ ;
++redis_logical_db: VAR_CACHEDB_REDISLOGICALDB STRING_ARG
++ {
++ #if defined(USE_CACHEDB) && defined(USE_REDIS)
++ int db;
++ OUTYY(("P(redis_logical_db:%s)\n", $2));
++ db = atoi($2);
++ if((db == 0 && strcmp($2, "0") != 0) || db < 0)
++ yyerror("valid redis logical database index expected");
++ else cfg_parser->cfg->redis_logical_db = db;
++ #else
++ OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
++ #endif
++ free($2);
++ }
++ ;
+ server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG
+ {
+ OUTYY(("P(server_tcp_connection_limit:%s %s)\n", $2, $3));
+@@ -3731,6 +3854,31 @@
+ }
+ }
+ ;
++server_answer_cookie: VAR_ANSWER_COOKIE STRING_ARG
++ {
++ OUTYY(("P(server_answer_cookie:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->do_answer_cookie = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
++server_cookie_secret: VAR_COOKIE_SECRET STRING_ARG
++ {
++ uint8_t secret[32];
++ size_t secret_len = sizeof(secret);
++
++ OUTYY(("P(server_cookie_secret:%s)\n", $2));
++ if(sldns_str2wire_hex_buf($2, secret, &secret_len)
++ || (secret_len != 16))
++ yyerror("expected 128 bit hex string");
++ else {
++ cfg_parser->cfg->cookie_secret_len = secret_len;
++ memcpy(cfg_parser->cfg->cookie_secret, secret, sizeof(secret));
++ }
++ free($2);
++ }
++ ;
+ ipsetstart: VAR_IPSET
+ {
+ OUTYY(("\nP(ipset:)\n"));
+@@ -3800,10 +3948,11 @@
+ strcmp(action, "refuse_non_local")!=0 &&
+ strcmp(action, "allow_setrd")!=0 &&
+ strcmp(action, "allow")!=0 &&
+- strcmp(action, "allow_snoop")!=0)
++ strcmp(action, "allow_snoop")!=0 &&
++ strcmp(action, "allow_cookie")!=0)
+ {
+ yyerror("expected deny, refuse, deny_non_local, "
+- "refuse_non_local, allow, allow_setrd or "
+- "allow_snoop as access control action");
++ "refuse_non_local, allow, allow_setrd, "
++ "allow_snoop or allow_cookie as access control action");
+ }
+ }
+--- contrib/unbound/util/data/msgencode.c.orig
++++ contrib/unbound/util/data/msgencode.c
+@@ -806,6 +806,95 @@
+ return 1 + 2 + 2 + 4 + 2 + rdatalen;
+ }
+
++uint16_t
++calc_edns_option_size(struct edns_data* edns, uint16_t code)
++{
++ size_t rdatalen = 0;
++ struct edns_option* opt;
++ if(!edns || !edns->edns_present)
++ return 0;
++ for(opt = edns->opt_list_inplace_cb_out; opt; opt = opt->next) {
++ if(opt->opt_code == code)
++ rdatalen += 4 + opt->opt_len;
++ }
++ for(opt = edns->opt_list_out; opt; opt = opt->next) {
++ if(opt->opt_code == code)
++ rdatalen += 4 + opt->opt_len;
++ }
++ return rdatalen;
++}
++
++uint16_t
++calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size)
++{
++ size_t rdatalen = 0;
++ struct edns_option* opt;
++ *txt_size = 0;
++ if(!edns || !edns->edns_present)
++ return 0;
++ for(opt = edns->opt_list_inplace_cb_out; opt; opt = opt->next) {
++ if(opt->opt_code == LDNS_EDNS_EDE) {
++ rdatalen += 4 + opt->opt_len;
++ if(opt->opt_len > 2) *txt_size += opt->opt_len - 2;
++ if(opt->opt_len >= 2 && sldns_read_uint16(
++ opt->opt_data) == LDNS_EDE_OTHER) {
++ *txt_size += 4 + 2;
++ }
++ }
++ }
++ for(opt = edns->opt_list_out; opt; opt = opt->next) {
++ if(opt->opt_code == LDNS_EDNS_EDE) {
++ rdatalen += 4 + opt->opt_len;
++ if(opt->opt_len > 2) *txt_size += opt->opt_len - 2;
++ if(opt->opt_len >= 2 && sldns_read_uint16(
++ opt->opt_data) == LDNS_EDE_OTHER) {
++ *txt_size += 4 + 2;
++ }
++ }
++ }
++ return rdatalen;
++}
++
++/* Trims the EDE OPTION-DATA to not include any EXTRA-TEXT data.
++ * Also removes any LDNS_EDE_OTHER options from the list since they are useless
++ * without the extra text. */
++static void
++ede_trim_text(struct edns_option** list)
++{
++ struct edns_option* curr, *prev = NULL;
++ if(!list || !(*list)) return;
++ /* Unlink and repoint if LDNS_EDE_OTHER are first in list */
++ while(list && *list && (*list)->opt_code == LDNS_EDNS_EDE
++ && (*list)->opt_len >= 2
++ && sldns_read_uint16((*list)->opt_data) == LDNS_EDE_OTHER ) {
++ *list = (*list)->next;
++ }
++ if(!list || !(*list)) return;
++ curr = *list;
++ while(curr) {
++ if(curr->opt_code == LDNS_EDNS_EDE) {
++ if(curr->opt_len >= 2 && sldns_read_uint16(
++ curr->opt_data) == LDNS_EDE_OTHER) {
++ /* LDNS_EDE_OTHER cannot be the first option in
++ * this while, so prev is always initialized at
++ * this point from the other branches;
++ * cut this option off */
++ prev->next = curr->next;
++ curr = curr->next;
++ } else if(curr->opt_len > 2) {
++ /* trim this option's EXTRA-TEXT */
++ curr->opt_len = 2;
++ prev = curr;
++ curr = curr->next;
++ }
++ } else {
++ /* continue */
++ prev = curr;
++ curr = curr->next;
++ }
++ }
++}
++
+ static void
+ attach_edns_record_max_msg_sz(sldns_buffer* pkt, struct edns_data* edns,
+ uint16_t max_msg_sz)
+@@ -894,6 +983,7 @@
+ {
+ uint16_t flags;
+ unsigned int attach_edns = 0;
++ uint16_t edns_field_size, ede_size, ede_txt_size;
+
+ if(!cached || rep->authoritative) {
+ /* original flags, copy RD and CD bits from query. */
+@@ -916,25 +1006,41 @@
+ log_assert(flags & BIT_QR); /* QR bit must be on in our replies */
+ if(udpsize < LDNS_HEADER_SIZE)
+ return 0;
++ /* currently edns does not change during calculations;
++ * calculate sizes once here */
++ edns_field_size = calc_edns_field_size(edns);
++ ede_size = calc_ede_option_size(edns, &ede_txt_size);
+ if(sldns_buffer_capacity(pkt) < udpsize)
+ udpsize = sldns_buffer_capacity(pkt);
+- if(udpsize < LDNS_HEADER_SIZE + calc_edns_field_size(edns)) {
++ if(!edns || !edns->edns_present) {
++ attach_edns = 0;
++ /* EDEs are optional, try to fit anything else before them */
++ } else if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) {
+ /* packet too small to contain edns, omit it. */
+ attach_edns = 0;
+ } else {
+ /* reserve space for edns record */
+- attach_edns = (unsigned int)calc_edns_field_size(edns);
+- udpsize -= attach_edns;
++ attach_edns = (unsigned int)edns_field_size - ede_size;
+ }
+
+ if(!reply_info_encode(qinf, rep, id, flags, pkt, timenow, region,
+- udpsize, dnssec, MINIMAL_RESPONSES)) {
++ udpsize - attach_edns, dnssec, MINIMAL_RESPONSES)) {
+ log_err("reply encode: out of memory");
+ return 0;
+ }
+- if(attach_edns && sldns_buffer_capacity(pkt) >=
+- sldns_buffer_limit(pkt)+attach_edns)
+- attach_edns_record_max_msg_sz(pkt, edns, udpsize+attach_edns);
++ if(attach_edns) {
++ if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size)
++ attach_edns_record_max_msg_sz(pkt, edns, udpsize);
++ else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) {
++ ede_trim_text(&edns->opt_list_inplace_cb_out);
++ ede_trim_text(&edns->opt_list_out);
++ attach_edns_record_max_msg_sz(pkt, edns, udpsize);
++ } else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) {
++ edns_opt_list_remove(&edns->opt_list_inplace_cb_out, LDNS_EDNS_EDE);
++ edns_opt_list_remove(&edns->opt_list_out, LDNS_EDNS_EDE);
++ attach_edns_record_max_msg_sz(pkt, edns, udpsize);
++ }
++ }
+ return 1;
+ }
+
+@@ -958,15 +1064,17 @@
+ sldns_buffer_flip(pkt);
+ }
+
+-void
+-error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
+- uint16_t qid, uint16_t qflags, struct edns_data* edns)
++void
++extended_error_encode(sldns_buffer* buf, uint16_t rcode,
++ struct query_info* qinfo, uint16_t qid, uint16_t qflags,
++ uint16_t xflags, struct edns_data* edns)
+ {
+ uint16_t flags;
+
+ sldns_buffer_clear(buf);
+ sldns_buffer_write(buf, &qid, sizeof(uint16_t));
+- flags = (uint16_t)(BIT_QR | BIT_RA | r); /* QR and retcode*/
++ flags = (uint16_t)(BIT_QR | BIT_RA | (rcode & 0xF)); /* QR and retcode*/
++ flags |= xflags;
+ flags |= (qflags & (BIT_RD|BIT_CD)); /* copy RD and CD bit */
+ sldns_buffer_write_u16(buf, flags);
+ if(qinfo) flags = 1;
+@@ -993,11 +1101,25 @@
+ struct edns_data es = *edns;
+ es.edns_version = EDNS_ADVERTISED_VERSION;
+ es.udp_size = EDNS_ADVERTISED_SIZE;
+- es.ext_rcode = 0;
++ es.ext_rcode = (uint8_t)(rcode >> 4);
+ es.bits &= EDNS_DO;
+ if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
+- edns->udp_size)
+- return;
++ edns->udp_size) {
++ edns_opt_list_remove(&es.opt_list_inplace_cb_out, LDNS_EDNS_EDE);
++ edns_opt_list_remove(&es.opt_list_out, LDNS_EDNS_EDE);
++ if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
++ edns->udp_size) {
++ return;
++ }
++ }
+ attach_edns_record(buf, &es);
+ }
+ }
++
++void
++error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
++ uint16_t qid, uint16_t qflags, struct edns_data* edns)
++{
++ extended_error_encode(buf, (r & 0x000F), qinfo, qid, qflags,
++ (r & 0xFFF0), edns);
++}
+--- contrib/unbound/util/data/msgencode.h.orig
++++ contrib/unbound/util/data/msgencode.h
+@@ -108,6 +108,27 @@
+ */
+ uint16_t calc_edns_field_size(struct edns_data* edns);
+
++/**
++ * Calculate the size of a specific EDNS option in packet.
++ * @param edns: edns data or NULL.
++ * @param code: the opt code to get the size of.
++ * @return octets the option will take up.
++ */
++uint16_t calc_edns_option_size(struct edns_data* edns, uint16_t code);
++
++/**
++ * Calculate the size of the EDE option(s) in packet. Also calculate seperately
++ * the size of the EXTRA-TEXT field(s) in case we can trim them to fit.
++ * In this case include any LDNS_EDE_OTHER options in their entirety since they
++ * are useless without extra text.
++ * @param edns: edns data or NULL.
++ * @param txt_size: the size of the EXTRA-TEXT field(s); this includes
++ * LDNS_EDE_OTHER in their entirety since they are useless without
++ * extra text.
++ * @return octets the option will take up.
++ */
++uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size);
++
+ /**
+ * Attach EDNS record to buffer. Buffer has complete packet. There must
+ * be enough room left for the EDNS record.
+@@ -116,11 +137,11 @@
+ */
+ void attach_edns_record(struct sldns_buffer* pkt, struct edns_data* edns);
+
+-/**
++/**
+ * Encode an error. With QR and RA set.
+ *
+ * @param pkt: where to store the packet.
+- * @param r: RCODE value to encode.
++ * @param r: RCODE value to encode (may contain extra flags).
+ * @param qinfo: if not NULL, the query is included.
+ * @param qid: query ID to set in packet. network order.
+ * @param qflags: original query flags (to copy RD and CD bits). host order.
+@@ -130,4 +151,21 @@
+ void error_encode(struct sldns_buffer* pkt, int r, struct query_info* qinfo,
+ uint16_t qid, uint16_t qflags, struct edns_data* edns);
+
++/**
++ * Encode an extended error. With QR and RA set.
++ *
++ * @param pkt: where to store the packet.
++ * @param rcode: Extended RCODE value to encode.
++ * @param qinfo: if not NULL, the query is included.
++ * @param qid: query ID to set in packet. network order.
++ * @param qflags: original query flags (to copy RD and CD bits). host order.
++ * @param xflags: extra flags to set (such as for example BIT_AA and/or BIT_TC)
++ * @param edns: if not NULL, this is the query edns info,
++ * and an edns reply is attached. Only attached if EDNS record fits reply.
++ * Without edns extended errors (i.e. > 15) will not be conveyed.
++ */
++void extended_error_encode(struct sldns_buffer* pkt, uint16_t rcode,
++ struct query_info* qinfo, uint16_t qid, uint16_t qflags,
++ uint16_t xflags, struct edns_data* edns);
++
+ #endif /* UTIL_DATA_MSGENCODE_H */
+--- contrib/unbound/util/data/msgparse.c.orig
++++ contrib/unbound/util/data/msgparse.c
+@@ -45,6 +45,9 @@
+ #include "util/netevent.h"
+ #include "util/storage/lookup3.h"
+ #include "util/regional.h"
++#include "util/rfc_1982.h"
++#include "util/edns.h"
++#include "util/net_help.h"
+ #include "sldns/rrdef.h"
+ #include "sldns/sbuffer.h"
+ #include "sldns/parseutil.h"
+@@ -940,22 +943,11 @@
+ return 0;
+ }
+
+-static int
+-edns_opt_list_append_keepalive(struct edns_option** list, int msec,
+- struct regional* region)
+-{
+- uint8_t data[2]; /* For keepalive value */
+- data[0] = (uint8_t)((msec >> 8) & 0xff);
+- data[1] = (uint8_t)(msec & 0xff);
+- return edns_opt_list_append(list, LDNS_EDNS_KEEPALIVE, sizeof(data),
+- data, region);
+-}
+-
+ /** parse EDNS options from EDNS wireformat rdata */
+ static int
+ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
+ struct edns_data* edns, struct config_file* cfg, struct comm_point* c,
+- struct regional* region)
++ struct comm_reply* repinfo, uint32_t now, struct regional* region)
+ {
+ /* To respond with a Keepalive option, the client connection must have
+ * received one message with a TCP Keepalive EDNS option, and that
+@@ -979,6 +971,10 @@
+ while(rdata_len >= 4) {
+ uint16_t opt_code = sldns_read_uint16(rdata_ptr);
+ uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
++ uint8_t server_cookie[40];
++ enum edns_cookie_val_status cookie_val_status;
++ int cookie_is_v4 = 1;
++
+ rdata_ptr += 4;
+ rdata_len -= 4;
+ if(opt_len > rdata_len)
+@@ -1041,6 +1037,76 @@
+ edns->padding_block_size = cfg->pad_responses_block_size;
+ break;
+
++ case LDNS_EDNS_COOKIE:
++ if(!cfg || !cfg->do_answer_cookie || !repinfo)
++ break;
++ if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
++ verbose(VERB_ALGO, "worker request: "
++ "badly formatted cookie");
++ return LDNS_RCODE_FORMERR;
++ }
++ edns->cookie_present = 1;
++
++ /* Copy client cookie, version and timestamp for
++ * validation and creation purposes.
++ */
++ if(opt_len >= 16) {
++ memmove(server_cookie, rdata_ptr, 16);
++ } else {
++ memset(server_cookie, 0, 16);
++ memmove(server_cookie, rdata_ptr, opt_len);
++ }
++
++ /* Copy client ip for validation and creation
++ * purposes. It will be overwritten if (re)creation
++ * is needed.
++ */
++ if(repinfo->remote_addr.ss_family == AF_INET) {
++ memcpy(server_cookie + 16,
++ &((struct sockaddr_in*)&repinfo->remote_addr)->sin_addr, 4);
++ } else {
++ cookie_is_v4 = 0;
++ memcpy(server_cookie + 16,
++ &((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
++ }
++
++ cookie_val_status = edns_cookie_server_validate(
++ rdata_ptr, opt_len, cfg->cookie_secret,
++ cfg->cookie_secret_len, cookie_is_v4,
++ server_cookie, now);
++ switch(cookie_val_status) {
++ case COOKIE_STATUS_VALID:
++ case COOKIE_STATUS_VALID_RENEW:
++ edns->cookie_valid = 1;
++ /* Reuse cookie */
++ if(!edns_opt_list_append(
++ &edns->opt_list_out, LDNS_EDNS_COOKIE,
++ opt_len, rdata_ptr, region)) {
++ log_err("out of memory");
++ return LDNS_RCODE_SERVFAIL;
++ }
++ /* Cookie to be reused added to outgoing
++ * options. Done!
++ */
++ break;
++ case COOKIE_STATUS_CLIENT_ONLY:
++ edns->cookie_client = 1;
++ /* fallthrough */
++ case COOKIE_STATUS_FUTURE:
++ case COOKIE_STATUS_EXPIRED:
++ case COOKIE_STATUS_INVALID:
++ default:
++ edns_cookie_server_write(server_cookie,
++ cfg->cookie_secret, cookie_is_v4, now);
++ if(!edns_opt_list_append(&edns->opt_list_out,
++ LDNS_EDNS_COOKIE, 24, server_cookie,
++ region)) {
++ log_err("out of memory");
++ return LDNS_RCODE_SERVFAIL;
++ }
++ break;
++ }
++ break;
+ default:
+ break;
+ }
+@@ -1115,6 +1181,8 @@
+ edns->opt_list_out = NULL;
+ edns->opt_list_inplace_cb_out = NULL;
+ edns->padding_block_size = 0;
++ edns->cookie_present = 0;
++ edns->cookie_valid = 0;
+
+ /* take the options */
+ rdata_len = found->rr_first->size-2;
+@@ -1170,7 +1238,8 @@
+
+ int
+ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
+- struct config_file* cfg, struct comm_point* c, struct regional* region)
++ struct config_file* cfg, struct comm_point* c,
++ struct comm_reply* repinfo, time_t now, struct regional* region)
+ {
+ size_t rdata_len;
+ uint8_t* rdata_ptr;
+@@ -1206,6 +1275,8 @@
+ edns->opt_list_out = NULL;
+ edns->opt_list_inplace_cb_out = NULL;
+ edns->padding_block_size = 0;
++ edns->cookie_present = 0;
++ edns->cookie_valid = 0;
+
+ /* take the options */
+ rdata_len = sldns_buffer_read_u16(pkt);
+@@ -1214,7 +1285,7 @@
+ rdata_ptr = sldns_buffer_current(pkt);
+ /* ignore rrsigs */
+ return parse_edns_options_from_query(rdata_ptr, rdata_len, edns, cfg,
+- c, region);
++ c, repinfo, now, region);
+ }
+
+ void
+@@ -1236,3 +1307,27 @@
+ }
+ }
+
++/** remove RR from msgparse RRset, return true if rrset is entirely bad */
++int
++msgparse_rrset_remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* rrset,
++ struct rr_parse* prev, struct rr_parse* rr, struct sockaddr_storage* addr, socklen_t addrlen)
++{
++ if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN && str) {
++ uint8_t buf[LDNS_MAX_DOMAINLEN+1];
++ dname_pkt_copy(pkt, buf, rrset->dname);
++ if(addr)
++ log_name_addr(VERB_QUERY, str, buf, addr, addrlen);
++ else log_nametypeclass(VERB_QUERY, str, buf,
++ rrset->type, ntohs(rrset->rrset_class));
++ }
++ if(prev)
++ prev->next = rr->next;
++ else rrset->rr_first = rr->next;
++ if(rrset->rr_last == rr)
++ rrset->rr_last = prev;
++ rrset->rr_count --;
++ rrset->size -= rr->size;
++ /* rr struct still exists, but is unlinked, so that in the for loop
++ * the rr->next works fine to continue. */
++ return rrset->rr_count == 0;
++}
+--- contrib/unbound/util/data/msgparse.h.orig
++++ contrib/unbound/util/data/msgparse.h
+@@ -72,6 +72,7 @@
+ struct edns_option;
+ struct config_file;
+ struct comm_point;
++struct comm_reply;
+
+ /** number of buckets in parse rrset hash table. Must be power of 2. */
+ #define PARSE_TABLE_SIZE 32
+@@ -217,8 +218,6 @@
+ * region.
+ */
+ struct edns_data {
+- /** if EDNS OPT record was present */
+- int edns_present;
+ /** Extended RCODE */
+ uint8_t ext_rcode;
+ /** The EDNS version number */
+@@ -238,7 +237,15 @@
+ struct edns_option* opt_list_inplace_cb_out;
+ /** block size to pad */
+ uint16_t padding_block_size;
+-};
++ /** if EDNS OPT record was present */
++ unsigned int edns_present : 1;
++ /** if a cookie was present */
++ unsigned int cookie_present : 1;
++ /** if the cookie validated */
++ unsigned int cookie_valid : 1;
++ /** if the cookie holds only the client part */
++ unsigned int cookie_client : 1;
++};
+
+ /**
+ * EDNS option
+@@ -310,12 +317,15 @@
+ * initialised.
+ * @param cfg: the configuration (with nsid value etc.)
+ * @param c: commpoint to determine transport (if needed)
++ * @param repinfo: commreply to determine the client address
++ * @param now: current time
+ * @param region: region to alloc results in (edns option contents)
+ * @return: 0 on success, or an RCODE on error.
+ * RCODE formerr if OPT is badly formatted and so on.
+ */
+ int parse_edns_from_query_pkt(struct sldns_buffer* pkt, struct edns_data* edns,
+- struct config_file* cfg, struct comm_point* c, struct regional* region);
++ struct config_file* cfg, struct comm_point* c,
++ struct comm_reply* repinfo, time_t now, struct regional* region);
+
+ /**
+ * Calculate hash value for rrset in packet.
+@@ -361,4 +371,22 @@
+ void log_edns_opt_list(enum verbosity_value level, const char* info_str,
+ struct edns_option* list);
+
++/**
++ * Remove RR from msgparse RRset.
++ * @param str: this string is used for logging if verbose. If NULL, there is
++ * no logging of the remove.
++ * @param pkt: packet in buffer that is removed from. Used to log the name
++ * of the item removed.
++ * @param rrset: RRset that the RR is removed from.
++ * @param prev: previous RR in list, or NULL.
++ * @param rr: RR that is removed.
++ * @param addr: address used for logging, if verbose, or NULL then it is not
++ * used.
++ * @param addrlen: length of addr, if that is not NULL.
++ * @return true if rrset is entirely bad, it would then need to be removed.
++ */
++int msgparse_rrset_remove_rr(const char* str, struct sldns_buffer* pkt,
++ struct rrset_parse* rrset, struct rr_parse* prev, struct rr_parse* rr,
++ struct sockaddr_storage* addr, socklen_t addrlen);
++
+ #endif /* UTIL_DATA_MSGPARSE_H */
+--- contrib/unbound/util/data/msgreply.c.orig
++++ contrib/unbound/util/data/msgreply.c
+@@ -94,7 +94,7 @@
+ struct reply_info*
+ construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd,
+ time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns,
+- size_t ar, size_t total, enum sec_status sec)
++ size_t ar, size_t total, enum sec_status sec, sldns_ede_code reason_bogus)
+ {
+ struct reply_info* rep;
+ /* rrset_count-1 because the first ref is part of the struct. */
+@@ -117,7 +117,9 @@
+ rep->ar_numrrsets = ar;
+ rep->rrset_count = total;
+ rep->security = sec;
+- rep->reason_bogus = LDNS_EDE_NONE;
++ rep->reason_bogus = reason_bogus;
++ /* this is only allocated and used for caching on copy */
++ rep->reason_bogus_str = NULL;
+ rep->authoritative = 0;
+ /* array starts after the refs */
+ if(region)
+@@ -137,7 +139,7 @@
+ {
+ *rep = construct_reply_info_base(region, msg->flags, msg->qdcount, 0,
+ 0, 0, msg->an_rrsets, msg->ns_rrsets, msg->ar_rrsets,
+- msg->rrset_count, sec_status_unchecked);
++ msg->rrset_count, sec_status_unchecked, LDNS_EDE_NONE);
+ if(!*rep)
+ return 0;
+ return 1;
+@@ -182,7 +184,7 @@
+ new_rep = construct_reply_info_base(region, rep->flags,
+ rep->qdcount, rep->ttl, rep->prefetch_ttl,
+ rep->serve_expired_ttl, an_numrrsets, 0, 0, an_numrrsets,
+- sec_status_insecure);
++ sec_status_insecure, LDNS_EDE_NONE);
+ if(!new_rep)
+ return NULL;
+ if(!reply_info_alloc_rrset_keys(new_rep, NULL, region))
+@@ -580,6 +582,10 @@
+ for(i=0; i<rep->rrset_count; i++) {
+ ub_packed_rrset_parsedelete(rep->rrsets[i], alloc);
+ }
++ if(rep->reason_bogus_str) {
++ free(rep->reason_bogus_str);
++ rep->reason_bogus_str = NULL;
++ }
+ free(rep);
+ }
+
+@@ -661,6 +667,10 @@
+ reply_info_delete(void* d, void* ATTR_UNUSED(arg))
+ {
+ struct reply_info* r = (struct reply_info*)d;
++ if(r->reason_bogus_str) {
++ free(r->reason_bogus_str);
++ r->reason_bogus_str = NULL;
++ }
+ free(r);
+ }
+
+@@ -737,17 +747,36 @@
+ return 1;
+ }
+
+-struct reply_info*
+-reply_info_copy(struct reply_info* rep, struct alloc_cache* alloc,
++struct reply_info*
++reply_info_copy(struct reply_info* rep, struct alloc_cache* alloc,
+ struct regional* region)
+ {
+ struct reply_info* cp;
+- cp = construct_reply_info_base(region, rep->flags, rep->qdcount,
+- rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl,
++ cp = construct_reply_info_base(region, rep->flags, rep->qdcount,
++ rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl,
+ rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets,
+- rep->rrset_count, rep->security);
++ rep->rrset_count, rep->security, rep->reason_bogus);
+ if(!cp)
+ return NULL;
++
++ if(rep->reason_bogus_str && *rep->reason_bogus_str != 0) {
++ if(region) {
++ cp->reason_bogus_str = (char*)regional_alloc(region,
++ sizeof(char)
++ * (strlen(rep->reason_bogus_str)+1));
++ } else {
++ cp->reason_bogus_str = malloc(sizeof(char)
++ * (strlen(rep->reason_bogus_str)+1));
++ }
++ if(!cp->reason_bogus_str) {
++ if(!region)
++ reply_info_parsedelete(cp, alloc);
++ return NULL;
++ }
++ memcpy(cp->reason_bogus_str, rep->reason_bogus_str,
++ strlen(rep->reason_bogus_str)+1);
++ }
++
+ /* allocate ub_key structures special or not */
+ if(!reply_info_alloc_rrset_keys(cp, alloc, region)) {
+ if(!region)
+@@ -1020,6 +1049,16 @@
+ return 1;
+ }
+
++int edns_opt_list_append_keepalive(struct edns_option** list, int msec,
++ struct regional* region)
++{
++ uint8_t data[2]; /* For keepalive value */
++ data[0] = (uint8_t)((msec >> 8) & 0xff);
++ data[1] = (uint8_t)(msec & 0xff);
++ return edns_opt_list_append(list, LDNS_EDNS_KEEPALIVE, sizeof(data),
++ data, region);
++}
++
+ int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len,
+ uint8_t* data, struct regional* region)
+ {
+@@ -1195,7 +1234,7 @@
+ }
+
+ struct edns_option* edns_opt_copy_region(struct edns_option* list,
+- struct regional* region)
++ struct regional* region)
+ {
+ struct edns_option* result = NULL, *cur = NULL, *s;
+ while(list) {
+@@ -1224,6 +1263,42 @@
+ return result;
+ }
+
++struct edns_option* edns_opt_copy_filter_region(struct edns_option* list,
++ uint16_t* filter_list, size_t filter_list_len, struct regional* region)
++{
++ struct edns_option* result = NULL, *cur = NULL, *s;
++ size_t i;
++ while(list) {
++ for(i=0; i<filter_list_len; i++)
++ if(filter_list[i] == list->opt_code) goto found;
++ if(i == filter_list_len) goto next;
++found:
++ /* copy edns option structure */
++ s = regional_alloc_init(region, list, sizeof(*list));
++ if(!s) return NULL;
++ s->next = NULL;
++
++ /* copy option data */
++ if(s->opt_data) {
++ s->opt_data = regional_alloc_init(region, s->opt_data,
++ s->opt_len);
++ if(!s->opt_data)
++ return NULL;
++ }
++
++ /* link into list */
++ if(cur)
++ cur->next = s;
++ else result = s;
++ cur = s;
++
++next:
++ /* examine next element */
++ list = list->next;
++ }
++ return result;
++}
++
+ int edns_opt_compare(struct edns_option* p, struct edns_option* q)
+ {
+ if(!p && !q) return 0;
+--- contrib/unbound/util/data/msgreply.h.orig
++++ contrib/unbound/util/data/msgreply.h
+@@ -170,9 +170,17 @@
+
+ /**
+ * EDE (rfc8914) code with reason for DNSSEC bogus status.
++ * Used for caching the EDE.
+ */
+ sldns_ede_code reason_bogus;
+
++ /**
++ * EDE (rfc8914) NULL-terminated string with human-readable reason
++ * for DNSSEC bogus status.
++ * Used for caching the EDE.
++ */
++ char* reason_bogus_str;
++
+ /**
+ * Number of RRsets in each section.
+ * The answer section. Add up the RRs in every RRset to calculate
+@@ -240,13 +248,15 @@
+ * @param ar: ar count
+ * @param total: total rrset count (presumably an+ns+ar).
+ * @param sec: security status of the reply info.
++ * @param reason_bogus: the Extended DNS Error for DNSSEC bogus status
+ * @return the reply_info base struct with the array for putting the rrsets
+ * in. The array has been zeroed. Returns NULL on malloc failure.
+ */
+ struct reply_info*
+ construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd,
+- time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns,
+- size_t ar, size_t total, enum sec_status sec);
++ time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns,
++ size_t ar, size_t total, enum sec_status sec,
++ sldns_ede_code reason_bogus);
+
+ /**
+ * Parse wire query into a queryinfo structure, return 0 on parse error.
+@@ -567,6 +577,16 @@
+ int edns_opt_list_append_ede(struct edns_option** list, struct regional* region,
+ sldns_ede_code code, const char *txt);
+
++/**
++ * Append edns keep alive option to edns options list
++ * @param list: the edns option list to append the edns option to.
++ * @param msec: the duration in msecs for the keep alive.
++ * @param region: region to allocate the new edns option.
++ * @return false on failure.
++ */
++int edns_opt_list_append_keepalive(struct edns_option** list, int msec,
++ struct regional* region);
++
+ /**
+ * Remove any option found on the edns option list that matches the code.
+ * @param list: the list of edns options.
+@@ -718,6 +738,12 @@
+ struct edns_option* edns_opt_copy_region(struct edns_option* list,
+ struct regional* region);
+
++/**
++ * Copy a filtered edns option list allocated to the new region
++ */
++struct edns_option* edns_opt_copy_filter_region(struct edns_option* list,
++ uint16_t* filter_list, size_t filter_list_len, struct regional* region);
++
+ /**
+ * Copy edns option list allocated with malloc
+ */
+--- contrib/unbound/util/edns.c.orig
++++ contrib/unbound/util/edns.c
+@@ -45,8 +45,11 @@
+ #include "util/netevent.h"
+ #include "util/net_help.h"
+ #include "util/regional.h"
++#include "util/rfc_1982.h"
++#include "util/siphash.h"
+ #include "util/data/msgparse.h"
+ #include "util/data/msgreply.h"
++#include "sldns/sbuffer.h"
+
+ #if 0
+ /* XXX: remove me */
+@@ -133,3 +136,59 @@
+ return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen);
+ }
+
++uint8_t*
++edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4,
++ uint8_t* hash)
++{
++ v4?siphash(in, 20, secret, hash, 8):siphash(in, 32, secret, hash, 8);
++ return hash;
++}
++
++void
++edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
++ uint32_t timestamp)
++{
++ uint8_t hash[8];
++ buf[ 8] = 1; /* Version */
++ buf[ 9] = 0; /* Reserved */
++ buf[10] = 0; /* Reserved */
++ buf[11] = 0; /* Reserved */
++ sldns_write_uint32(buf + 12, timestamp);
++ (void)edns_cookie_server_hash(buf, secret, v4, hash);
++ memcpy(buf + 16, hash, 8);
++}
++
++enum edns_cookie_val_status
++edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
++ const uint8_t* secret, size_t secret_len, int v4,
++ const uint8_t* hash_input, uint32_t now)
++{
++ uint8_t hash[8];
++ uint32_t timestamp;
++ uint32_t subt_1982 = 0; /* Initialize for the compiler; unused value */
++ int comp_1982;
++ if(cookie_len != 24)
++ /* RFC9018 cookies are 24 bytes long */
++ return COOKIE_STATUS_CLIENT_ONLY;
++ if(secret_len != 16 || /* RFC9018 cookies have 16 byte secrets */
++ cookie[8] != 1) /* RFC9018 cookies are cookie version 1 */
++ return COOKIE_STATUS_INVALID;
++ timestamp = sldns_read_uint32(cookie + 12);
++ if((comp_1982 = compare_1982(now, timestamp)) > 0
++ && (subt_1982 = subtract_1982(timestamp, now)) > 3600)
++ /* Cookie is older than 1 hour (see RFC9018 Section 4.3.) */
++ return COOKIE_STATUS_EXPIRED;
++ if(comp_1982 <= 0 && subtract_1982(now, timestamp) > 300)
++ /* Cookie time is more than 5 minutes in the future.
++ * (see RFC9018 Section 4.3.) */
++ return COOKIE_STATUS_FUTURE;
++ if(memcmp(edns_cookie_server_hash(hash_input, secret, v4, hash),
++ cookie + 16, 8) != 0)
++ /* Hashes do not match */
++ return COOKIE_STATUS_INVALID;
++ if(comp_1982 > 0 && subt_1982 > 1800)
++ /* Valid cookie but older than 30 minutes, so create a new one
++ * anyway */
++ return COOKIE_STATUS_VALID_RENEW;
++ return COOKIE_STATUS_VALID;
++}
+--- contrib/unbound/util/edns.h.orig
++++ contrib/unbound/util/edns.h
+@@ -75,6 +75,15 @@
+ size_t string_len;
+ };
+
++enum edns_cookie_val_status {
++ COOKIE_STATUS_CLIENT_ONLY = -3,
++ COOKIE_STATUS_FUTURE = -2,
++ COOKIE_STATUS_EXPIRED = -1,
++ COOKIE_STATUS_INVALID = 0,
++ COOKIE_STATUS_VALID = 1,
++ COOKIE_STATUS_VALID_RENEW = 2,
++};
++
+ /**
+ * Create structure to hold EDNS strings
+ * @return: newly created edns_strings, NULL on alloc failure.
+@@ -106,4 +115,54 @@
+ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
+ socklen_t addrlen);
+
++/**
++ * Compute the interoperable DNS cookie (RFC9018) hash.
++ * @param in: buffer input for the hash generation. It needs to be:
++ * Client Cookie | Version | Reserved | Timestamp | Client-IP
++ * @param secret: the server secret; implicit length of 16 octets.
++ * @param v4: if the client IP is v4 or v6.
++ * @param hash: buffer to write the hash to.
++ * return a pointer to the hash.
++ */
++uint8_t* edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret,
++ int v4, uint8_t* hash);
++
++/**
++ * Write an interoperable DNS server cookie (RFC9018).
++ * @param buf: buffer to write to. It should have a size of at least 32 octets
++ * as it doubles as the output buffer and the hash input buffer.
++ * The first 8 octets are expected to be the Client Cookie and will be
++ * left untouched.
++ * The next 8 octets will be written with Version | Reserved | Timestamp.
++ * The next 4 or 16 octets are expected to be the IPv4 or the IPv6 address
++ * based on the v4 flag.
++ * Thus the first 20 or 32 octets, based on the v4 flag, will be used as
++ * the hash input.
++ * The server hash (8 octets) will be written after the first 16 octets;
++ * overwriting the address information.
++ * The caller expects a complete, 24 octet long cookie in the buffer.
++ * @param secret: the server secret; implicit length of 16 octets.
++ * @param v4: if the client IP is v4 or v6.
++ * @param timestamp: the timestamp to use.
++ */
++void edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
++ uint32_t timestamp);
++
++/**
++ * Validate an interoperable DNS cookie (RFC9018).
++ * @param cookie: pointer to the cookie data.
++ * @param cookie_len: the length of the cookie data.
++ * @param secret: pointer to the server secret.
++ * @param secret_len: the length of the secret.
++ * @param v4: if the client IP is v4 or v6.
++ * @param hash_input: pointer to the hash input for validation. It needs to be:
++ * Client Cookie | Version | Reserved | Timestamp | Client-IP
++ * @param now: the current time.
++ * return edns_cookie_val_status with the cookie validation status i.e.,
++ * <=0 for invalid, else valid.
++ */
++enum edns_cookie_val_status edns_cookie_server_validate(const uint8_t* cookie,
++ size_t cookie_len, const uint8_t* secret, size_t secret_len, int v4,
++ const uint8_t* hash_input, uint32_t now);
++
+ #endif
+--- contrib/unbound/util/fptr_wlist.c.orig
++++ contrib/unbound/util/fptr_wlist.c
+@@ -131,6 +131,7 @@
+ else if(fptr == &pending_udp_timer_delay_cb) return 1;
+ else if(fptr == &worker_stat_timer_cb) return 1;
+ else if(fptr == &worker_probe_timer_cb) return 1;
++ else if(fptr == &validate_suspend_timer_cb) return 1;
+ #ifdef UB_ON_WINDOWS
+ else if(fptr == &wsvc_cron_cb) return 1;
+ #endif
+@@ -168,7 +169,9 @@
+ fptr_whitelist_event(void (*fptr)(int, short, void *))
+ {
+ if(fptr == &comm_point_udp_callback) return 1;
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
+ else if(fptr == &comm_point_udp_ancil_callback) return 1;
++#endif
+ else if(fptr == &comm_point_tcp_accept_callback) return 1;
+ else if(fptr == &comm_point_tcp_handle_callback) return 1;
+ else if(fptr == &comm_timer_callback) return 1;
+@@ -659,6 +662,10 @@
+ #else
+ (void)fptr;
+ #endif
++#ifdef WITH_PYTHONMODULE
++ if(fptr == &python_inplace_cb_edns_back_parsed_call)
++ return 1;
++#endif
+ #ifdef WITH_DYNLIBMODULE
+ if(fptr == &dynlib_inplace_cb_edns_back_parsed)
+ return 1;
+@@ -675,6 +682,10 @@
+ #else
+ (void)fptr;
+ #endif
++#ifdef WITH_PYTHONMODULE
++ if(fptr == &python_inplace_cb_query_response)
++ return 1;
++#endif
+ #ifdef WITH_DYNLIBMODULE
+ if(fptr == &dynlib_inplace_cb_query_response)
+ return 1;
+--- contrib/unbound/util/iana_ports.inc.orig
++++ contrib/unbound/util/iana_ports.inc
+@@ -674,6 +674,8 @@
+ 911,
+ 912,
+ 913,
++914,
++915,
+ 989,
+ 990,
+ 991,
+@@ -1901,6 +1903,7 @@
+ 2256,
+ 2257,
+ 2258,
++2259,
+ 2260,
+ 2261,
+ 2262,
+@@ -2010,6 +2013,7 @@
+ 2366,
+ 2367,
+ 2368,
++2369,
+ 2370,
+ 2372,
+ 2378,
+--- contrib/unbound/util/log.c.orig
++++ contrib/unbound/util/log.c
+@@ -187,7 +187,7 @@
+ default_ident = id;
+ }
+
+-void log_ident_revert_to_default()
++void log_ident_revert_to_default(void)
+ {
+ ident = default_ident;
+ }
+--- contrib/unbound/util/module.c.orig
++++ contrib/unbound/util/module.c
+@@ -84,8 +84,10 @@
+ const char* str, sldns_ede_code reason_bogus)
+ {
+ struct errinf_strlist* p;
+- if((qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) || !str)
++ if(!str || (qstate->env->cfg->val_log_level < 2 &&
++ !qstate->env->cfg->log_servfail)) {
+ return;
++ }
+ p = (struct errinf_strlist*)regional_alloc(qstate->region, sizeof(*p));
+ if(!p) {
+ log_err("malloc failure in validator-error-info string");
+@@ -152,15 +154,19 @@
+ return p;
+ }
+
++/* Try to find the latest (most specific) dnssec failure */
+ sldns_ede_code errinf_to_reason_bogus(struct module_qstate* qstate)
+ {
+ struct errinf_strlist* s;
++ sldns_ede_code ede = LDNS_EDE_NONE;
+ for(s=qstate->errinf; s; s=s->next) {
+- if (s->reason_bogus != LDNS_EDE_NONE) {
+- return s->reason_bogus;
+- }
++ if(s->reason_bogus == LDNS_EDE_NONE) continue;
++ if(ede != LDNS_EDE_NONE
++ && ede != LDNS_EDE_DNSSEC_BOGUS
++ && s->reason_bogus == LDNS_EDE_DNSSEC_BOGUS) continue;
++ ede = s->reason_bogus;
+ }
+- return LDNS_EDE_NONE;
++ return ede;
+ }
+
+ char* errinf_to_str_servfail(struct module_qstate* qstate)
+@@ -188,6 +194,24 @@
+ return p;
+ }
+
++char* errinf_to_str_misc(struct module_qstate* qstate)
++{
++ char buf[20480];
++ char* p = buf;
++ size_t left = sizeof(buf);
++ struct errinf_strlist* s;
++ if(!qstate->errinf)
++ snprintf(p, left, "misc failure");
++ else for(s=qstate->errinf; s; s=s->next) {
++ snprintf(p, left, "%s%s", (s==qstate->errinf?"":" "), s->str);
++ left -= strlen(p); p += strlen(p);
++ }
++ p = strdup(buf);
++ if(!p)
++ log_err("malloc failure in errinf_to_str");
++ return p;
++}
++
+ void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr)
+ {
+ char buf[1024];
+--- contrib/unbound/util/module.h.orig
++++ contrib/unbound/util/module.h
+@@ -619,6 +619,12 @@
+ /** if this is a validation recursion query that does not get
+ * validation itself */
+ int is_valrec;
++#ifdef CLIENT_SUBNET
++ /** the client network address is needed for the client-subnet option
++ * when prefetching, but we can't use reply_list in mesh_info, because
++ * we don't want to send a reply for the internal query. */
++ struct sockaddr_storage client_addr;
++#endif
+
+ /** comm_reply contains server replies */
+ struct comm_reply* reply;
+@@ -671,6 +677,8 @@
+ * those servers. By comparing expiry time with qstarttime for type NS.
+ */
+ time_t qstarttime;
++ /** whether a message from cachedb will be used for the reply */
++ int is_cachedb_answer;
+
+ /**
+ * Attributes of clients that share the qstate that may affect IP-based
+@@ -683,6 +691,8 @@
+ struct respip_action_info* respip_action_info;
+ /** if the query is rpz passthru, no further rpz processing for it */
+ int rpz_passthru;
++ /* Flag tcp required. */
++ int tcp_required;
+
+ /** whether the reply should be dropped */
+ int is_drop;
+@@ -818,11 +828,11 @@
+ * This string is malloced and has to be freed by caller.
+ */
+ char* errinf_to_str_bogus(struct module_qstate* qstate);
++
+ /**
+- * Check the sldns_ede_code of the qstate.
++ * Check the sldns_ede_code of the qstate->errinf.
+ * @param qstate: query state.
+- * @return LDNS_EDE_DNSSEC_BOGUS by default, or the first explicitly set
+- * sldns_ede_code.
++ * @return the latest explicitly set sldns_ede_code or LDNS_EDE_NONE.
+ */
+ sldns_ede_code errinf_to_reason_bogus(struct module_qstate* qstate);
+
+@@ -834,6 +844,14 @@
+ */
+ char* errinf_to_str_servfail(struct module_qstate* qstate);
+
++/**
++ * Create error info in string. For misc failures that are not servfail.
++ * @param qstate: query state.
++ * @return string or NULL on malloc failure (already logged).
++ * This string is malloced and has to be freed by caller.
++ */
++char* errinf_to_str_misc(struct module_qstate* qstate);
++
+ /**
+ * Initialize the edns known options by allocating the required space.
+ * @param env: the module environment.
+--- contrib/unbound/util/net_help.c.orig
++++ contrib/unbound/util/net_help.c
+@@ -779,8 +779,8 @@
+ return match;
+ }
+
+-void
+-addr_to_str(struct sockaddr_storage* addr, socklen_t addrlen,
++void
++addr_to_str(struct sockaddr_storage* addr, socklen_t addrlen,
+ char* buf, size_t len)
+ {
+ int af = (int)((struct sockaddr_in*)addr)->sin_family;
+@@ -792,7 +792,51 @@
+ }
+ }
+
+-int
++int
++prefixnet_is_nat64(int prefixnet)
++{
++ return (prefixnet == 32 || prefixnet == 40 ||
++ prefixnet == 48 || prefixnet == 56 ||
++ prefixnet == 64 || prefixnet == 96);
++}
++
++void
++addr_to_nat64(const struct sockaddr_storage* addr,
++ const struct sockaddr_storage* nat64_prefix,
++ socklen_t nat64_prefixlen, int nat64_prefixnet,
++ struct sockaddr_storage* nat64_addr, socklen_t* nat64_addrlen)
++{
++ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
++ struct sockaddr_in6 *sin6;
++ uint8_t *v4_byte;
++ int i;
++
++ /* This needs to be checked by the caller */
++ log_assert(addr->ss_family == AF_INET);
++ /* Current usage is only from config values; prefix lengths enforced
++ * during config validation */
++ log_assert(prefixnet_is_nat64(nat64_prefixnet));
++
++ *nat64_addr = *nat64_prefix;
++ *nat64_addrlen = nat64_prefixlen;
++
++ sin6 = (struct sockaddr_in6 *)nat64_addr;
++ sin6->sin6_flowinfo = 0;
++ sin6->sin6_port = sin->sin_port;
++
++ nat64_prefixnet = nat64_prefixnet / 8;
++
++ v4_byte = (uint8_t *)&sin->sin_addr.s_addr;
++ for(i = 0; i < 4; i++) {
++ if(nat64_prefixnet == 8) {
++ /* bits 64...71 are MBZ */
++ sin6->sin6_addr.s6_addr[nat64_prefixnet++] = 0;
++ }
++ sin6->sin6_addr.s6_addr[nat64_prefixnet++] = *v4_byte++;
++ }
++}
++
++int
+ addr_is_ip4mapped(struct sockaddr_storage* addr, socklen_t addrlen)
+ {
+ /* prefix for ipv4 into ipv6 mapping is ::ffff:x.x.x.x */
+@@ -908,6 +952,111 @@
+ #endif /* HAVE_SSL */
+ }
+
++#ifdef HAVE_SSL
++/** Print crypt erro with SSL_get_error want code and err_get_error code */
++static void log_crypto_err_io_code_arg(const char* str, int r,
++ unsigned long err, int err_present)
++{
++ int print_errno = 0, print_crypto_err = 0;
++ const char* inf = NULL;
++
++ switch(r) {
++ case SSL_ERROR_NONE:
++ inf = "no error";
++ break;
++ case SSL_ERROR_ZERO_RETURN:
++ inf = "channel closed";
++ break;
++ case SSL_ERROR_WANT_READ:
++ inf = "want read";
++ break;
++ case SSL_ERROR_WANT_WRITE:
++ inf = "want write";
++ break;
++ case SSL_ERROR_WANT_CONNECT:
++ inf = "want connect";
++ break;
++ case SSL_ERROR_WANT_ACCEPT:
++ inf = "want accept";
++ break;
++ case SSL_ERROR_WANT_X509_LOOKUP:
++ inf = "want X509 lookup";
++ break;
++#ifdef SSL_ERROR_WANT_ASYNC
++ case SSL_ERROR_WANT_ASYNC:
++ inf = "want async";
++ break;
++#endif
++#ifdef SSL_ERROR_WANT_ASYNC_JOB
++ case SSL_ERROR_WANT_ASYNC_JOB:
++ inf = "want async job";
++ break;
++#endif
++#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
++ case SSL_ERROR_WANT_CLIENT_HELLO_CB:
++ inf = "want client hello cb";
++ break;
++#endif
++ case SSL_ERROR_SYSCALL:
++ print_errno = 1;
++ inf = "syscall";
++ break;
++ case SSL_ERROR_SSL:
++ print_crypto_err = 1;
++ inf = "SSL, usually protocol, error";
++ break;
++ default:
++ inf = "unknown SSL_get_error result code";
++ print_errno = 1;
++ print_crypto_err = 1;
++ }
++ if(print_crypto_err) {
++ if(print_errno) {
++ char buf[1024];
++ snprintf(buf, sizeof(buf), "%s with errno %s",
++ str, strerror(errno));
++ if(err_present)
++ log_crypto_err_code(buf, err);
++ else log_crypto_err(buf);
++ } else {
++ if(err_present)
++ log_crypto_err_code(str, err);
++ else log_crypto_err(str);
++ }
++ } else {
++ if(print_errno) {
++ if(errno == 0)
++ log_err("str: syscall error with errno %s",
++ strerror(errno));
++ else log_err("str: %s", strerror(errno));
++ } else {
++ log_err("str: %s", inf);
++ }
++ }
++}
++#endif /* HAVE_SSL */
++
++void log_crypto_err_io(const char* str, int r)
++{
++#ifdef HAVE_SSL
++ log_crypto_err_io_code_arg(str, r, 0, 0);
++#else
++ (void)str;
++ (void)r;
++#endif /* HAVE_SSL */
++}
++
++void log_crypto_err_io_code(const char* str, int r, unsigned long err)
++{
++#ifdef HAVE_SSL
++ log_crypto_err_io_code_arg(str, r, err, 1);
++#else
++ (void)str;
++ (void)r;
++ (void)err;
++#endif /* HAVE_SSL */
++}
++
+ #ifdef HAVE_SSL
+ /** log certificate details */
+ void
+@@ -1005,6 +1154,16 @@
+ log_crypto_err("could not set cipher list with SSL_CTX_set_cipher_list");
+ }
+ #endif
++#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF)
++ /* ignore errors when peers do not send the mandatory close_notify
++ * alert on shutdown.
++ * Relevant for openssl >= 3 */
++ if((SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF) &
++ SSL_OP_IGNORE_UNEXPECTED_EOF) != SSL_OP_IGNORE_UNEXPECTED_EOF) {
++ log_crypto_err("could not set SSL_OP_IGNORE_UNEXPECTED_EOF");
++ return 0;
++ }
++#endif
+
+ if((SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE) &
+ SSL_OP_CIPHER_SERVER_PREFERENCE) !=
+@@ -1233,6 +1392,17 @@
+ SSL_CTX_free(ctx);
+ return 0;
+ }
++#endif
++#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF)
++ /* ignore errors when peers do not send the mandatory close_notify
++ * alert on shutdown.
++ * Relevant for openssl >= 3 */
++ if((SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF) &
++ SSL_OP_IGNORE_UNEXPECTED_EOF) != SSL_OP_IGNORE_UNEXPECTED_EOF) {
++ log_crypto_err("could not set SSL_OP_IGNORE_UNEXPECTED_EOF");
++ SSL_CTX_free(ctx);
++ return 0;
++ }
+ #endif
+ if(key && key[0]) {
+ if(!SSL_CTX_use_certificate_chain_file(ctx, pem)) {
+--- contrib/unbound/util/net_help.h.orig
++++ contrib/unbound/util/net_help.h
+@@ -331,6 +331,29 @@
+ void addr_to_str(struct sockaddr_storage* addr, socklen_t addrlen,
+ char* buf, size_t len);
+
++/**
++ * Check if the prefix network length is one of the allowed 32, 40, 48, 56, 64,
++ * or 96.
++ * @param prefixnet: prefix network length to check.
++ * @return 1 on success, 0 on failure.
++ */
++int prefixnet_is_nat64(int prefixnet);
++
++/**
++ * Create a NAT64 address from a given address (needs to be IPv4) and a given
++ * NAT64 prefix. The NAT64 prefix net needs to be one of 32, 40, 48, 56, 64, 96.
++ * @param addr: IPv4 address.
++ * @param nat64_prefix: NAT64 prefix.
++ * @param nat64_prefixlen: NAT64 prefix len.
++ * @param nat64_prefixnet: NAT64 prefix mask.
++ * @param nat64_addr: the resulting NAT64 address.
++ * @param nat64_addrlen: the resulting NAT64 address length.
++ */
++void addr_to_nat64(const struct sockaddr_storage* addr,
++ const struct sockaddr_storage* nat64_prefix,
++ socklen_t nat64_prefixlen, int nat64_prefixnet,
++ struct sockaddr_storage* nat64_addr, socklen_t* nat64_addrlen);
++
+ /**
+ * See if sockaddr is an ipv6 mapped ipv4 address, "::ffff:0.0.0.0"
+ * @param addr: address
+@@ -406,6 +429,24 @@
+ */
+ void log_crypto_err_code(const char* str, unsigned long err);
+
++/**
++ * Log an error from libcrypto that came from SSL_write and so on, with
++ * a value from SSL_get_error, calls log_err. If that fails it logs with
++ * log_crypto_err.
++ * @param str: what failed
++ * @param r: output of SSL_get_error on the I/O operation result.
++ */
++void log_crypto_err_io(const char* str, int r);
++
++/**
++ * Log an error from libcrypt that came from an I/O routine with the
++ * errcode from ERR_get_error. Calls log_err() and log_crypto_err_code.
++ * @param str: what failed
++ * @param r: output of SSL_get_error on the I/O operation result.
++ * @param err: error code from ERR_get_error
++ */
++void log_crypto_err_io_code(const char* str, int r, unsigned long err);
++
+ /**
+ * Log certificate details verbosity, string, of X509 cert
+ * @param level: verbosity level
+--- contrib/unbound/util/netevent.c.orig
++++ contrib/unbound/util/netevent.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -46,6 +46,7 @@
+ #include "util/tcp_conn_limit.h"
+ #include "util/fptr_wlist.h"
+ #include "util/proxy_protocol.h"
++#include "util/timeval_func.h"
+ #include "sldns/pkthdr.h"
+ #include "sldns/sbuffer.h"
+ #include "sldns/str2wire.h"
+@@ -71,7 +72,9 @@
+ #ifdef HAVE_OPENSSL_ERR_H
+ #include <openssl/err.h>
+ #endif
+-
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++#include <linux/net_tstamp.h>
++#endif
+ /* -------- Start of local definitions -------- */
+ /** if CMSG_ALIGN is not defined on this platform, a workaround */
+ #ifndef CMSG_ALIGN
+@@ -113,7 +116,19 @@
+
+ /** timeout in millisec to wait for write to unblock, packets dropped after.*/
+ #define SEND_BLOCKED_WAIT_TIMEOUT 200
++/** max number of times to wait for write to unblock, packets dropped after.*/
++#define SEND_BLOCKED_MAX_RETRY 5
+
++/** Let's make timestamping code cleaner and redefine SO_TIMESTAMP* */
++#ifndef SO_TIMESTAMP
++#define SO_TIMESTAMP 29
++#endif
++#ifndef SO_TIMESTAMPNS
++#define SO_TIMESTAMPNS 35
++#endif
++#ifndef SO_TIMESTAMPING
++#define SO_TIMESTAMPING 37
++#endif
+ /**
+ * The internal event structure for keeping ub_event info for the event.
+ * Possibly other structures (list, tree) this is part of.
+@@ -177,7 +192,7 @@
+
+ /* -------- End of local definitions -------- */
+
+-struct comm_base*
++struct comm_base*
+ comm_base_create(int sigs)
+ {
+ struct comm_base* b = (struct comm_base*)calloc(1,
+@@ -220,7 +235,7 @@
+ return b;
+ }
+
+-void
++void
+ comm_base_delete(struct comm_base* b)
+ {
+ if(!b)
+@@ -237,7 +252,7 @@
+ free(b);
+ }
+
+-void
++void
+ comm_base_delete_no_base(struct comm_base* b)
+ {
+ if(!b)
+@@ -253,14 +268,14 @@
+ free(b);
+ }
+
+-void
++void
+ comm_base_timept(struct comm_base* b, time_t** tt, struct timeval** tv)
+ {
+ *tt = &b->eb->secs;
+ *tv = &b->eb->now;
+ }
+
+-void
++void
+ comm_base_dispatch(struct comm_base* b)
+ {
+ int retval;
+@@ -389,9 +404,10 @@
+ WSAGetLastError() == WSAENOBUFS ||
+ WSAGetLastError() == WSAEWOULDBLOCK) {
+ #endif
++ int retries = 0;
+ /* if we set the fd blocking, other threads suddenly
+ * have a blocking fd that they operate on */
+- while(sent == -1 && (
++ while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
+ #ifndef USE_WINSOCK
+ errno == EAGAIN || errno == EINTR ||
+ # ifdef EWOULDBLOCK
+@@ -406,6 +422,13 @@
+ #endif
+ )) {
+ #if defined(HAVE_POLL) || defined(USE_WINSOCK)
++ int send_nobufs = (
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ );
+ struct pollfd p;
+ int pret;
+ memset(&p, 0, sizeof(p));
+@@ -444,8 +467,48 @@
+ log_err("poll udp out failed: %s",
+ sock_strerror(errno));
+ return 0;
++ } else if((pret < 0 &&
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ ) || (send_nobufs && retries > 0)) {
++ /* ENOBUFS, and poll returned without
++ * a timeout. Or the retried send call
++ * returned ENOBUFS. It is good to
++ * wait a bit for the error to clear. */
++ /* The timeout is 20*(2^(retries+1)),
++ * it increases exponentially, starting
++ * at 40 msec. After 5 tries, 1240 msec
++ * have passed in total, when poll
++ * returned the error, and 1200 msec
++ * when send returned the errors. */
++#ifndef USE_WINSOCK
++ pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#else
++ pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#endif
++ if(pret < 0 &&
++#ifndef USE_WINSOCK
++ errno != EAGAIN && errno != EINTR &&
++# ifdef EWOULDBLOCK
++ errno != EWOULDBLOCK &&
++# endif
++ errno != ENOBUFS
++#else
++ WSAGetLastError() != WSAEINPROGRESS &&
++ WSAGetLastError() != WSAEINTR &&
++ WSAGetLastError() != WSAENOBUFS &&
++ WSAGetLastError() != WSAEWOULDBLOCK
++#endif
++ ) {
++ log_err("poll udp out timer failed: %s",
++ sock_strerror(errno));
++ }
+ }
+ #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
++ retries++;
+ if (!is_connected) {
+ sent = sendto(c->fd, (void*)sldns_buffer_begin(packet),
+ sldns_buffer_remaining(packet), 0,
+@@ -470,7 +533,7 @@
+ (struct sockaddr_storage*)addr, addrlen);
+ return 0;
+ } else if((size_t)sent != sldns_buffer_remaining(packet)) {
+- log_err("sent %d in place of %d bytes",
++ log_err("sent %d in place of %d bytes",
+ (int)sent, (int)sldns_buffer_remaining(packet));
+ return 0;
+ }
+@@ -489,7 +552,7 @@
+ if(r->srctype == 6) {
+ #ifdef IPV6_PKTINFO
+ char buf[1024];
+- if(inet_ntop(AF_INET6, &r->pktinfo.v6info.ipi6_addr,
++ if(inet_ntop(AF_INET6, &r->pktinfo.v6info.ipi6_addr,
+ buf, (socklen_t)sizeof(buf)) == 0) {
+ (void)strlcpy(buf, "(inet_ntop error)", sizeof(buf));
+ }
+@@ -499,13 +562,13 @@
+ } else if(r->srctype == 4) {
+ #ifdef IP_PKTINFO
+ char buf1[1024], buf2[1024];
+- if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_addr,
++ if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_addr,
+ buf1, (socklen_t)sizeof(buf1)) == 0) {
+ (void)strlcpy(buf1, "(inet_ntop error)", sizeof(buf1));
+ }
+ buf1[sizeof(buf1)-1]=0;
+ #ifdef HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST
+- if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_spec_dst,
++ if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_spec_dst,
+ buf2, (socklen_t)sizeof(buf2)) == 0) {
+ (void)strlcpy(buf2, "(inet_ntop error)", sizeof(buf2));
+ }
+@@ -517,7 +580,7 @@
+ buf1, buf2);
+ #elif defined(IP_RECVDSTADDR)
+ char buf1[1024];
+- if(inet_ntop(AF_INET, &r->pktinfo.v4addr,
++ if(inet_ntop(AF_INET, &r->pktinfo.v4addr,
+ buf1, (socklen_t)sizeof(buf1)) == 0) {
+ (void)strlcpy(buf1, "(inet_ntop error)", sizeof(buf1));
+ }
+@@ -531,7 +594,7 @@
+ /** send a UDP reply over specified interface*/
+ static int
+ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
+- struct sockaddr* addr, socklen_t addrlen, struct comm_reply* r)
++ struct sockaddr* addr, socklen_t addrlen, struct comm_reply* r)
+ {
+ #if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_SENDMSG)
+ ssize_t sent;
+@@ -579,6 +642,11 @@
+ cmsg_data = CMSG_DATA(cmsg);
+ ((struct in_pktinfo *) cmsg_data)->ipi_ifindex = 0;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in_pktinfo) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in_pktinfo), 0, cmsg->cmsg_len
++ - sizeof(struct in_pktinfo));
+ #elif defined(IP_SENDSRCADDR)
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
+ log_assert(msg.msg_controllen <= sizeof(control.buf));
+@@ -587,6 +655,11 @@
+ memmove(CMSG_DATA(cmsg), &r->pktinfo.v4addr,
+ sizeof(struct in_addr));
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in_addr) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in_addr), 0, cmsg->cmsg_len
++ - sizeof(struct in_addr));
+ #else
+ verbose(VERB_ALGO, "no IP_PKTINFO or IP_SENDSRCADDR");
+ msg.msg_control = NULL;
+@@ -603,6 +676,11 @@
+ cmsg_data = CMSG_DATA(cmsg);
+ ((struct in6_pktinfo *) cmsg_data)->ipi6_ifindex = 0;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in6_pktinfo) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in6_pktinfo), 0, cmsg->cmsg_len
++ - sizeof(struct in6_pktinfo));
+ } else {
+ /* try to pass all 0 to use default route */
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+@@ -611,9 +689,14 @@
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ memset(CMSG_DATA(cmsg), 0, sizeof(struct in6_pktinfo));
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in6_pktinfo) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in6_pktinfo), 0, cmsg->cmsg_len
++ - sizeof(struct in6_pktinfo));
+ }
+ #endif /* S_SPLINT_S */
+- if(verbosity >= VERB_ALGO)
++ if(verbosity >= VERB_ALGO && r->srctype != 0)
+ p_ancil("send_udp over interface", r);
+ sent = sendmsg(c->fd, &msg, 0);
+ if(sent == -1) {
+@@ -632,7 +715,8 @@
+ WSAGetLastError() == WSAENOBUFS ||
+ WSAGetLastError() == WSAEWOULDBLOCK) {
+ #endif
+- while(sent == -1 && (
++ int retries = 0;
++ while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
+ #ifndef USE_WINSOCK
+ errno == EAGAIN || errno == EINTR ||
+ # ifdef EWOULDBLOCK
+@@ -647,6 +731,13 @@
+ #endif
+ )) {
+ #if defined(HAVE_POLL) || defined(USE_WINSOCK)
++ int send_nobufs = (
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ );
+ struct pollfd p;
+ int pret;
+ memset(&p, 0, sizeof(p));
+@@ -685,8 +776,48 @@
+ log_err("poll udp out failed: %s",
+ sock_strerror(errno));
+ return 0;
++ } else if((pret < 0 &&
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ ) || (send_nobufs && retries > 0)) {
++ /* ENOBUFS, and poll returned without
++ * a timeout. Or the retried send call
++ * returned ENOBUFS. It is good to
++ * wait a bit for the error to clear. */
++ /* The timeout is 20*(2^(retries+1)),
++ * it increases exponentially, starting
++ * at 40 msec. After 5 tries, 1240 msec
++ * have passed in total, when poll
++ * returned the error, and 1200 msec
++ * when send returned the errors. */
++#ifndef USE_WINSOCK
++ pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#else
++ pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#endif
++ if(pret < 0 &&
++#ifndef USE_WINSOCK
++ errno != EAGAIN && errno != EINTR &&
++# ifdef EWOULDBLOCK
++ errno != EWOULDBLOCK &&
++# endif
++ errno != ENOBUFS
++#else
++ WSAGetLastError() != WSAEINPROGRESS &&
++ WSAGetLastError() != WSAEINTR &&
++ WSAGetLastError() != WSAENOBUFS &&
++ WSAGetLastError() != WSAEWOULDBLOCK
++#endif
++ ) {
++ log_err("poll udp out timer failed: %s",
++ sock_strerror(errno));
++ }
+ }
+ #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
++ retries++;
+ sent = sendmsg(c->fd, &msg, 0);
+ }
+ }
+@@ -695,7 +826,7 @@
+ if(!udp_send_errno_needs_log(addr, addrlen))
+ return 0;
+ verbose(VERB_OPS, "sendmsg failed: %s", strerror(errno));
+- log_addr(VERB_OPS, "remote address is",
++ log_addr(VERB_OPS, "remote address is",
+ (struct sockaddr_storage*)addr, addrlen);
+ #ifdef __NetBSD__
+ /* netbsd 7 has IP_PKTINFO for recv but not send */
+@@ -705,7 +836,7 @@
+ #endif
+ return 0;
+ } else if((size_t)sent != sldns_buffer_remaining(packet)) {
+- log_err("sent %d in place of %d bytes",
++ log_err("sent %d in place of %d bytes",
+ (int)sent, (int)sldns_buffer_remaining(packet));
+ return 0;
+ }
+@@ -761,15 +892,18 @@
+ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
+ int stream) {
+ size_t size;
+- struct pp2_header *header = pp2_read_header(buf);
+- if(header == NULL) return 0;
++ struct pp2_header *header;
++ int err = pp2_read_header(sldns_buffer_begin(buf),
++ sldns_buffer_remaining(buf));
++ if(err) return 0;
++ header = (struct pp2_header*)sldns_buffer_begin(buf);
+ size = PP2_HEADER_SIZE + ntohs(header->len);
+ if((header->ver_cmd & 0xF) == PP2_CMD_LOCAL) {
+ /* A connection from the proxy itself.
+ * No need to do anything with addresses. */
+ goto done;
+ }
+- if(header->fam_prot == 0x00) {
++ if(header->fam_prot == PP2_UNSPEC_UNSPEC) {
+ /* Unspecified family and protocol. This could be used for
+ * health checks by proxies.
+ * No need to do anything with addresses. */
+@@ -777,8 +911,8 @@
+ }
+ /* Read the proxied address */
+ switch(header->fam_prot) {
+- case 0x11: /* AF_INET|STREAM */
+- case 0x12: /* AF_INET|DGRAM */
++ case PP2_INET_STREAM:
++ case PP2_INET_DGRAM:
+ {
+ struct sockaddr_in* addr =
+ (struct sockaddr_in*)&rep->client_addr;
+@@ -789,8 +923,8 @@
+ }
+ /* Ignore the destination address; it should be us. */
+ break;
+- case 0x21: /* AF_INET6|STREAM */
+- case 0x22: /* AF_INET6|DGRAM */
++ case PP2_INET6_STREAM:
++ case PP2_INET6_DGRAM:
+ {
+ struct sockaddr_in6* addr =
+ (struct sockaddr_in6*)&rep->client_addr;
+@@ -803,6 +937,10 @@
+ }
+ /* Ignore the destination address; it should be us. */
+ break;
++ default:
++ log_err("proxy_protocol: unsupported family and "
++ "protocol 0x%x", (int)header->fam_prot);
++ return 0;
+ }
+ rep->is_proxied = 1;
+ done:
+@@ -817,10 +955,10 @@
+ return 1;
+ }
+
+-void
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
++void
+ comm_point_udp_ancil_callback(int fd, short event, void* arg)
+ {
+-#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
+ struct comm_reply rep;
+ struct msghdr msg;
+ struct iovec iov[1];
+@@ -833,6 +971,9 @@
+ #ifndef S_SPLINT_S
+ struct cmsghdr* cmsg;
+ #endif /* S_SPLINT_S */
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++ struct timespec *ts;
++#endif /* HAVE_LINUX_NET_TSTAMP_H */
+
+ rep.c = (struct comm_point*)arg;
+ log_assert(rep.c->type == comm_udp);
+@@ -843,6 +984,7 @@
+ ub_comm_base_now(rep.c->ev->base);
+ for(i=0; i<NUM_UDP_PER_SELECT; i++) {
+ sldns_buffer_clear(rep.c->buffer);
++ timeval_clear(&rep.c->recv_tv);
+ rep.remote_addrlen = (socklen_t)sizeof(rep.remote_addr);
+ log_assert(fd != -1);
+ log_assert(sldns_buffer_remaining(rep.c->buffer) > 0);
+@@ -894,9 +1036,23 @@
+ sizeof(struct in_addr));
+ break;
+ #endif /* IP_PKTINFO or IP_RECVDSTADDR */
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++ } else if( cmsg->cmsg_level == SOL_SOCKET &&
++ cmsg->cmsg_type == SO_TIMESTAMPNS) {
++ ts = (struct timespec *)CMSG_DATA(cmsg);
++ TIMESPEC_TO_TIMEVAL(&rep.c->recv_tv, ts);
++ } else if( cmsg->cmsg_level == SOL_SOCKET &&
++ cmsg->cmsg_type == SO_TIMESTAMPING) {
++ ts = (struct timespec *)CMSG_DATA(cmsg);
++ TIMESPEC_TO_TIMEVAL(&rep.c->recv_tv, ts);
++ } else if( cmsg->cmsg_level == SOL_SOCKET &&
++ cmsg->cmsg_type == SO_TIMESTAMP) {
++ memmove(&rep.c->recv_tv, CMSG_DATA(cmsg), sizeof(struct timeval));
++#endif /* HAVE_LINUX_NET_TSTAMP_H */
+ }
+ }
+- if(verbosity >= VERB_ALGO)
++
++ if(verbosity >= VERB_ALGO && rep.srctype != 0)
+ p_ancil("receive_udp on interface", &rep);
+ #endif /* S_SPLINT_S */
+
+@@ -914,23 +1070,23 @@
+ fptr_ok(fptr_whitelist_comm_point(rep.c->callback));
+ if((*rep.c->callback)(rep.c, rep.c->cb_arg, NETEVENT_NOERROR, &rep)) {
+ /* send back immediate reply */
+- (void)comm_point_send_udp_msg_if(rep.c, rep.c->buffer,
++ struct sldns_buffer *buffer;
++#ifdef USE_DNSCRYPT
++ buffer = rep.c->dnscrypt_buffer;
++#else
++ buffer = rep.c->buffer;
++#endif
++ (void)comm_point_send_udp_msg_if(rep.c, buffer,
+ (struct sockaddr*)&rep.remote_addr,
+ rep.remote_addrlen, &rep);
+ }
+ if(!rep.c || rep.c->fd == -1) /* commpoint closed */
+ break;
+ }
+-#else
+- (void)fd;
+- (void)event;
+- (void)arg;
+- fatal_exit("recvmsg: No support for IPV6_PKTINFO; IP_PKTINFO or IP_RECVDSTADDR. "
+- "Please disable interface-automatic");
+-#endif /* AF_INET6 && IPV6_PKTINFO && HAVE_RECVMSG */
+ }
++#endif /* AF_INET6 && IPV6_PKTINFO && HAVE_RECVMSG */
+
+-void
++void
+ comm_point_udp_callback(int fd, short event, void* arg)
+ {
+ struct comm_reply rep;
+@@ -950,14 +1106,14 @@
+ rep.remote_addrlen = (socklen_t)sizeof(rep.remote_addr);
+ log_assert(fd != -1);
+ log_assert(sldns_buffer_remaining(rep.c->buffer) > 0);
+- rcv = recvfrom(fd, (void*)sldns_buffer_begin(rep.c->buffer),
++ rcv = recvfrom(fd, (void*)sldns_buffer_begin(rep.c->buffer),
+ sldns_buffer_remaining(rep.c->buffer), MSG_DONTWAIT,
+ (struct sockaddr*)&rep.remote_addr, &rep.remote_addrlen);
+ if(rcv == -1) {
+ #ifndef USE_WINSOCK
+ if(errno != EAGAIN && errno != EINTR
+ && udp_recv_needs_log(errno))
+- log_err("recvfrom %d failed: %s",
++ log_err("recvfrom %d failed: %s",
+ fd, strerror(errno));
+ #else
+ if(WSAGetLastError() != WSAEINPROGRESS &&
+@@ -1012,7 +1168,7 @@
+
+ /** Use a new tcp handler for new query fd, set to read query */
+ static void
+-setup_tcp_handler(struct comm_point* c, int fd, int cur, int max)
++setup_tcp_handler(struct comm_point* c, int fd, int cur, int max)
+ {
+ int handler_usage;
+ log_assert(c->type == comm_tcp || c->type == comm_http);
+@@ -1076,10 +1232,10 @@
+ /* EINTR is signal interrupt. others are closed connection. */
+ if( errno == EINTR || errno == EAGAIN
+ #ifdef EWOULDBLOCK
+- || errno == EWOULDBLOCK
++ || errno == EWOULDBLOCK
+ #endif
+ #ifdef ECONNABORTED
+- || errno == ECONNABORTED
++ || errno == ECONNABORTED
+ #endif
+ #ifdef EPROTO
+ || errno == EPROTO
+@@ -1253,7 +1409,7 @@
+ #endif /* HAVE_NGHTTP2 */
+
+
+-void
++void
+ comm_point_tcp_accept_callback(int fd, short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg, *c_hdl;
+@@ -1516,7 +1672,8 @@
+ } else {
+ unsigned long err = ERR_get_error();
+ if(!squelch_err_ssl_handshake(err)) {
+- log_crypto_err_code("ssl handshake failed", err);
++ log_crypto_err_io_code("ssl handshake failed",
++ want, err);
+ log_addr(VERB_OPS, "ssl handshake failed",
+ &c->repinfo.remote_addr,
+ c->repinfo.remote_addrlen);
+@@ -1666,23 +1823,30 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read",
++ want);
+ return 0;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_init;
+ }
+ }
+ if(c->pp2_header_state == pp2_header_init) {
+- header = pp2_read_header(c->buffer);
+- if(!header) {
++ int err;
++ err = pp2_read_header(
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_limit(c->buffer));
++ if(err) {
+ log_err("proxy_protocol: could not parse "
+- "PROXYv2 header");
++ "PROXYv2 header (%s)",
++ pp_lookup_error(err));
+ return 0;
+ }
++ header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
+ want_read_size = ntohs(header->len);
+- if(sldns_buffer_remaining(c->buffer) <
++ if(sldns_buffer_limit(c->buffer) <
+ PP2_HEADER_SIZE + want_read_size) {
+ log_err_addr("proxy_protocol: not enough "
+ "buffer size to read PROXYv2 header", "",
+@@ -1727,10 +1891,12 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read",
++ want);
+ return 0;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_done;
+ }
+@@ -1741,6 +1907,7 @@
+ c->repinfo.remote_addrlen);
+ return 0;
+ }
++ sldns_buffer_flip(c->buffer);
+ if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
+ log_err_addr("proxy_protocol: could not consume "
+ "PROXYv2 header", "", &c->repinfo.remote_addr,
+@@ -1785,7 +1952,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return 0;
+ }
+ c->tcp_byte_count += r;
+@@ -1835,7 +2002,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return 0;
+ }
+ sldns_buffer_skip(c->buffer, (ssize_t)r);
+@@ -1926,7 +2093,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return 0;
+ }
+ if(c->tcp_write_and_read) {
+@@ -1978,7 +2145,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return 0;
+ }
+ if(c->tcp_write_and_read) {
+@@ -2062,19 +2229,25 @@
+ goto recv_error_initial;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_init;
+ }
+ }
+ if(c->pp2_header_state == pp2_header_init) {
+- header = pp2_read_header(c->buffer);
+- if(!header) {
++ int err;
++ err = pp2_read_header(
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_limit(c->buffer));
++ if(err) {
+ log_err("proxy_protocol: could not parse "
+- "PROXYv2 header");
++ "PROXYv2 header (%s)",
++ pp_lookup_error(err));
+ return 0;
+ }
++ header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
+ want_read_size = ntohs(header->len);
+- if(sldns_buffer_remaining(c->buffer) <
++ if(sldns_buffer_limit(c->buffer) <
+ PP2_HEADER_SIZE + want_read_size) {
+ log_err_addr("proxy_protocol: not enough "
+ "buffer size to read PROXYv2 header", "",
+@@ -2101,6 +2274,7 @@
+ goto recv_error;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_done;
+ }
+@@ -2111,6 +2285,7 @@
+ c->repinfo.remote_addrlen);
+ return 0;
+ }
++ sldns_buffer_flip(c->buffer);
+ if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
+ log_err_addr("proxy_protocol: could not consume "
+ "PROXYv2 header", "", &c->repinfo.remote_addr,
+@@ -2161,7 +2336,7 @@
+ log_err("in comm_point_tcp_handle_read buffer_remaining is "
+ "not > 0 as expected, continuing with (harmless) 0 "
+ "length recv");
+- r = recv(fd, (void*)sldns_buffer_current(c->buffer),
++ r = recv(fd, (void*)sldns_buffer_current(c->buffer),
+ sldns_buffer_remaining(c->buffer), MSG_DONTWAIT);
+ if(r == 0) {
+ if(c->tcp_req_info)
+@@ -2252,8 +2427,8 @@
+ return 0;
+ }
+
+-/**
+- * Handle tcp writing callback.
++/**
++ * Handle tcp writing callback.
+ * @param fd: file descriptor of socket.
+ * @param c: comm point to write buffer out of.
+ * @return: 0 on error
+@@ -2277,7 +2452,7 @@
+ /* from Stevens, unix network programming, vol1, 3rd ed, p450*/
+ int error = 0;
+ socklen_t len = (socklen_t)sizeof(error);
+- if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
++ if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
+ &len) < 0){
+ #ifndef USE_WINSOCK
+ error = errno; /* on solaris errno is error */
+@@ -2318,7 +2493,7 @@
+ return ssl_handle_it(c, 1);
+
+ #ifdef USE_MSG_FASTOPEN
+- /* Only try this on first use of a connection that uses tfo,
++ /* Only try this on first use of a connection that uses tfo,
+ otherwise fall through to normal write */
+ /* Also, TFO support on WINDOWS not implemented at the moment */
+ if(c->tcp_do_fastopen == 1) {
+@@ -2473,7 +2648,7 @@
+ if(WSAGetLastError() == WSAEWOULDBLOCK) {
+ ub_winsock_tcp_wouldblock(c->ev->ev,
+ UB_EV_WRITE);
+- return 1;
++ return 1;
+ }
+ if(WSAGetLastError() == WSAECONNRESET && verbosity < 2)
+ return 0; /* silence reset by peer */
+@@ -2522,7 +2697,7 @@
+ return 1;
+ if(WSAGetLastError() == WSAEWOULDBLOCK) {
+ ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_WRITE);
+- return 1;
++ return 1;
+ }
+ if(WSAGetLastError() == WSAECONNRESET && verbosity < 2)
+ return 0; /* silence reset by peer */
+@@ -2541,7 +2716,7 @@
+ if((!c->tcp_write_and_read && sldns_buffer_remaining(buffer) == 0) || (c->tcp_write_and_read && c->tcp_write_byte_count == c->tcp_write_pkt_len + 2)) {
+ tcp_callback_writer(c);
+ }
+-
++
+ return 1;
+ }
+
+@@ -2561,7 +2736,7 @@
+ if(!c->tcp_do_close) {
+ fptr_ok(fptr_whitelist_comm_point(
+ c->callback));
+- (void)(*c->callback)(c, c->cb_arg,
++ (void)(*c->callback)(c, c->cb_arg,
+ NETEVENT_CLOSED, NULL);
+ }
+ return 0;
+@@ -2618,7 +2793,7 @@
+ }
+ }
+
+-void
++void
+ comm_point_tcp_handle_callback(int fd, short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg;
+@@ -2764,7 +2939,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return 0;
+ }
+ verbose(VERB_ALGO, "ssl http read more skip to %d + %d",
+@@ -2783,7 +2958,7 @@
+ {
+ ssize_t r;
+ log_assert(sldns_buffer_remaining(c->buffer) > 0);
+- r = recv(fd, (void*)sldns_buffer_current(c->buffer),
++ r = recv(fd, (void*)sldns_buffer_current(c->buffer),
+ sldns_buffer_remaining(c->buffer), MSG_DONTWAIT);
+ if(r == 0) {
+ return 0;
+@@ -3052,7 +3227,7 @@
+ /* return and wait to read more */
+ return 1;
+ }
+-
++
+ /* callback of http reader for a new part of the data */
+ c->http_stored = 0;
+ sldns_buffer_set_position(c->buffer, 0);
+@@ -3215,7 +3390,7 @@
+ strerror(errno));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return r;
+@@ -3402,7 +3577,7 @@
+ /* from Stevens, unix network programming, vol1, 3rd ed, p450*/
+ int error = 0;
+ socklen_t len = (socklen_t)sizeof(error);
+- if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
++ if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
+ &len) < 0){
+ #ifndef USE_WINSOCK
+ error = errno; /* on solaris errno is error */
+@@ -3470,7 +3645,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return 0;
+ }
+ sldns_buffer_skip(c->buffer, (ssize_t)r);
+@@ -3487,7 +3662,7 @@
+ {
+ ssize_t r;
+ log_assert(sldns_buffer_remaining(c->buffer) > 0);
+- r = send(fd, (void*)sldns_buffer_current(c->buffer),
++ r = send(fd, (void*)sldns_buffer_current(c->buffer),
+ sldns_buffer_remaining(c->buffer), 0);
+ if(r == -1) {
+ #ifndef USE_WINSOCK
+@@ -3498,7 +3673,7 @@
+ return 1;
+ if(WSAGetLastError() == WSAEWOULDBLOCK) {
+ ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_WRITE);
+- return 1;
++ return 1;
+ }
+ #endif
+ log_err_addr("http send r", sock_strerror(errno),
+@@ -3543,7 +3718,7 @@
+ strerror(errno));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return r;
+@@ -3619,8 +3794,8 @@
+ #endif
+ }
+
+-/**
+- * Handle http writing callback.
++/**
++ * Handle http writing callback.
+ * @param fd: file descriptor of socket.
+ * @param c: comm point to write buffer out of.
+ * @return: 0 on error
+@@ -3686,7 +3861,7 @@
+ return 1;
+ }
+
+-void
++void
+ comm_point_http_handle_callback(int fd, short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg;
+@@ -3739,7 +3914,7 @@
+ if(event&UB_EV_READ) {
+ if(!comm_point_tcp_handle_read(fd, c, 1)) {
+ fptr_ok(fptr_whitelist_comm_point(c->callback));
+- (void)(*c->callback)(c, c->cb_arg, NETEVENT_CLOSED,
++ (void)(*c->callback)(c, c->cb_arg, NETEVENT_CLOSED,
+ NULL);
+ }
+ return;
+@@ -3747,21 +3922,21 @@
+ log_err("Ignored event %d for localhdl.", event);
+ }
+
+-void comm_point_raw_handle_callback(int ATTR_UNUSED(fd),
++void comm_point_raw_handle_callback(int ATTR_UNUSED(fd),
+ short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg;
+ int err = NETEVENT_NOERROR;
+ log_assert(c->type == comm_raw);
+ ub_comm_base_now(c->ev->base);
+-
++
+ if(event&UB_EV_TIMEOUT)
+ err = NETEVENT_TIMEOUT;
+ fptr_ok(fptr_whitelist_comm_point_raw(c->callback));
+ (void)(*c->callback)(c, c->cb_arg, err, NULL);
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_udp(struct comm_base *base, int fd, sldns_buffer* buffer,
+ int pp2_enabled, comm_point_callback_type* callback,
+ void* callback_arg, struct unbound_socket* socket)
+@@ -3824,7 +3999,8 @@
+ return c;
+ }
+
+-struct comm_point*
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
++struct comm_point*
+ comm_point_create_udp_ancil(struct comm_base *base, int fd,
+ sldns_buffer* buffer, int pp2_enabled,
+ comm_point_callback_type* callback, void* callback_arg, struct unbound_socket* socket)
+@@ -3886,9 +4062,10 @@
+ c->event_added = 1;
+ return c;
+ }
++#endif
+
+-static struct comm_point*
+-comm_point_create_tcp_handler(struct comm_base *base,
++static struct comm_point*
++comm_point_create_tcp_handler(struct comm_base *base,
+ struct comm_point* parent, size_t bufsize,
+ struct sldns_buffer* spoolbuf, comm_point_callback_type* callback,
+ void* callback_arg, struct unbound_socket* socket)
+@@ -3985,8 +4162,8 @@
+ return c;
+ }
+
+-static struct comm_point*
+-comm_point_create_http_handler(struct comm_base *base,
++static struct comm_point*
++comm_point_create_http_handler(struct comm_base *base,
+ struct comm_point* parent, size_t bufsize, int harden_large_queries,
+ uint32_t http_max_streams, char* http_endpoint,
+ comm_point_callback_type* callback, void* callback_arg,
+@@ -4083,7 +4260,7 @@
+ return NULL;
+ }
+ #endif
+-
++
+ /* add to parent free list */
+ c->tcp_free = parent->tcp_free;
+ parent->tcp_free = c;
+@@ -4105,7 +4282,7 @@
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_tcp(struct comm_base *base, int fd, int num,
+ int idle_timeout, int harden_large_queries,
+ uint32_t http_max_streams, char* http_endpoint,
+@@ -4203,11 +4380,11 @@
+ return NULL;
+ }
+ }
+-
++
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_tcp_out(struct comm_base *base, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg)
+ {
+@@ -4274,7 +4451,7 @@
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_http_out(struct comm_base *base, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg,
+ sldns_buffer* temp)
+@@ -4345,7 +4522,7 @@
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_local(struct comm_base *base, int fd, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg)
+ {
+@@ -4413,8 +4590,8 @@
+ return c;
+ }
+
+-struct comm_point*
+-comm_point_create_raw(struct comm_base* base, int fd, int writing,
++struct comm_point*
++comm_point_create_raw(struct comm_base* base, int fd, int writing,
+ comm_point_callback_type* callback, void* callback_arg)
+ {
+ struct comm_point* c = (struct comm_point*)calloc(1,
+@@ -4478,7 +4655,7 @@
+ return c;
+ }
+
+-void
++void
+ comm_point_close(struct comm_point* c)
+ {
+ if(!c)
+@@ -4518,10 +4695,10 @@
+ c->fd = -1;
+ }
+
+-void
++void
+ comm_point_delete(struct comm_point* c)
+ {
+- if(!c)
++ if(!c)
+ return;
+ if((c->type == comm_tcp || c->type == comm_http) && c->ssl) {
+ #ifdef HAVE_SSL
+@@ -4560,7 +4737,7 @@
+ free(c);
+ }
+
+-void
++void
+ comm_point_send_reply(struct comm_reply *repinfo)
+ {
+ struct sldns_buffer* buffer;
+@@ -4624,7 +4801,7 @@
+ }
+ }
+
+-void
++void
+ comm_point_drop_reply(struct comm_reply* repinfo)
+ {
+ if(!repinfo)
+@@ -4648,7 +4825,7 @@
+ reclaim_tcp_handler(repinfo->c);
+ }
+
+-void
++void
+ comm_point_stop_listening(struct comm_point* c)
+ {
+ verbose(VERB_ALGO, "comm point stop listening %d", c->fd);
+@@ -4660,10 +4837,10 @@
+ }
+ }
+
+-void
++void
+ comm_point_start_listening(struct comm_point* c, int newfd, int msec)
+ {
+- verbose(VERB_ALGO, "comm point start listening %d (%d msec)",
++ verbose(VERB_ALGO, "comm point start listening %d (%d msec)",
+ c->fd==-1?newfd:c->fd, msec);
+ if(c->type == comm_tcp_accept && !c->tcp_free) {
+ /* no use to start listening no free slots. */
+@@ -4747,10 +4924,10 @@
+ size_t comm_point_get_mem(struct comm_point* c)
+ {
+ size_t s;
+- if(!c)
++ if(!c)
+ return 0;
+ s = sizeof(*c) + sizeof(*c->ev);
+- if(c->timeout)
++ if(c->timeout)
+ s += sizeof(*c->timeout);
+ if(c->type == comm_tcp || c->type == comm_local) {
+ s += sizeof(*c->buffer) + sldns_buffer_capacity(c->buffer);
+@@ -4769,7 +4946,7 @@
+ return s;
+ }
+
+-struct comm_timer*
++struct comm_timer*
+ comm_timer_create(struct comm_base* base, void (*cb)(void*), void* cb_arg)
+ {
+ struct internal_timer *tm = (struct internal_timer*)calloc(1,
+@@ -4782,7 +4959,7 @@
+ tm->base = base;
+ tm->super.callback = cb;
+ tm->super.cb_arg = cb_arg;
+- tm->ev = ub_event_new(base->eb->base, -1, UB_EV_TIMEOUT,
++ tm->ev = ub_event_new(base->eb->base, -1, UB_EV_TIMEOUT,
+ comm_timer_callback, &tm->super);
+ if(tm->ev == NULL) {
+ log_err("timer_create: event_base_set failed.");
+@@ -4792,7 +4969,7 @@
+ return &tm->super;
+ }
+
+-void
++void
+ comm_timer_disable(struct comm_timer* timer)
+ {
+ if(!timer)
+@@ -4801,7 +4978,7 @@
+ timer->ev_timer->enabled = 0;
+ }
+
+-void
++void
+ comm_timer_set(struct comm_timer* timer, struct timeval* tv)
+ {
+ log_assert(tv);
+@@ -4813,7 +4990,7 @@
+ timer->ev_timer->enabled = 1;
+ }
+
+-void
++void
+ comm_timer_delete(struct comm_timer* timer)
+ {
+ if(!timer)
+@@ -4826,7 +5003,7 @@
+ free(timer->ev_timer);
+ }
+
+-void
++void
+ comm_timer_callback(int ATTR_UNUSED(fd), short event, void* arg)
+ {
+ struct comm_timer* tm = (struct comm_timer*)arg;
+@@ -4838,19 +5015,19 @@
+ (*tm->callback)(tm->cb_arg);
+ }
+
+-int
++int
+ comm_timer_is_set(struct comm_timer* timer)
+ {
+ return (int)timer->ev_timer->enabled;
+ }
+
+-size_t
++size_t
+ comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer))
+ {
+ return sizeof(struct internal_timer);
+ }
+
+-struct comm_signal*
++struct comm_signal*
+ comm_signal_create(struct comm_base* base,
+ void (*callback)(int, void*), void* cb_arg)
+ {
+@@ -4867,7 +5044,7 @@
+ return com;
+ }
+
+-void
++void
+ comm_signal_callback(int sig, short event, void* arg)
+ {
+ struct comm_signal* comsig = (struct comm_signal*)arg;
+@@ -4878,10 +5055,10 @@
+ (*comsig->callback)(sig, comsig->cb_arg);
+ }
+
+-int
++int
+ comm_signal_bind(struct comm_signal* comsig, int sig)
+ {
+- struct internal_signal* entry = (struct internal_signal*)calloc(1,
++ struct internal_signal* entry = (struct internal_signal*)calloc(1,
+ sizeof(struct internal_signal));
+ if(!entry) {
+ log_err("malloc failed");
+@@ -4908,7 +5085,7 @@
+ return 1;
+ }
+
+-void
++void
+ comm_signal_delete(struct comm_signal* comsig)
+ {
+ struct internal_signal* p, *np;
+--- contrib/unbound/util/netevent.h.orig
++++ contrib/unbound/util/netevent.h
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -60,6 +60,7 @@
+ #ifndef NET_EVENT_H
+ #define NET_EVENT_H
+
++#include <sys/time.h>
+ #include "dnscrypt/dnscrypt.h"
+ #ifdef HAVE_NGHTTP2_NGHTTP2_H
+ #include <nghttp2/nghttp2.h>
+@@ -83,7 +84,7 @@
+ enum listen_type;
+
+ /** callback from communication point function type */
+-typedef int comm_point_callback_type(struct comm_point*, void*, int,
++typedef int comm_point_callback_type(struct comm_point*, void*, int,
+ struct comm_reply*);
+
+ /** to pass no_error to callback function */
+@@ -91,7 +92,7 @@
+ /** to pass closed connection to callback function */
+ #define NETEVENT_CLOSED -1
+ /** to pass timeout happened to callback function */
+-#define NETEVENT_TIMEOUT -2
++#define NETEVENT_TIMEOUT -2
+ /** to pass fallback from capsforID to callback function; 0x20 failed */
+ #define NETEVENT_CAPSFAIL -3
+ /** to pass done transfer to callback function; http file is complete */
+@@ -165,8 +166,8 @@
+ socklen_t client_addrlen;
+ };
+
+-/**
+- * Communication point to the network
++/**
++ * Communication point to the network
+ * These behaviours can be accomplished by setting the flags
+ * and passing return values from the callback.
+ * udp frontside: called after readdone. sendafter.
+@@ -206,7 +207,7 @@
+ int max_tcp_count;
+ /** current number of tcp handler in-use for this accept socket */
+ int cur_tcp_count;
+- /** malloced array of tcp handlers for a tcp-accept,
++ /** malloced array of tcp handlers for a tcp-accept,
+ of size max_tcp_count. */
+ struct comm_point** tcp_handlers;
+ /** linked list of free tcp_handlers to use for new queries.
+@@ -271,9 +272,9 @@
+ /** is this a UDP, TCP-accept or TCP socket. */
+ enum comm_point_type {
+ /** UDP socket - handle datagrams. */
+- comm_udp,
++ comm_udp,
+ /** TCP accept socket - only creates handlers if readable. */
+- comm_tcp_accept,
++ comm_tcp_accept,
+ /** TCP handler socket - handle byteperbyte readwrite. */
+ comm_tcp,
+ /** HTTP handler socket */
+@@ -282,7 +283,7 @@
+ comm_local,
+ /** raw - not DNS format - for pipe readers and writers */
+ comm_raw
+- }
++ }
+ /** variable with type of socket, UDP,TCP-accept,TCP,pipe */
+ type;
+
+@@ -303,7 +304,7 @@
+ /** if set the connection is NOT closed on delete. */
+ int do_not_close;
+
+- /** if set, the connection is closed on error, on timeout,
++ /** if set, the connection is closed on error, on timeout,
+ and after read/write completes. No callback is done. */
+ int tcp_do_close;
+
+@@ -383,15 +384,16 @@
+ /** number of queries outstanding on this socket, used by
+ * outside network for udp ports */
+ int inuse;
+-
++ /** the timestamp when the packet was received by the kernel */
++ struct timeval recv_tv;
+ /** callback when done.
+ tcp_accept does not get called back, is NULL then.
+ If a timeout happens, callback with timeout=1 is called.
+- If an error happens, callback is called with error set
++ If an error happens, callback is called with error set
+ nonzero. If not NETEVENT_NOERROR, it is an errno value.
+ If the connection is closed (by remote end) then the
+ callback is called with error set to NETEVENT_CLOSED=-1.
+- If a timeout happens on the connection, the error is set to
++ If a timeout happens on the connection, the error is set to
+ NETEVENT_TIMEOUT=-2.
+ The reply_info can be copied if the reply needs to happen at a
+ later time. It consists of a struct with commpoint and address.
+@@ -399,7 +401,7 @@
+ Note the reply information is temporary and must be copied.
+ NULL is passed for_reply info, in cases where error happened.
+
+- declare as:
++ declare as:
+ int my_callback(struct comm_point* c, void* my_arg, int error,
+ struct comm_reply *reply_info);
+
+@@ -446,14 +448,14 @@
+
+ /**
+ * Create a new comm base.
+- * @param sigs: if true it attempts to create a default loop for
++ * @param sigs: if true it attempts to create a default loop for
+ * signal handling.
+ * @return: the new comm base. NULL on error.
+ */
+ struct comm_base* comm_base_create(int sigs);
+
+ /**
+- * Create comm base that uses the given ub_event_base (underlying pluggable
++ * Create comm base that uses the given ub_event_base (underlying pluggable
+ * event mechanism pointer).
+ * @param base: underlying pluggable event base.
+ * @return: the new comm base. NULL on error.
+@@ -619,7 +621,7 @@
+ * @return: the commpoint or NULL on error.
+ */
+ struct comm_point* comm_point_create_local(struct comm_base* base,
+- int fd, size_t bufsize,
++ int fd, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg);
+
+ /**
+@@ -632,7 +634,7 @@
+ * @return: the commpoint or NULL on error.
+ */
+ struct comm_point* comm_point_create_raw(struct comm_base* base,
+- int fd, int writing,
++ int fd, int writing,
+ comm_point_callback_type* callback, void* callback_arg);
+
+ /**
+@@ -722,7 +724,7 @@
+ * @param cb_arg: user callback argument.
+ * @return: the new timer or NULL on error.
+ */
+-struct comm_timer* comm_timer_create(struct comm_base* base,
++struct comm_timer* comm_timer_create(struct comm_base* base,
+ void (*cb)(void*), void* cb_arg);
+
+ /**
+@@ -792,7 +794,7 @@
+ * if -1, error message has been printed if necessary, simply drop
+ * out of the reading handler.
+ */
+-int comm_point_perform_accept(struct comm_point* c,
++int comm_point_perform_accept(struct comm_point* c,
+ struct sockaddr_storage* addr, socklen_t* addrlen);
+
+ /**** internal routines ****/
+@@ -801,7 +803,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for udp comm point.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -811,7 +813,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for udp ancillary data comm point.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -821,7 +823,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp accept comm point
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -831,7 +833,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp data comm point
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -841,7 +843,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp data comm point
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -955,7 +957,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for timer comm.
+ * @param fd: file descriptor (always -1).
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_timer structure.
+ */
+@@ -965,7 +967,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for signal comm.
+ * @param fd: file descriptor (used for the signal number).
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the internal commsignal structure.
+ */
+@@ -975,7 +977,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * libevent callback for AF_UNIX fds
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -985,7 +987,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * libevent callback for raw fd access.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -995,7 +997,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * libevent callback for timeout on slow accept.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+--- contrib/unbound/util/proxy_protocol.c.orig
++++ contrib/unbound/util/proxy_protocol.c
+@@ -38,102 +38,162 @@
+ *
+ * This file contains PROXY protocol functions.
+ */
+-#include "config.h"
+-#include "util/log.h"
+ #include "util/proxy_protocol.h"
+
+-int
+-pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
++/**
++ * Internal struct initialized with function pointers for writing uint16 and
++ * uint32.
++ */
++struct proxy_protocol_data {
++ void (*write_uint16)(void* buf, uint16_t data);
++ void (*write_uint32)(void* buf, uint32_t data);
++};
++struct proxy_protocol_data pp_data;
++
++/**
++ * Internal lookup table; could be further generic like sldns_lookup_table
++ * for all the future generic stuff.
++ */
++struct proxy_protocol_lookup_table {
++ int id;
++ const char *text;
++};
++
++/**
++ * Internal parsing error text; could be exposed with pp_lookup_error.
++ */
++static struct proxy_protocol_lookup_table pp_parse_errors_data[] = {
++ { PP_PARSE_NOERROR, "no parse error" },
++ { PP_PARSE_SIZE, "not enough space for header" },
++ { PP_PARSE_WRONG_HEADERv2, "could not match PROXYv2 header" },
++ { PP_PARSE_UNKNOWN_CMD, "unknown command" },
++ { PP_PARSE_UNKNOWN_FAM_PROT, "unknown family and protocol" },
++};
++
++void
++pp_init(void (*write_uint16)(void* buf, uint16_t data),
++ void (*write_uint32)(void* buf, uint32_t data)) {
++ pp_data.write_uint16 = write_uint16;
++ pp_data.write_uint32 = write_uint32;
++}
++
++const char*
++pp_lookup_error(enum pp_parse_errors error) {
++ return pp_parse_errors_data[error].text;
++}
++
++size_t
++pp2_write_to_buf(uint8_t* buf, size_t buflen,
++#ifdef INET6
++ struct sockaddr_storage* src,
++#else
++ struct sockaddr_in* src,
++#endif
+ int stream)
+ {
+ int af;
++ size_t expected_size;
+ if(!src) return 0;
+ af = (int)((struct sockaddr_in*)src)->sin_family;
+- if(sldns_buffer_remaining(buf) <
+- PP2_HEADER_SIZE + (af==AF_INET?12:36)) {
++ expected_size = PP2_HEADER_SIZE + (af==AF_INET?12:36);
++ if(buflen < expected_size) {
+ return 0;
+ }
+ /* sig */
+- sldns_buffer_write(buf, PP2_SIG, PP2_SIG_LEN);
++ memcpy(buf, PP2_SIG, PP2_SIG_LEN);
++ buf += PP2_SIG_LEN;
+ /* version and command */
+- sldns_buffer_write_u8(buf, (PP2_VERSION << 4) | PP2_CMD_PROXY);
+- if(af==AF_INET) {
++ *buf = (PP2_VERSION << 4) | PP2_CMD_PROXY;
++ buf++;
++ switch(af) {
++ case AF_INET:
+ /* family and protocol */
+- sldns_buffer_write_u8(buf,
+- (PP2_AF_INET<<4) |
+- (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
++ *buf = (PP2_AF_INET<<4) |
++ (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
++ buf++;
+ /* length */
+- sldns_buffer_write_u16(buf, 12);
++ (*pp_data.write_uint16)(buf, 12);
++ buf += 2;
+ /* src addr */
+- sldns_buffer_write(buf,
++ memcpy(buf,
+ &((struct sockaddr_in*)src)->sin_addr.s_addr, 4);
++ buf += 4;
+ /* dst addr */
+- sldns_buffer_write_u32(buf, 0);
++ (*pp_data.write_uint32)(buf, 0);
++ buf += 4;
+ /* src port */
+- sldns_buffer_write(buf,
++ memcpy(buf,
+ &((struct sockaddr_in*)src)->sin_port, 2);
++ buf += 2;
++ /* dst addr */
+ /* dst port */
+- sldns_buffer_write_u16(buf, 0);
+- } else {
++ (*pp_data.write_uint16)(buf, 12);
++ break;
++#ifdef INET6
++ case AF_INET6:
+ /* family and protocol */
+- sldns_buffer_write_u8(buf,
+- (PP2_AF_INET6<<4) |
+- (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
++ *buf = (PP2_AF_INET6<<4) |
++ (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
++ buf++;
+ /* length */
+- sldns_buffer_write_u16(buf, 36);
++ (*pp_data.write_uint16)(buf, 36);
++ buf += 2;
+ /* src addr */
+- sldns_buffer_write(buf,
++ memcpy(buf,
+ &((struct sockaddr_in6*)src)->sin6_addr, 16);
++ buf += 16;
+ /* dst addr */
+- sldns_buffer_set_at(buf,
+- sldns_buffer_position(buf), 0, 16);
+- sldns_buffer_skip(buf, 16);
++ memset(buf, 0, 16);
++ buf += 16;
+ /* src port */
+- sldns_buffer_write(buf,
+- &((struct sockaddr_in6*)src)->sin6_port, 2);
++ memcpy(buf, &((struct sockaddr_in6*)src)->sin6_port, 2);
++ buf += 2;
+ /* dst port */
+- sldns_buffer_write_u16(buf, 0);
++ (*pp_data.write_uint16)(buf, 0);
++ break;
++#endif /* INET6 */
++ case AF_UNIX:
++ /* fallthrough */
++ default:
++ return 0;
+ }
+- return 1;
++ return expected_size;
+ }
+
+-struct pp2_header*
+-pp2_read_header(struct sldns_buffer* buf)
++int
++pp2_read_header(uint8_t* buf, size_t buflen)
+ {
+ size_t size;
+- struct pp2_header* header = (struct pp2_header*)sldns_buffer_begin(buf);
++ struct pp2_header* header = (struct pp2_header*)buf;
+ /* Try to fail all the unsupported cases first. */
+- if(sldns_buffer_remaining(buf) < PP2_HEADER_SIZE) {
+- log_err("proxy_protocol: not enough space for header");
+- return NULL;
++ if(buflen < PP2_HEADER_SIZE) {
++ return PP_PARSE_SIZE;
+ }
+ /* Check for PROXYv2 header */
+ if(memcmp(header, PP2_SIG, PP2_SIG_LEN) != 0 ||
+ ((header->ver_cmd & 0xF0)>>4) != PP2_VERSION) {
+- log_err("proxy_protocol: could not match PROXYv2 header");
+- return NULL;
++ return PP_PARSE_WRONG_HEADERv2;
+ }
+ /* Check the length */
+ size = PP2_HEADER_SIZE + ntohs(header->len);
+- if(sldns_buffer_remaining(buf) < size) {
+- log_err("proxy_protocol: not enough space for header");
+- return NULL;
++ if(buflen < size) {
++ return PP_PARSE_SIZE;
+ }
+ /* Check for supported commands */
+ if((header->ver_cmd & 0xF) != PP2_CMD_LOCAL &&
+ (header->ver_cmd & 0xF) != PP2_CMD_PROXY) {
+- log_err("proxy_protocol: unsupported command");
+- return NULL;
++ return PP_PARSE_UNKNOWN_CMD;
+ }
+ /* Check for supported family and protocol */
+- if(header->fam_prot != 0x00 /* AF_UNSPEC|UNSPEC */ &&
+- header->fam_prot != 0x11 /* AF_INET|STREAM */ &&
+- header->fam_prot != 0x12 /* AF_INET|DGRAM */ &&
+- header->fam_prot != 0x21 /* AF_INET6|STREAM */ &&
+- header->fam_prot != 0x22 /* AF_INET6|DGRAM */) {
+- log_err("proxy_protocol: unsupported family and protocol");
+- return NULL;
++ if(header->fam_prot != PP2_UNSPEC_UNSPEC &&
++ header->fam_prot != PP2_INET_STREAM &&
++ header->fam_prot != PP2_INET_DGRAM &&
++ header->fam_prot != PP2_INET6_STREAM &&
++ header->fam_prot != PP2_INET6_DGRAM &&
++ header->fam_prot != PP2_UNIX_STREAM &&
++ header->fam_prot != PP2_UNIX_DGRAM) {
++ return PP_PARSE_UNKNOWN_FAM_PROT;
+ }
+ /* We have a correct header */
+- return header;
++ return PP_PARSE_NOERROR;
+ }
+--- contrib/unbound/util/proxy_protocol.h.orig
++++ contrib/unbound/util/proxy_protocol.h
+@@ -42,7 +42,7 @@
+ #ifndef PROXY_PROTOCOL_H
+ #define PROXY_PROTOCOL_H
+
+-#include "sldns/sbuffer.h"
++#include "config.h"
+
+ /** PROXYv2 minimum header size */
+ #define PP2_HEADER_SIZE 16
+@@ -51,11 +51,11 @@
+ #define PP2_SIG "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
+ #define PP2_SIG_LEN 12
+
+-/** PROXYv2 version */
++/** PROXYv2 version (protocol value) */
+ #define PP2_VERSION 0x2
+
+ /**
+- * PROXYv2 command.
++ * PROXYv2 command (protocol value).
+ */
+ enum pp2_command {
+ PP2_CMD_LOCAL = 0x0,
+@@ -63,7 +63,7 @@
+ };
+
+ /**
+- * PROXYv2 address family.
++ * PROXYv2 address family (protocol value).
+ */
+ enum pp2_af {
+ PP2_AF_UNSPEC = 0x0,
+@@ -73,7 +73,7 @@
+ };
+
+ /**
+- * PROXYv2 protocol.
++ * PROXYv2 protocol (protocol value).
+ */
+ enum pp2_protocol {
+ PP2_PROT_UNSPEC = 0x0,
+@@ -81,6 +81,19 @@
+ PP2_PROT_DGRAM = 0x2
+ };
+
++/**
++ * Expected combinations of address family and protocol values used in checks.
++ */
++enum pp2_af_protocol_combination {
++ PP2_UNSPEC_UNSPEC = (PP2_AF_UNSPEC<<4)|PP2_PROT_UNSPEC,
++ PP2_INET_STREAM = (PP2_AF_INET<<4)|PP2_PROT_STREAM,
++ PP2_INET_DGRAM = (PP2_AF_INET<<4)|PP2_PROT_DGRAM,
++ PP2_INET6_STREAM = (PP2_AF_INET6<<4)|PP2_PROT_STREAM,
++ PP2_INET6_DGRAM = (PP2_AF_INET6<<4)|PP2_PROT_DGRAM,
++ PP2_UNIX_STREAM = (PP2_AF_UNIX<<4)|PP2_PROT_STREAM,
++ PP2_UNIX_DGRAM = (PP2_AF_UNIX<<4)|PP2_PROT_DGRAM
++};
++
+ /**
+ * PROXYv2 header.
+ */
+@@ -109,23 +122,56 @@
+ } addr;
+ };
+
++/**
++ * PROXY parse errors.
++ */
++enum pp_parse_errors {
++ PP_PARSE_NOERROR = 0,
++ PP_PARSE_SIZE,
++ PP_PARSE_WRONG_HEADERv2,
++ PP_PARSE_UNKNOWN_CMD,
++ PP_PARSE_UNKNOWN_FAM_PROT,
++};
++
++/**
++ * Initialize the internal proxy structure.
++ * @param write_uint16: pointer to a function that can write uint16.
++ * @param write_uint32: pointer to a function that can write uint32.
++ */
++void pp_init(void (*write_uint16)(void* buf, uint16_t data),
++ void (*write_uint32)(void* buf, uint32_t data));
++
++/**
++ * Lookup the parsing error description.
++ * @param error: parsing error from pp2_read_header.
++ * @return the description.
++ */
++const char* pp_lookup_error(enum pp_parse_errors error);
++
+ /**
+ * Write a PROXYv2 header at the current position of the buffer.
+- * @param buf: the buffer to write to.
++ * @param buf: pointer to the buffer to write data to.
++ * @param buflen: available size on the buffer.
+ * @param src: the source address.
+ * @param stream: if the protocol is stream or datagram.
+ * @return 1 on success, 0 on failure.
+ */
+-int pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
++size_t pp2_write_to_buf(uint8_t* buf, size_t buflen,
++#ifdef INET6
++ struct sockaddr_storage* src,
++#else
++ struct sockaddr_in* src,
++#endif
+ int stream);
+
+ /**
+ * Read a PROXYv2 header from the current position of the buffer.
+ * It does initial validation and returns a pointer to the buffer position on
+ * success.
+- * @param buf: the buffer to read from.
+- * @return the pointer to the buffer position on success, NULL on error.
++ * @param buf: pointer to the buffer data to read from.
++ * @param buflen: available size on the buffer.
++ * @return parsing error, 0 on success.
+ */
+-struct pp2_header* pp2_read_header(struct sldns_buffer* buf);
++int pp2_read_header(uint8_t* buf, size_t buflen);
+
+ #endif /* PROXY_PROTOCOL_H */
+--- contrib/unbound/util/regional.c.orig
++++ contrib/unbound/util/regional.c
+@@ -186,7 +186,7 @@
+ {
+ void *s = regional_alloc(r, size);
+ if(!s) return NULL;
+- memcpy(s, init, size);
++ memmove(s, init, size);
+ return s;
+ }
+
+--- /dev/null
++++ contrib/unbound/util/rfc_1982.c
+@@ -0,0 +1,75 @@
++/*
++ * util/rfc_1982.c - RFC 1982 Serial Number Arithmetic
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains functions for RFC 1982 serial number arithmetic.
++ */
++#include "config.h"
++#include "util/rfc_1982.h"
++
++int
++compare_1982(uint32_t a, uint32_t b)
++{
++ /* for 32 bit values */
++ const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
++
++ if (a == b) {
++ return 0;
++ } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
++ return -1;
++ } else {
++ return 1;
++ }
++}
++
++uint32_t
++subtract_1982(uint32_t a, uint32_t b)
++{
++ /* for 32 bit values */
++ const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
++
++ if(a == b)
++ return 0;
++ if(a < b && b - a < cutoff) {
++ return b-a;
++ }
++ if(a > b && a - b > cutoff) {
++ return ((uint32_t)0xffffffff) - (a-b-1);
++ }
++ /* wrong case, b smaller than a */
++ return 0;
++}
+--- /dev/null
++++ contrib/unbound/util/rfc_1982.h
+@@ -0,0 +1,63 @@
++/*
++ * util/rfc_1982.h - RFC 1982 Serial Number Arithmetic
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains functions for RFC 1982 serial number arithmetic.
++ */
++#ifndef RFC_1982_H
++#define RFC_1982_H
++
++/**
++ * RFC 1982 comparison, uses unsigned integers, and tries to avoid
++ * compiler optimization (eg. by avoiding a-b<0 comparisons).
++ * @param a: value to compare.
++ * @param b: value to compare.
++ * @return 0 if equal, 1 if a > b, else -1.
++ */
++int compare_1982(uint32_t a, uint32_t b);
++
++/**
++ * RFC 1982 subtraction, uses unsigned integers, and tries to avoid
++ * compiler optimization (eg. by avoiding a-b<0 comparisons).
++ * @param a: value to subtract from.
++ * @param b: value to subtract.
++ * @return the difference between them if we know that b is larger than a,
++ * that is the distance between them in serial number arithmetic.
++ */
++uint32_t subtract_1982(uint32_t a, uint32_t b);
++
++#endif /* RFC_1982_H */
+--- /dev/null
++++ contrib/unbound/util/siphash.c
+@@ -0,0 +1,192 @@
++/*
++ SipHash reference C implementation
++
++ Copyright (c) 2012-2016 Jean-Philippe Aumasson
++ <jeanphilippe.aumasson@gmail.com>
++ Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
++
++ To the extent possible under law, the author(s) have dedicated all copyright
++ and related and neighboring rights to this software to the public domain
++ worldwide. This software is distributed without any warranty.
++
++ You should have received a copy of the CC0 Public Domain Dedication along
++ with
++ this software. If not, see
++ <http://creativecommons.org/publicdomain/zero/1.0/>.
++ */
++/**
++ * Edited slightly for integration in Unbound. Edits are noted with 'EDIT'.
++ */
++/** EDIT
++ * \#include <assert.h>
++ * \#include <stdint.h>
++ * \#include <stdio.h>
++ * \#include <string.h>
++ * Replaced the above includes with Unbound's config.h
++ */
++#include "config.h"
++
++/** EDIT
++ * prevent warning from -Wmissing-prototypes
++ */
++#include "util/siphash.h"
++
++/* default: SipHash-2-4 */
++#define cROUNDS 2
++#define dROUNDS 4
++
++#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
++
++#define U32TO8_LE(p, v) \
++ (p)[0] = (uint8_t)((v)); \
++ (p)[1] = (uint8_t)((v) >> 8); \
++ (p)[2] = (uint8_t)((v) >> 16); \
++ (p)[3] = (uint8_t)((v) >> 24);
++
++#define U64TO8_LE(p, v) \
++ U32TO8_LE((p), (uint32_t)((v))); \
++ U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
++
++#define U8TO64_LE(p) \
++ (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
++ ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
++ ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
++ ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
++
++#define SIPROUND \
++ do { \
++ v0 += v1; \
++ v1 = ROTL(v1, 13); \
++ v1 ^= v0; \
++ v0 = ROTL(v0, 32); \
++ v2 += v3; \
++ v3 = ROTL(v3, 16); \
++ v3 ^= v2; \
++ v0 += v3; \
++ v3 = ROTL(v3, 21); \
++ v3 ^= v0; \
++ v2 += v1; \
++ v1 = ROTL(v1, 17); \
++ v1 ^= v2; \
++ v2 = ROTL(v2, 32); \
++ } while (0)
++
++#ifdef DEBUG
++#define TRACE \
++ do { \
++ printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \
++ (uint32_t)v0); \
++ printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \
++ (uint32_t)v1); \
++ printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \
++ (uint32_t)v2); \
++ printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \
++ (uint32_t)v3); \
++ } while (0)
++#else
++#define TRACE
++#endif
++
++int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
++ uint8_t *out, const size_t outlen) {
++
++ uint64_t v0 = 0x736f6d6570736575ULL;
++ uint64_t v1 = 0x646f72616e646f6dULL;
++ uint64_t v2 = 0x6c7967656e657261ULL;
++ uint64_t v3 = 0x7465646279746573ULL;
++ uint64_t k0 = U8TO64_LE(k);
++ uint64_t k1 = U8TO64_LE(k + 8);
++ uint64_t m;
++ int i;
++ const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
++ const int left = inlen & 7;
++ uint64_t b = ((uint64_t)inlen) << 56;
++ /** EDIT
++ * The following assert moved here from the top for C90 compliance.
++ */
++ assert((outlen == 8) || (outlen == 16));
++ v3 ^= k1;
++ v2 ^= k0;
++ v1 ^= k1;
++ v0 ^= k0;
++
++ if (outlen == 16)
++ v1 ^= 0xee;
++
++ for (; in != end; in += 8) {
++ m = U8TO64_LE(in);
++ v3 ^= m;
++
++ TRACE;
++ for (i = 0; i < cROUNDS; ++i)
++ SIPROUND;
++
++ v0 ^= m;
++ }
++
++ switch (left) {
++ case 7:
++ b |= ((uint64_t)in[6]) << 48;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 6:
++ b |= ((uint64_t)in[5]) << 40;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 5:
++ b |= ((uint64_t)in[4]) << 32;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 4:
++ b |= ((uint64_t)in[3]) << 24;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 3:
++ b |= ((uint64_t)in[2]) << 16;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 2:
++ b |= ((uint64_t)in[1]) << 8;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 1:
++ b |= ((uint64_t)in[0]);
++ break;
++ case 0:
++ break;
++ }
++
++ v3 ^= b;
++
++ TRACE;
++ for (i = 0; i < cROUNDS; ++i)
++ SIPROUND;
++
++ v0 ^= b;
++
++ if (outlen == 16)
++ v2 ^= 0xee;
++ else
++ v2 ^= 0xff;
++
++ TRACE;
++ for (i = 0; i < dROUNDS; ++i)
++ SIPROUND;
++
++ b = v0 ^ v1 ^ v2 ^ v3;
++ U64TO8_LE(out, b);
++
++ if (outlen == 8)
++ return 0;
++
++ v1 ^= 0xdd;
++
++ TRACE;
++ for (i = 0; i < dROUNDS; ++i)
++ SIPROUND;
++
++ b = v0 ^ v1 ^ v2 ^ v3;
++ U64TO8_LE(out + 8, b);
++
++ return 0;
++}
+--- /dev/null
++++ contrib/unbound/util/siphash.h
+@@ -0,0 +1,43 @@
++/*
++ * util/siphash.h - header for SipHash reference C implementation.
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++/**
++ * \file
++ * Contains the SipHash reference C implementation.
++ */
++#ifndef UTIL_SIPHASH_H
++#define UTIL_SIPHASH_H
++int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
++ uint8_t *out, const size_t outlen);
++#endif /* UTIL_SIPHASH_H */
+--- contrib/unbound/util/storage/lruhash.c.orig
++++ contrib/unbound/util/storage/lruhash.c
+@@ -81,6 +81,7 @@
+ table->num = 0;
+ table->space_used = 0;
+ table->space_max = maxmem;
++ table->max_collisions = 0;
+ table->array = calloc(table->size, sizeof(struct lruhash_bin));
+ if(!table->array) {
+ lock_quick_destroy(&table->lock);
+@@ -216,15 +217,19 @@
+
+ struct lruhash_entry*
+ bin_find_entry(struct lruhash* table,
+- struct lruhash_bin* bin, hashvalue_type hash, void* key)
++ struct lruhash_bin* bin, hashvalue_type hash, void* key, size_t* collisions)
+ {
++ size_t c = 0;
+ struct lruhash_entry* p = bin->overflow_list;
+ while(p) {
+ if(p->hash == hash && table->compfunc(p->key, key) == 0)
+- return p;
++ break;
++ c++;
+ p = p->overflow_next;
+ }
+- return NULL;
++ if (collisions != NULL)
++ *collisions = c;
++ return p;
+ }
+
+ void
+@@ -303,6 +308,7 @@
+ struct lruhash_bin* bin;
+ struct lruhash_entry* found, *reclaimlist=NULL;
+ size_t need_size;
++ size_t collisions;
+ fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
+ fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
+ fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
+@@ -317,12 +323,14 @@
+ lock_quick_lock(&bin->lock);
+
+ /* see if entry exists already */
+- if(!(found=bin_find_entry(table, bin, hash, entry->key))) {
++ if(!(found=bin_find_entry(table, bin, hash, entry->key, &collisions))) {
+ /* if not: add to bin */
+ entry->overflow_next = bin->overflow_list;
+ bin->overflow_list = entry;
+ lru_front(table, entry);
+ table->num++;
++ if (table->max_collisions < collisions)
++ table->max_collisions = collisions;
+ table->space_used += need_size;
+ } else {
+ /* if so: update data - needs a writelock */
+@@ -362,7 +370,7 @@
+ lock_quick_lock(&table->lock);
+ bin = &table->array[hash & table->size_mask];
+ lock_quick_lock(&bin->lock);
+- if((entry=bin_find_entry(table, bin, hash, key)))
++ if((entry=bin_find_entry(table, bin, hash, key, NULL)))
+ lru_touch(table, entry);
+ lock_quick_unlock(&table->lock);
+
+@@ -389,7 +397,7 @@
+ lock_quick_lock(&table->lock);
+ bin = &table->array[hash & table->size_mask];
+ lock_quick_lock(&bin->lock);
+- if((entry=bin_find_entry(table, bin, hash, key))) {
++ if((entry=bin_find_entry(table, bin, hash, key, NULL))) {
+ bin_overflow_remove(bin, entry);
+ lru_remove(table, entry);
+ } else {
+@@ -579,6 +587,7 @@
+ struct lruhash_bin* bin;
+ struct lruhash_entry* found, *reclaimlist = NULL;
+ size_t need_size;
++ size_t collisions;
+ fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
+ fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
+ fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
+@@ -593,7 +602,7 @@
+ lock_quick_lock(&bin->lock);
+
+ /* see if entry exists already */
+- if ((found = bin_find_entry(table, bin, hash, entry->key)) != NULL) {
++ if ((found = bin_find_entry(table, bin, hash, entry->key, &collisions)) != NULL) {
+ /* if so: keep the existing data - acquire a writelock */
+ lock_rw_wrlock(&found->lock);
+ }
+@@ -604,6 +613,8 @@
+ bin->overflow_list = entry;
+ lru_front(table, entry);
+ table->num++;
++ if (table->max_collisions < collisions)
++ table->max_collisions = collisions;
+ table->space_used += need_size;
+ /* return the entry that was presented, and lock it */
+ found = entry;
+--- contrib/unbound/util/storage/lruhash.h.orig
++++ contrib/unbound/util/storage/lruhash.h
+@@ -178,6 +178,8 @@
+ size_t space_used;
+ /** the amount of space the hash table is maximally allowed to use. */
+ size_t space_max;
++ /** the maximum collisions were detected during the lruhash_insert operations. */
++ size_t max_collisions;
+ };
+
+ /**
+@@ -357,10 +359,11 @@
+ * @param bin: hash bin to look into.
+ * @param hash: hash value to look for.
+ * @param key: key to look for.
++ * @param collisions: how many collisions were found during the search.
+ * @return: the entry or NULL if not found.
+ */
+ struct lruhash_entry* bin_find_entry(struct lruhash* table,
+- struct lruhash_bin* bin, hashvalue_type hash, void* key);
++ struct lruhash_bin* bin, hashvalue_type hash, void* key, size_t* collisions);
+
+ /**
+ * Remove entry from bin overflow chain.
+--- contrib/unbound/util/storage/slabhash.c.orig
++++ contrib/unbound/util/storage/slabhash.c
+@@ -242,3 +242,21 @@
+ }
+ return cnt;
+ }
++
++void get_slabhash_stats(struct slabhash* sh, long long* num, long long* collisions)
++{
++ size_t slab, cnt = 0, max_collisions = 0;
++
++ for(slab=0; slab<sh->size; slab++) {
++ lock_quick_lock(&sh->array[slab]->lock);
++ cnt += sh->array[slab]->num;
++ if (max_collisions < sh->array[slab]->max_collisions) {
++ max_collisions = sh->array[slab]->max_collisions;
++ }
++ lock_quick_unlock(&sh->array[slab]->lock);
++ }
++ if (num != NULL)
++ *num = cnt;
++ if (collisions != NULL)
++ *collisions = max_collisions;
++}
+--- contrib/unbound/util/storage/slabhash.h.orig
++++ contrib/unbound/util/storage/slabhash.h
+@@ -200,6 +200,15 @@
+ */
+ size_t count_slabhash_entries(struct slabhash* table);
+
++/**
++ * Retrieves number of items in slabhash and the current max collision level
++ * @param table: slabbed hash table.
++ * @param entries_count: where to save the current number of elements.
++ * @param max_collisions: where to save the current max collisions level.
++ */
++void get_slabhash_stats(struct slabhash* table,
++ long long* entries_count, long long* max_collisions);
++
+ /* --- test representation --- */
+ /** test structure contains test key */
+ struct slabhash_testkey {
+--- contrib/unbound/util/timehist.c.orig
++++ contrib/unbound/util/timehist.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -46,6 +46,7 @@
+ #include <sys/types.h>
+ #include "util/timehist.h"
+ #include "util/log.h"
++#include "util/timeval_func.h"
+
+ /** special timestwo operation for time values in histogram setup */
+ static void
+@@ -83,12 +84,12 @@
+
+ struct timehist* timehist_setup(void)
+ {
+- struct timehist* hist = (struct timehist*)calloc(1,
++ struct timehist* hist = (struct timehist*)calloc(1,
+ sizeof(struct timehist));
+ if(!hist)
+ return NULL;
+ hist->num = NUM_BUCKETS_HIST;
+- hist->buckets = (struct th_buck*)calloc(hist->num,
++ hist->buckets = (struct th_buck*)calloc(hist->num,
+ sizeof(struct th_buck));
+ if(!hist->buckets) {
+ free(hist);
+@@ -114,23 +115,6 @@
+ hist->buckets[i].count = 0;
+ }
+
+-/** histogram compare of time values */
+-static int
+-timeval_smaller(const struct timeval* x, const struct timeval* y)
+-{
+-#ifndef S_SPLINT_S
+- if(x->tv_sec < y->tv_sec)
+- return 1;
+- else if(x->tv_sec == y->tv_sec) {
+- if(x->tv_usec <= y->tv_usec)
+- return 1;
+- else return 0;
+- }
+- else return 0;
+-#endif
+-}
+-
+-
+ void timehist_insert(struct timehist* hist, struct timeval* tv)
+ {
+ size_t i;
+@@ -194,7 +178,7 @@
+ return res;
+ }
+
+-double
++double
+ timehist_quartile(struct timehist* hist, double q)
+ {
+ double lookfor, passed, res;
+@@ -209,22 +193,22 @@
+ lookfor *= q;
+ passed = 0;
+ i = 0;
+- while(i+1 < hist->num &&
++ while(i+1 < hist->num &&
+ passed+(double)hist->buckets[i].count < lookfor) {
+ passed += (double)hist->buckets[i++].count;
+ }
+ /* got the right bucket */
+ #ifndef S_SPLINT_S
+- low = (double)hist->buckets[i].lower.tv_sec +
++ low = (double)hist->buckets[i].lower.tv_sec +
+ (double)hist->buckets[i].lower.tv_usec/1000000.;
+- up = (double)hist->buckets[i].upper.tv_sec +
++ up = (double)hist->buckets[i].upper.tv_sec +
+ (double)hist->buckets[i].upper.tv_usec/1000000.;
+ #endif
+ res = (lookfor - passed)*(up-low)/((double)hist->buckets[i].count);
+ return low+res;
+ }
+
+-void
++void
+ timehist_export(struct timehist* hist, long long* array, size_t sz)
+ {
+ size_t i;
+@@ -235,7 +219,7 @@
+ array[i] = (long long)hist->buckets[i].count;
+ }
+
+-void
++void
+ timehist_import(struct timehist* hist, long long* array, size_t sz)
+ {
+ size_t i;
+--- /dev/null
++++ contrib/unbound/util/timeval_func.c
+@@ -0,0 +1,113 @@
++/*
++ * util/timeval_func.c - helpers to work with struct timeval values.
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains helpers to manipulate struct timeval values.
++ */
++
++#include "config.h"
++#include "timeval_func.h"
++
++/** subtract timers and the values do not overflow or become negative */
++void
++timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start)
++{
++#ifndef S_SPLINT_S
++ time_t end_usec = end->tv_usec;
++ d->tv_sec = end->tv_sec - start->tv_sec;
++ if(end_usec < start->tv_usec) {
++ end_usec += 1000000;
++ d->tv_sec--;
++ }
++ d->tv_usec = end_usec - start->tv_usec;
++#endif
++}
++
++/** add timers and the values do not overflow or become negative */
++void
++timeval_add(struct timeval* d, const struct timeval* add)
++{
++#ifndef S_SPLINT_S
++ d->tv_sec += add->tv_sec;
++ d->tv_usec += add->tv_usec;
++ if(d->tv_usec >= 1000000 ) {
++ d->tv_usec -= 1000000;
++ d->tv_sec++;
++ }
++#endif
++}
++
++/** divide sum of timers to get average */
++void
++timeval_divide(struct timeval* avg, const struct timeval* sum, long long d)
++{
++#ifndef S_SPLINT_S
++ long long leftover;
++ if(d <= 0) {
++ avg->tv_sec = 0;
++ avg->tv_usec = 0;
++ return;
++ }
++ avg->tv_sec = sum->tv_sec / d;
++ avg->tv_usec = sum->tv_usec / d;
++ /* handle fraction from seconds divide */
++ leftover = sum->tv_sec - avg->tv_sec*d;
++ if(leftover <= 0)
++ leftover = 0;
++ avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
++ if(avg->tv_sec < 0)
++ avg->tv_sec = 0;
++ if(avg->tv_usec < 0)
++ avg->tv_usec = 0;
++#endif
++}
++
++/** histogram compare of time values */
++int
++timeval_smaller(const struct timeval* x, const struct timeval* y)
++{
++#ifndef S_SPLINT_S
++ if(x->tv_sec < y->tv_sec)
++ return 1;
++ else if(x->tv_sec == y->tv_sec) {
++ if(x->tv_usec <= y->tv_usec)
++ return 1;
++ else return 0;
++ }
++ else return 0;
++#endif
++}
+--- /dev/null
++++ contrib/unbound/util/timeval_func.h
+@@ -0,0 +1,53 @@
++/*
++ * util/timeval_func.h - definitions of helpers for struct timeval values.
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains definitions of helpers to manipulate struct timeval
++ * values, implemented in the corresponding C file.
++ */
++#include <sys/time.h>
++
++#ifndef timeval_isset
++#define timeval_isset(tv) ((tv)->tv_sec || (tv)->tv_usec)
++#endif
++#ifndef timeval_clear
++#define timeval_clear(tv) ((tv)->tv_sec = (tv)->tv_usec = 0)
++#endif
++void timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start);
++void timeval_add(struct timeval* d, const struct timeval* add);
++void timeval_divide(struct timeval* avg, const struct timeval* sum, long long d);
++int timeval_smaller(const struct timeval* x, const struct timeval* y);
+--- contrib/unbound/validator/autotrust.c.orig
++++ contrib/unbound/validator/autotrust.c
+@@ -2376,6 +2376,8 @@
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+ edns.padding_block_size = 0;
++ edns.cookie_present = 0;
++ edns.cookie_valid = 0;
+ if(sldns_buffer_capacity(buf) < 65535)
+ edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
+ else edns.udp_size = 65535;
+--- contrib/unbound/validator/val_anchor.c.orig
++++ contrib/unbound/validator/val_anchor.c
+@@ -1322,3 +1322,24 @@
+ free(taglist);
+ return 0;
+ }
++
++struct trust_anchor*
++anchors_find_any_noninsecure(struct val_anchors* anchors)
++{
++ struct trust_anchor* ta, *next;
++ lock_basic_lock(&anchors->lock);
++ ta=(struct trust_anchor*)rbtree_first(anchors->tree);
++ while((rbnode_type*)ta != RBTREE_NULL) {
++ next = (struct trust_anchor*)rbtree_next(&ta->node);
++ lock_basic_lock(&ta->lock);
++ if(ta->numDS != 0 || ta->numDNSKEY != 0) {
++ /* not an insecurepoint */
++ lock_basic_unlock(&anchors->lock);
++ return ta;
++ }
++ lock_basic_unlock(&ta->lock);
++ ta = next;
++ }
++ lock_basic_unlock(&anchors->lock);
++ return NULL;
++}
+--- contrib/unbound/validator/val_anchor.h.orig
++++ contrib/unbound/validator/val_anchor.h
+@@ -240,4 +240,12 @@
+ int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs,
+ size_t namelen, uint16_t dclass, uint16_t keytag);
+
++/**
++ * Find an anchor that is not an insecure point, if any, or there are no
++ * DNSSEC verification anchors if none.
++ * @param anchors: anchor storage
++ * @return trust anchor or NULL. It is locked.
++ */
++struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors);
++
+ #endif /* VALIDATOR_VAL_ANCHOR_H */
+--- contrib/unbound/validator/val_kcache.c.orig
++++ contrib/unbound/validator/val_kcache.c
+@@ -81,17 +81,11 @@
+
+ void
+ key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey,
+- struct module_qstate* qstate)
++ int copy_reason)
+ {
+- struct key_entry_key* k = key_entry_copy(kkey);
++ struct key_entry_key* k = key_entry_copy(kkey, copy_reason);
+ if(!k)
+ return;
+- if(key_entry_isbad(k) && qstate->errinf &&
+- qstate->env->cfg->val_log_level >= 2) {
+- /* on malloc failure there is simply no reason string */
+- key_entry_set_reason(k, errinf_to_str_bogus(qstate));
+- key_entry_set_reason_bogus(k, errinf_to_reason_bogus(qstate));
+- }
+ key_entry_hash(k);
+ slabhash_insert(kcache->slab, k->entry.hash, &k->entry,
+ k->entry.data, NULL);
+--- contrib/unbound/validator/val_kcache.h.orig
++++ contrib/unbound/validator/val_kcache.h
+@@ -76,10 +76,10 @@
+ * @param kcache: the key cache.
+ * @param kkey: key entry key, assumed malloced in a region, is copied
+ * to perform update or insertion. Its data pointer is also copied.
+- * @param qstate: store errinf reason in case its bad.
++ * @param copy_reason: if the reason string needs to be copied (allocated).
+ */
+ void key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey,
+- struct module_qstate* qstate);
++ int copy_reason);
+
+ /**
+ * Remove an entry from the key cache.
+--- contrib/unbound/validator/val_kentry.c.orig
++++ contrib/unbound/validator/val_kentry.c
+@@ -152,7 +152,7 @@
+ }
+
+ struct key_entry_key*
+-key_entry_copy(struct key_entry_key* kkey)
++key_entry_copy(struct key_entry_key* kkey, int copy_reason)
+ {
+ struct key_entry_key* newk;
+ if(!kkey)
+@@ -190,7 +190,7 @@
+ }
+ packed_rrset_ptr_fixup(newd->rrset_data);
+ }
+- if(d->reason) {
++ if(copy_reason && d->reason && *d->reason != 0) {
+ newd->reason = strdup(d->reason);
+ if(!newd->reason) {
+ free(newd->rrset_data);
+@@ -199,6 +199,8 @@
+ free(newk);
+ return NULL;
+ }
++ } else {
++ newd->reason = NULL;
+ }
+ if(d->algo) {
+ newd->algo = (uint8_t*)strdup((char*)d->algo);
+@@ -237,22 +239,6 @@
+ return (int)(d->isbad);
+ }
+
+-void
+-key_entry_set_reason(struct key_entry_key* kkey, char* reason)
+-{
+- struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data;
+- d->reason = reason;
+-}
+-
+-void
+-key_entry_set_reason_bogus(struct key_entry_key* kkey, sldns_ede_code ede)
+-{
+- struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data;
+- if (ede != LDNS_EDE_NONE) { /* reason_bogus init is LDNS_EDE_NONE already */
+- d->reason_bogus = ede;
+- }
+-}
+-
+ char*
+ key_entry_get_reason(struct key_entry_key* kkey)
+ {
+@@ -294,6 +280,7 @@
+ struct key_entry_key*
+ key_entry_create_null(struct regional* region,
+ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now)
+ {
+ struct key_entry_key* k;
+@@ -302,8 +289,10 @@
+ return NULL;
+ d->ttl = now + ttl;
+ d->isbad = 0;
+- d->reason = NULL;
+- d->reason_bogus = LDNS_EDE_NONE;
++ d->reason = (!reason || *reason == 0)
++ ?NULL :(char*)regional_strdup(region, reason);
++ /* On allocation error we don't store the reason string */
++ d->reason_bogus = reason_bogus;
+ d->rrset_type = LDNS_RR_TYPE_DNSKEY;
+ d->rrset_data = NULL;
+ d->algo = NULL;
+@@ -313,7 +302,9 @@
+ struct key_entry_key*
+ key_entry_create_rrset(struct regional* region,
+ uint8_t* name, size_t namelen, uint16_t dclass,
+- struct ub_packed_rrset_key* rrset, uint8_t* sigalg, time_t now)
++ struct ub_packed_rrset_key* rrset, uint8_t* sigalg,
++ sldns_ede_code reason_bogus, const char* reason,
++ time_t now)
+ {
+ struct key_entry_key* k;
+ struct key_entry_data* d;
+@@ -323,8 +314,10 @@
+ return NULL;
+ d->ttl = rd->ttl + now;
+ d->isbad = 0;
+- d->reason = NULL;
+- d->reason_bogus = LDNS_EDE_NONE;
++ d->reason = (!reason || *reason == 0)
++ ?NULL :(char*)regional_strdup(region, reason);
++ /* On allocation error we don't store the reason string */
++ d->reason_bogus = reason_bogus;
+ d->rrset_type = ntohs(rrset->rk.type);
+ d->rrset_data = (struct packed_rrset_data*)regional_alloc_init(region,
+ rd, packed_rrset_sizeof(rd));
+@@ -341,7 +334,8 @@
+
+ struct key_entry_key*
+ key_entry_create_bad(struct regional* region,
+- uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now)
+ {
+ struct key_entry_key* k;
+@@ -350,8 +344,10 @@
+ return NULL;
+ d->ttl = now + ttl;
+ d->isbad = 1;
+- d->reason = NULL;
+- d->reason_bogus = LDNS_EDE_NONE;
++ d->reason = (!reason || *reason == 0)
++ ?NULL :(char*)regional_strdup(region, reason);
++ /* On allocation error we don't store the reason string */
++ d->reason_bogus = reason_bogus;
+ d->rrset_type = LDNS_RR_TYPE_DNSKEY;
+ d->rrset_data = NULL;
+ d->algo = NULL;
+--- contrib/unbound/validator/val_kentry.h.orig
++++ contrib/unbound/validator/val_kentry.h
+@@ -120,9 +120,11 @@
+ /**
+ * Copy a key entry, malloced.
+ * @param kkey: the key entry key (and data pointer) to copy.
++ * @param copy_reason: if the reason string needs to be copied (allocated).
+ * @return newly allocated entry or NULL on a failure to allocate memory.
+ */
+-struct key_entry_key* key_entry_copy(struct key_entry_key* kkey);
++struct key_entry_key* key_entry_copy(struct key_entry_key* kkey,
++ int copy_reason);
+
+ /**
+ * See if this is a null entry. Does not do locking.
+@@ -145,23 +147,6 @@
+ */
+ int key_entry_isbad(struct key_entry_key* kkey);
+
+-/**
+- * Set reason why a key is bad.
+- * @param kkey: bad key.
+- * @param reason: string to attach, you must allocate it.
+- * Not safe to call twice unless you deallocate it yourself.
+- */
+-void key_entry_set_reason(struct key_entry_key* kkey, char* reason);
+-
+-/**
+- * Set the EDE (RFC8914) code why the key is bad, if it
+- * exists (so not LDNS_EDE_NONE).
+- * @param kkey: bad key.
+- * @param ede: EDE code to attach to this key.
+- */
+-void key_entry_set_reason_bogus(struct key_entry_key* kkey, sldns_ede_code ede);
+-
+-
+ /**
+ * Get reason why a key is bad.
+ * @param kkey: bad key
+@@ -184,11 +169,14 @@
+ * @param namelen: length of name
+ * @param dclass: class of key entry. (host order);
+ * @param ttl: what ttl should the key have. relative.
++ * @param reason_bogus: accompanying EDE code.
++ * @param reason: accompanying NULL-terminated EDE string (or NULL).
+ * @param now: current time (added to ttl).
+ * @return new key entry or NULL on alloc failure
+ */
+ struct key_entry_key* key_entry_create_null(struct regional* region,
+- uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now);
+
+ /**
+@@ -199,12 +187,16 @@
+ * @param dclass: class of key entry. (host order);
+ * @param rrset: data for key entry. This is copied to the region.
+ * @param sigalg: signalled algorithm list (or NULL).
++ * @param reason_bogus: accompanying EDE code (usually LDNS_EDE_NONE).
++ * @param reason: accompanying NULL-terminated EDE string (or NULL).
+ * @param now: current time (added to ttl of rrset)
+ * @return new key entry or NULL on alloc failure
+ */
+ struct key_entry_key* key_entry_create_rrset(struct regional* region,
+- uint8_t* name, size_t namelen, uint16_t dclass,
+- struct ub_packed_rrset_key* rrset, uint8_t* sigalg, time_t now);
++ uint8_t* name, size_t namelen, uint16_t dclass,
++ struct ub_packed_rrset_key* rrset, uint8_t* sigalg,
++ sldns_ede_code reason_bogus, const char* reason,
++ time_t now);
+
+ /**
+ * Create a bad entry, in the given region.
+@@ -213,11 +205,14 @@
+ * @param namelen: length of name
+ * @param dclass: class of key entry. (host order);
+ * @param ttl: what ttl should the key have. relative.
++ * @param reason_bogus: accompanying EDE code.
++ * @param reason: accompanying NULL-terminated EDE string (or NULL).
+ * @param now: current time (added to ttl).
+ * @return new key entry or NULL on alloc failure
+ */
+ struct key_entry_key* key_entry_create_bad(struct regional* region,
+ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now);
+
+ /**
+--- contrib/unbound/validator/val_neg.c.orig
++++ contrib/unbound/validator/val_neg.c
+@@ -43,7 +43,7 @@
+ */
+ #include "config.h"
+ #ifdef HAVE_OPENSSL_SSL_H
+-#include "openssl/ssl.h"
++#include <openssl/ssl.h>
+ #define NSEC3_SHA_LEN SHA_DIGEST_LENGTH
+ #else
+ #define NSEC3_SHA_LEN 20
+@@ -1407,6 +1407,11 @@
+ /* Matching NSEC, use to generate No Data answer. Not creating answers
+ * yet for No Data proven using wildcard. */
+ if(nsec && nsec_proves_nodata(nsec, qinfo, &nodata_wc) && !nodata_wc) {
++ /* do not create nodata answers for qtype ANY, it is a query
++ * type, not an rrtype to disprove. Nameerrors are useful for
++ * qtype ANY, in the else branch. */
++ if(qinfo->qtype == LDNS_RR_TYPE_ANY)
++ return NULL;
+ if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len,
+ qinfo->qtype, qinfo->qclass, region, 2)))
+ return NULL;
+--- contrib/unbound/validator/val_nsec.c.orig
++++ contrib/unbound/validator/val_nsec.c
+@@ -174,12 +174,14 @@
+
+ /** check security status from cache or verify rrset, returns true if secure */
+ static int
+-nsec_verify_rrset(struct module_env* env, struct val_env* ve,
+- struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey,
+- char** reason, struct module_qstate* qstate)
++nsec_verify_rrset(struct module_env* env, struct val_env* ve,
++ struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey,
++ char** reason, sldns_ede_code* reason_bogus,
++ struct module_qstate* qstate)
+ {
+ struct packed_rrset_data* d = (struct packed_rrset_data*)
+ nsec->entry.data;
++ int verified = 0;
+ if(!d) return 0;
+ if(d->security == sec_status_secure)
+ return 1;
+@@ -187,7 +189,7 @@
+ if(d->security == sec_status_secure)
+ return 1;
+ d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason,
+- NULL, LDNS_SECTION_AUTHORITY, qstate);
++ reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified);
+ if(d->security == sec_status_secure) {
+ rrset_update_sec_status(env->rrset_cache, nsec, *env->now);
+ return 1;
+@@ -199,7 +201,7 @@
+ val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve,
+ struct query_info* qinfo, struct reply_info* rep,
+ struct key_entry_key* kkey, time_t* proof_ttl, char** reason,
+- struct module_qstate* qstate)
++ sldns_ede_code* reason_bogus, struct module_qstate* qstate)
+ {
+ struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns(
+ rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC,
+@@ -216,7 +218,8 @@
+ * 1) this is a delegation point and there is no DS
+ * 2) this is not a delegation point */
+ if(nsec) {
+- if(!nsec_verify_rrset(env, ve, nsec, kkey, reason, qstate)) {
++ if(!nsec_verify_rrset(env, ve, nsec, kkey, reason,
++ reason_bogus, qstate)) {
+ verbose(VERB_ALGO, "NSEC RRset for the "
+ "referral did not verify.");
+ return sec_status_bogus;
+@@ -225,6 +228,7 @@
+ if(sec == sec_status_bogus) {
+ /* something was wrong. */
+ *reason = "NSEC does not prove absence of DS";
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ return sec;
+ } else if(sec == sec_status_insecure) {
+ /* this wasn't a delegation point. */
+@@ -246,9 +250,11 @@
+ if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC))
+ continue;
+ if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, reason,
+- qstate)) {
++ reason_bogus, qstate)) {
+ verbose(VERB_ALGO, "NSEC for empty non-terminal "
+ "did not verify.");
++ *reason = "NSEC for empty non-terminal "
++ "did not verify.";
+ return sec_status_bogus;
+ }
+ if(nsec_proves_nodata(rep->rrsets[i], qinfo, &wc)) {
+--- contrib/unbound/validator/val_nsec.h.orig
++++ contrib/unbound/validator/val_nsec.h
+@@ -44,6 +44,7 @@
+ #ifndef VALIDATOR_VAL_NSEC_H
+ #define VALIDATOR_VAL_NSEC_H
+ #include "util/data/packed_rrset.h"
++#include "sldns/rrdef.h"
+ struct val_env;
+ struct module_env;
+ struct module_qstate;
+@@ -65,6 +66,7 @@
+ * @param kkey: key entry to use for verification of signatures.
+ * @param proof_ttl: if secure, the TTL of how long this proof lasts.
+ * @param reason: string explaining why bogus.
++ * @param reason_bogus: relevant EDE code for validation failure.
+ * @param qstate: qstate with region.
+ * @return security status.
+ * SECURE: proved absence of DS.
+@@ -75,7 +77,8 @@
+ enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env,
+ struct val_env* ve, struct query_info* qinfo,
+ struct reply_info* rep, struct key_entry_key* kkey,
+- time_t* proof_ttl, char** reason, struct module_qstate* qstate);
++ time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus,
++ struct module_qstate* qstate);
+
+ /**
+ * nsec typemap check, takes an NSEC-type bitmap as argument, checks for type.
+--- contrib/unbound/validator/val_nsec3.c.orig
++++ contrib/unbound/validator/val_nsec3.c
+@@ -57,6 +57,19 @@
+ /* we include nsec.h for the bitmap_has_type function */
+ #include "validator/val_nsec.h"
+ #include "sldns/sbuffer.h"
++#include "util/config_file.h"
++
++/**
++ * Max number of NSEC3 calculations at once, suspend query for later.
++ * 8 is low enough and allows for cases where multiple proofs are needed.
++ */
++#define MAX_NSEC3_CALCULATIONS 8
++/**
++ * When all allowed NSEC3 calculations at once resulted in error treat as
++ * bogus. NSEC3 hash errors are not cached and this helps breaks loops with
++ * erroneous data.
++ */
++#define MAX_NSEC3_ERRORS -1
+
+ /**
+ * This function we get from ldns-compat or from base system
+@@ -532,6 +545,17 @@
+ return memcmp(s1, s2, s1len);
+ }
+
++int
++nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region)
++{
++ if(ct->ct) return 1;
++ ct->ct = (rbtree_type*)regional_alloc(region, sizeof(*ct->ct));
++ if(!ct->ct) return 0;
++ ct->region = region;
++ rbtree_init(ct->ct, &nsec3_hash_cmp);
++ return 1;
++}
++
+ size_t
+ nsec3_get_hashed(sldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo,
+ size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max)
+@@ -646,7 +670,7 @@
+ c = (struct nsec3_cached_hash*)rbtree_search(table, &looki);
+ if(c) {
+ *hash = c;
+- return 1;
++ return 2;
+ }
+ /* create a new entry */
+ c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c));
+@@ -658,10 +682,10 @@
+ c->dname_len = dname_len;
+ r = nsec3_calc_hash(region, buf, c);
+ if(r != 1)
+- return r;
++ return r; /* returns -1 or 0 */
+ r = nsec3_calc_b32(region, buf, c);
+ if(r != 1)
+- return r;
++ return r; /* returns 0 */
+ #ifdef UNBOUND_DEBUG
+ n =
+ #else
+@@ -704,6 +728,7 @@
+ struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* s)
+ {
+ uint8_t* nm = s->rk.dname;
++ if(!hash) return 0; /* please clang */
+ /* compare, does hash of name based on params in this NSEC3
+ * match the owner name of this NSEC3?
+ * name must be: <hashlength>base32 . zone name
+@@ -730,34 +755,50 @@
+ * @param nmlen: length of name.
+ * @param rrset: nsec3 that matches is returned here.
+ * @param rr: rr number in nsec3 rrset that matches.
++ * @param calculations: current hash calculations.
+ * @return true if a matching NSEC3 is found, false if not.
+ */
+ static int
+ find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, uint8_t* nm, size_t nmlen,
+- struct ub_packed_rrset_key** rrset, int* rr)
++ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen,
++ struct ub_packed_rrset_key** rrset, int* rr,
++ int* calculations)
+ {
+ size_t i_rs;
+ int i_rr;
+ struct ub_packed_rrset_key* s;
+ struct nsec3_cached_hash* hash = NULL;
+ int r;
++ int calc_errors = 0;
+
+ /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
+ for(s=filter_first(flt, &i_rs, &i_rr); s;
+ s=filter_next(flt, &i_rs, &i_rr)) {
++ /* check if we are allowed more calculations */
++ if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ if(calc_errors == *calculations) {
++ *calculations = MAX_NSEC3_ERRORS;
++ }
++ break;
++ }
+ /* get name hashed for this NSEC3 RR */
+- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
++ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer,
+ s, i_rr, nm, nmlen, &hash);
+ if(r == 0) {
+ log_err("nsec3: malloc failure");
+ break; /* alloc failure */
+- } else if(r != 1)
+- continue; /* malformed NSEC3 */
+- else if(nsec3_hash_matches_owner(flt, hash, s)) {
+- *rrset = s; /* rrset with this name */
+- *rr = i_rr; /* matches hash with these parameters */
+- return 1;
++ } else if(r < 0) {
++ /* malformed NSEC3 */
++ calc_errors++;
++ (*calculations)++;
++ continue;
++ } else {
++ if(r == 1) (*calculations)++;
++ if(nsec3_hash_matches_owner(flt, hash, s)) {
++ *rrset = s; /* rrset with this name */
++ *rr = i_rr; /* matches hash with these parameters */
++ return 1;
++ }
+ }
+ }
+ *rrset = NULL;
+@@ -775,6 +816,7 @@
+ if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen))
+ return 0; /* malformed RR proves nothing */
+
++ if(!hash) return 0; /* please clang */
+ /* check the owner name is a hashed value . apex
+ * base32 encoded values must have equal length.
+ * hash_value and next hash value must have equal length. */
+@@ -823,35 +865,51 @@
+ * @param nmlen: length of name.
+ * @param rrset: covering NSEC3 rrset is returned here.
+ * @param rr: rr of cover is returned here.
++ * @param calculations: current hash calculations.
+ * @return true if a covering NSEC3 is found, false if not.
+ */
+ static int
+ find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, uint8_t* nm, size_t nmlen,
+- struct ub_packed_rrset_key** rrset, int* rr)
++ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen,
++ struct ub_packed_rrset_key** rrset, int* rr,
++ int* calculations)
+ {
+ size_t i_rs;
+ int i_rr;
+ struct ub_packed_rrset_key* s;
+ struct nsec3_cached_hash* hash = NULL;
+ int r;
++ int calc_errors = 0;
+
+ /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
+ for(s=filter_first(flt, &i_rs, &i_rr); s;
+ s=filter_next(flt, &i_rs, &i_rr)) {
++ /* check if we are allowed more calculations */
++ if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ if(calc_errors == *calculations) {
++ *calculations = MAX_NSEC3_ERRORS;
++ }
++ break;
++ }
+ /* get name hashed for this NSEC3 RR */
+- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
++ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer,
+ s, i_rr, nm, nmlen, &hash);
+ if(r == 0) {
+ log_err("nsec3: malloc failure");
+ break; /* alloc failure */
+- } else if(r != 1)
+- continue; /* malformed NSEC3 */
+- else if(nsec3_covers(flt->zone, hash, s, i_rr,
+- env->scratch_buffer)) {
+- *rrset = s; /* rrset with this name */
+- *rr = i_rr; /* covers hash with these parameters */
+- return 1;
++ } else if(r < 0) {
++ /* malformed NSEC3 */
++ calc_errors++;
++ (*calculations)++;
++ continue;
++ } else {
++ if(r == 1) (*calculations)++;
++ if(nsec3_covers(flt->zone, hash, s, i_rr,
++ env->scratch_buffer)) {
++ *rrset = s; /* rrset with this name */
++ *rr = i_rr; /* covers hash with these parameters */
++ return 1;
++ }
+ }
+ }
+ *rrset = NULL;
+@@ -869,11 +927,13 @@
+ * @param ct: cached hashes table.
+ * @param qinfo: query that is verified for.
+ * @param ce: closest encloser information is returned in here.
++ * @param calculations: current hash calculations.
+ * @return true if a closest encloser candidate is found, false if not.
+ */
+ static int
+-nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo, struct ce_response* ce)
++nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo,
++ struct ce_response* ce, int* calculations)
+ {
+ uint8_t* nm = qinfo->qname;
+ size_t nmlen = qinfo->qname_len;
+@@ -888,8 +948,12 @@
+ * may be the case. */
+
+ while(dname_subdomain_c(nm, flt->zone)) {
++ if(*calculations >= MAX_NSEC3_CALCULATIONS ||
++ *calculations == MAX_NSEC3_ERRORS) {
++ return 0;
++ }
+ if(find_matching_nsec3(env, flt, ct, nm, nmlen,
+- &ce->ce_rrset, &ce->ce_rr)) {
++ &ce->ce_rrset, &ce->ce_rr, calculations)) {
+ ce->ce = nm;
+ ce->ce_len = nmlen;
+ return 1;
+@@ -933,22 +997,38 @@
+ * If set true, and the return value is true, then you can be
+ * certain that the ce.nc_rrset and ce.nc_rr are set properly.
+ * @param ce: closest encloser information is returned in here.
++ * @param calculations: pointer to the current NSEC3 hash calculations.
+ * @return bogus if no closest encloser could be proven.
+ * secure if a closest encloser could be proven, ce is set.
+ * insecure if the closest-encloser candidate turns out to prove
+ * that an insecure delegation exists above the qname.
++ * unchecked if no more hash calculations are allowed at this point.
+ */
+ static enum sec_status
+-nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo, int prove_does_not_exist,
+- struct ce_response* ce)
++nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo,
++ int prove_does_not_exist, struct ce_response* ce, int* calculations)
+ {
+ uint8_t* nc;
+ size_t nc_len;
+ /* robust: clean out ce, in case it gets abused later */
+ memset(ce, 0, sizeof(*ce));
+
+- if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) {
++ if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce, calculations)) {
++ if(*calculations == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
++ "not find a candidate for the closest "
++ "encloser; all attempted hash calculations "
++ "were erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
++ "not find a candidate for the closest "
++ "encloser; reached MAX_NSEC3_CALCULATIONS "
++ "(%d); unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
+ "not find a candidate for the closest encloser.");
+ return sec_status_bogus;
+@@ -989,9 +1069,23 @@
+ /* Otherwise, we need to show that the next closer name is covered. */
+ next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len);
+ if(!find_covering_nsec3(env, flt, ct, nc, nc_len,
+- &ce->nc_rrset, &ce->nc_rr)) {
++ &ce->nc_rrset, &ce->nc_rr, calculations)) {
++ if(*calculations == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
++ "candidate encloser was the closest encloser; "
++ "all attempted hash calculations were "
++ "erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
++ "candidate encloser was the closest encloser; "
++ "reached MAX_NSEC3_CALCULATIONS (%d); "
++ "unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
+- "candidate encloser was the closest encloser");
++ "candidate encloser was the closest encloser");
+ return sec_status_bogus;
+ }
+ return sec_status_secure;
+@@ -1019,8 +1113,8 @@
+
+ /** Do the name error proof */
+ static enum sec_status
+-nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo)
++nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo, int* calc)
+ {
+ struct ce_response ce;
+ uint8_t* wc;
+@@ -1032,11 +1126,15 @@
+ /* First locate and prove the closest encloser to qname. We will
+ * use the variant that fails if the closest encloser turns out
+ * to be qname. */
+- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce);
++ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc);
+ if(sec != sec_status_secure) {
+ if(sec == sec_status_bogus)
+ verbose(VERB_ALGO, "nsec3 nameerror proof: failed "
+ "to prove a closest encloser");
++ else if(sec == sec_status_unchecked)
++ verbose(VERB_ALGO, "nsec3 nameerror proof: will "
++ "continue proving closest encloser after "
++ "suspend");
+ else verbose(VERB_ALGO, "nsec3 nameerror proof: closest "
+ "nsec3 is an insecure delegation");
+ return sec;
+@@ -1046,9 +1144,27 @@
+ /* At this point, we know that qname does not exist. Now we need
+ * to prove that the wildcard does not exist. */
+ log_assert(ce.ce);
+- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
+- if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen,
+- &wc_rrset, &wc_rr)) {
++ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen);
++ if(!wc) {
++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
++ "that the applicable wildcard did not exist.");
++ return sec_status_bogus;
++ }
++ if(!find_covering_nsec3(env, flt, ct, wc, wclen, &wc_rrset, &wc_rr, calc)) {
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
++ "that the applicable wildcard did not exist; "
++ "all attempted hash calculations were "
++ "erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
++ "that the applicable wildcard did not exist; "
++ "reached MAX_NSEC3_CALCULATIONS (%d); "
++ "unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
+ "that the applicable wildcard did not exist.");
+ return sec_status_bogus;
+@@ -1064,14 +1180,13 @@
+ enum sec_status
+ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey)
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+@@ -1079,7 +1194,7 @@
+ return sec_status_insecure; /* iteration count too high */
+ log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone",
+ flt.zone, 0, 0);
+- return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
++ return nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc);
+ }
+
+ /*
+@@ -1089,8 +1204,9 @@
+
+ /** Do the nodata proof */
+ static enum sec_status
+-nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo)
++nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo,
++ int* calc)
+ {
+ struct ce_response ce;
+ uint8_t* wc;
+@@ -1100,7 +1216,7 @@
+ enum sec_status sec;
+
+ if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len,
+- &rrset, &rr)) {
++ &rrset, &rr, calc)) {
+ /* cases 1 and 2 */
+ if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
+ verbose(VERB_ALGO, "proveNodata: Matching NSEC3 "
+@@ -1144,11 +1260,23 @@
+ }
+ return sec_status_secure;
+ }
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "proveNodata: all attempted hash "
++ "calculations were erroneous while finding a matching "
++ "NSEC3, bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "proveNodata: reached "
++ "MAX_NSEC3_CALCULATIONS (%d) while finding a "
++ "matching NSEC3; unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+
+ /* For cases 3 - 5, we need the proven closest encloser, and it
+ * can't match qname. Although, at this point, we know that it
+ * won't since we just checked that. */
+- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce);
++ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc);
+ if(sec == sec_status_bogus) {
+ verbose(VERB_ALGO, "proveNodata: did not match qname, "
+ "nor found a proven closest encloser.");
+@@ -1157,14 +1285,17 @@
+ verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure "
+ "delegation.");
+ return sec_status_insecure;
++ } else if(sec==sec_status_unchecked) {
++ return sec_status_unchecked;
+ }
+
+ /* Case 3: removed */
+
+ /* Case 4: */
+ log_assert(ce.ce);
+- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
+- if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) {
++ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen);
++ if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr,
++ calc)) {
+ /* found wildcard */
+ if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
+ verbose(VERB_ALGO, "nsec3 nodata proof: matching "
+@@ -1195,6 +1326,18 @@
+ }
+ return sec_status_secure;
+ }
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 nodata proof: all attempted hash "
++ "calculations were erroneous while matching "
++ "wildcard, bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 nodata proof: reached "
++ "MAX_NSEC3_CALCULATIONS (%d) while matching "
++ "wildcard, unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+
+ /* Case 5: */
+ /* Due to forwarders, cnames, and other collating effects, we
+@@ -1223,28 +1366,27 @@
+ enum sec_status
+ nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey)
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+ if(nsec3_iteration_count_high(ve, &flt, kkey))
+ return sec_status_insecure; /* iteration count too high */
+- return nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
++ return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
+ }
+
+ enum sec_status
+ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc)
++ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+ struct ce_response ce;
+ uint8_t* nc;
+@@ -1254,7 +1396,6 @@
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+@@ -1272,8 +1413,22 @@
+ /* Now we still need to prove that the original data did not exist.
+ * Otherwise, we need to show that the next closer name is covered. */
+ next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len);
+- if(!find_covering_nsec3(env, &flt, &ct, nc, nc_len,
+- &ce.nc_rrset, &ce.nc_rr)) {
++ if(!find_covering_nsec3(env, &flt, ct, nc, nc_len,
++ &ce.nc_rrset, &ce.nc_rr, calc)) {
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "proveWildcard: did not find a "
++ "covering NSEC3 that covered the next closer "
++ "name; all attempted hash calculations were "
++ "erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "proveWildcard: did not find a "
++ "covering NSEC3 that covered the next closer "
++ "name; reached MAX_NSEC3_CALCULATIONS "
++ "(%d); unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "proveWildcard: did not find a covering "
+ "NSEC3 that covered the next closer name.");
+ return sec_status_bogus;
+@@ -1294,6 +1449,7 @@
+ {
+ struct packed_rrset_data* d;
+ size_t i;
++ int verified = 0;
+ for(i=0; i<num; i++) {
+ d = (struct packed_rrset_data*)list[i]->entry.data;
+ if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3))
+@@ -1304,7 +1460,8 @@
+ if(d->security == sec_status_secure)
+ continue;
+ d->security = val_verify_rrset_entry(env, ve, list[i], kkey,
+- reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate);
++ reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate,
++ &verified);
+ if(d->security != sec_status_secure) {
+ verbose(VERB_ALGO, "NSEC3 did not verify");
+ return 0;
+@@ -1318,13 +1475,16 @@
+ nsec3_prove_nods(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+ struct query_info* qinfo, struct key_entry_key* kkey, char** reason,
+- sldns_ede_code* reason_bogus, struct module_qstate* qstate)
++ sldns_ede_code* reason_bogus, struct module_qstate* qstate,
++ struct nsec3_cache_table* ct)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+ struct ce_response ce;
+ struct ub_packed_rrset_key* rrset;
+ int rr;
++ int calc = 0;
++ enum sec_status sec;
++
+ log_assert(qinfo->qtype == LDNS_RR_TYPE_DS);
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) {
+@@ -1335,7 +1495,6 @@
+ *reason = "not all NSEC3 records secure";
+ return sec_status_bogus; /* not all NSEC3 records secure */
+ }
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone) {
+ *reason = "no NSEC3 records";
+@@ -1346,8 +1505,8 @@
+
+ /* Look for a matching NSEC3 to qname -- this is the normal
+ * NODATA case. */
+- if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len,
+- &rrset, &rr)) {
++ if(find_matching_nsec3(env, &flt, ct, qinfo->qname, qinfo->qname_len,
++ &rrset, &rr, &calc)) {
+ /* If the matching NSEC3 has the SOA bit set, it is from
+ * the wrong zone (the child instead of the parent). If
+ * it has the DS bit set, then we were lied to. */
+@@ -1370,10 +1529,24 @@
+ /* Otherwise, this proves no DS. */
+ return sec_status_secure;
+ }
++ if(calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 provenods: all attempted hash "
++ "calculations were erroneous while finding a matching "
++ "NSEC3, bogus");
++ return sec_status_bogus;
++ } else if(calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 provenods: reached "
++ "MAX_NSEC3_CALCULATIONS (%d) while finding a "
++ "matching NSEC3, unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+
+ /* Otherwise, we are probably in the opt-out case. */
+- if(nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce)
+- != sec_status_secure) {
++ sec = nsec3_prove_closest_encloser(env, &flt, ct, qinfo, 1, &ce, &calc);
++ if(sec == sec_status_unchecked) {
++ return sec_status_unchecked;
++ } else if(sec != sec_status_secure) {
+ /* an insecure delegation *above* the qname does not prove
+ * anything about this qname exactly, and bogus is bogus */
+ verbose(VERB_ALGO, "nsec3 provenods: did not match qname, "
+@@ -1407,17 +1580,16 @@
+
+ enum sec_status
+ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
+- struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata)
++ struct ub_packed_rrset_key** list, size_t num,
++ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+ enum sec_status sec, secnx;
+- rbtree_type ct;
+ struct nsec3_filter flt;
+ *nodata = 0;
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+@@ -1427,16 +1599,20 @@
+ /* try nxdomain and nodata after another, while keeping the
+ * hash cache intact */
+
+- secnx = nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
++ secnx = nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc);
+ if(secnx==sec_status_secure)
+ return sec_status_secure;
+- sec = nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
++ else if(secnx == sec_status_unchecked)
++ return sec_status_unchecked;
++ sec = nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
+ if(sec==sec_status_secure) {
+ *nodata = 1;
+ } else if(sec == sec_status_insecure) {
+ *nodata = 1;
+ } else if(secnx == sec_status_insecure) {
+ sec = sec_status_insecure;
++ } else if(sec == sec_status_unchecked) {
++ return sec_status_unchecked;
+ }
+ return sec;
+ }
+--- contrib/unbound/validator/val_nsec3.h.orig
++++ contrib/unbound/validator/val_nsec3.h
+@@ -98,6 +98,15 @@
+ /** The SHA1 hash algorithm for NSEC3 */
+ #define NSEC3_HASH_SHA1 0x01
+
++/**
++* Cache table for NSEC3 hashes.
++* It keeps a *pointer* to the region its items are allocated.
++*/
++struct nsec3_cache_table {
++ rbtree_type* ct;
++ struct regional* region;
++};
++
+ /**
+ * Determine if the set of NSEC3 records provided with a response prove NAME
+ * ERROR. This means that the NSEC3s prove a) the closest encloser exists,
+@@ -110,14 +119,18 @@
+ * @param num: number of RRsets in the array to examine.
+ * @param qinfo: query that is verified for.
+ * @param kkey: key entry that signed the NSEC3s.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the Name Error is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey);
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA
+@@ -144,15 +157,18 @@
+ * @param num: number of RRsets in the array to examine.
+ * @param qinfo: query that is verified for.
+ * @param kkey: key entry that signed the NSEC3s.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey);
+-
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * Prove that a positive wildcard match was appropriate (no direct match
+@@ -166,14 +182,18 @@
+ * @param kkey: key entry that signed the NSEC3s.
+ * @param wc: The purported wildcard that matched. This is the wildcard name
+ * as *.wildcard.name., with the *. label already removed.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc);
++ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * Prove that a DS response either had no DS, or wasn't a delegation point.
+@@ -189,17 +209,20 @@
+ * @param reason: string for bogus result.
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param qstate: qstate with region.
++ * @param ct: cached hashes table.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
+ * or if there was no DS in an insecure (i.e., opt-in) way,
+- * INDETERMINATE if it was clear that this wasn't a delegation point.
++ * INDETERMINATE if it was clear that this wasn't a delegation point,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nods(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+ struct query_info* qinfo, struct key_entry_key* kkey, char** reason,
+- sldns_ede_code* reason_bogus, struct module_qstate* qstate);
++ sldns_ede_code* reason_bogus, struct module_qstate* qstate,
++ struct nsec3_cache_table* ct);
+
+ /**
+ * Prove NXDOMAIN or NODATA.
+@@ -212,14 +235,18 @@
+ * @param kkey: key entry that signed the NSEC3s.
+ * @param nodata: if return value is secure, this indicates if nodata or
+ * nxdomain was proven.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata);
++ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * The NSEC3 hash result storage.
+@@ -256,6 +283,14 @@
+ */
+ int nsec3_hash_cmp(const void* c1, const void* c2);
+
++/**
++ * Initialise the NSEC3 cache table.
++ * @param ct: the nsec3 cache table.
++ * @param region: the region where allocations for the table will happen.
++ * @return true on success, false on malloc error.
++ */
++int nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region);
++
+ /**
+ * Obtain the hash of an owner name.
+ * Used internally by the nsec3 proof functions in this file.
+@@ -272,7 +307,8 @@
+ * @param dname_len: the length of the name.
+ * @param hash: the hash node is returned on success.
+ * @return:
+- * 1 on success, either from cache or newly hashed hash is returned.
++ * 2 on success, hash from cache is returned.
++ * 1 on success, newly computed hash is returned.
+ * 0 on a malloc failure.
+ * -1 if the NSEC3 rr was badly formatted (i.e. formerr).
+ */
+--- contrib/unbound/validator/val_sigcrypt.c.orig
++++ contrib/unbound/validator/val_sigcrypt.c
+@@ -48,6 +48,7 @@
+ #include "util/data/msgparse.h"
+ #include "util/data/dname.h"
+ #include "util/rbtree.h"
++#include "util/rfc_1982.h"
+ #include "util/module.h"
+ #include "util/net_help.h"
+ #include "util/regional.h"
+@@ -78,6 +79,9 @@
+ #include <openssl/engine.h>
+ #endif
+
++/** Maximum number of RRSIG validations for an RRset. */
++#define MAX_VALIDATE_RRSIGS 8
++
+ /** return number of rrs in an rrset */
+ static size_t
+ rrset_get_count(struct ub_packed_rrset_key* rrset)
+@@ -541,6 +545,8 @@
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param section: section of packet where this rrset comes from.
+ * @param qstate: qstate with region.
++ * @param numverified: incremented when the number of RRSIG validations
++ * increases.
+ * @return secure if any key signs *this* signature. bogus if no key signs it,
+ * unchecked on error, or indeterminate if all keys are not supported by
+ * the crypto library (openssl3+ only).
+@@ -551,7 +557,8 @@
+ struct ub_packed_rrset_key* dnskey, size_t sig_idx,
+ struct rbtree_type** sortree,
+ char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int* numverified)
+ {
+ /* find matching keys and check them */
+ enum sec_status sec = sec_status_bogus;
+@@ -575,6 +582,7 @@
+ tag != dnskey_calc_keytag(dnskey, i))
+ continue;
+ numchecked ++;
++ (*numverified)++;
+
+ /* see if key verifies */
+ sec = dnskey_verify_rrset_sig(env->scratch,
+@@ -585,6 +593,13 @@
+ return sec;
+ else if(sec == sec_status_indeterminate)
+ numindeterminate ++;
++ if(*numverified > MAX_VALIDATE_RRSIGS) {
++ *reason = "too many RRSIG validations";
++ if(reason_bogus)
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ verbose(VERB_ALGO, "verify sig: too many RRSIG validations");
++ return sec_status_bogus;
++ }
+ }
+ if(numchecked == 0) {
+ *reason = "signatures from unknown keys";
+@@ -608,7 +623,7 @@
+ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey,
+ uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate, int* verified)
+ {
+ enum sec_status sec;
+ size_t i, num;
+@@ -616,6 +631,7 @@
+ /* make sure that for all DNSKEY algorithms there are valid sigs */
+ struct algo_needs needs;
+ int alg;
++ *verified = 0;
+
+ num = rrset_get_sigcount(rrset);
+ if(num == 0) {
+@@ -640,7 +656,7 @@
+ for(i=0; i<num; i++) {
+ sec = dnskeyset_verify_rrset_sig(env, ve, *env->now, rrset,
+ dnskey, i, &sortree, reason, reason_bogus,
+- section, qstate);
++ section, qstate, verified);
+ /* see which algorithm has been fixed up */
+ if(sec == sec_status_secure) {
+ if(!sigalg)
+@@ -652,6 +668,13 @@
+ algo_needs_set_bogus(&needs,
+ (uint8_t)rrset_get_sig_algo(rrset, i));
+ }
++ if(*verified > MAX_VALIDATE_RRSIGS) {
++ verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations");
++ *reason = "too many RRSIG validations";
++ if(reason_bogus)
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ return sec_status_bogus;
++ }
+ }
+ if(sigalg && (alg=algo_needs_missing(&needs)) != 0) {
+ verbose(VERB_ALGO, "rrset failed to verify: "
+@@ -690,6 +713,7 @@
+ int buf_canon = 0;
+ uint16_t tag = dnskey_calc_keytag(dnskey, dnskey_idx);
+ int algo = dnskey_get_algo(dnskey, dnskey_idx);
++ int numverified = 0;
+
+ num = rrset_get_sigcount(rrset);
+ if(num == 0) {
+@@ -713,14 +737,22 @@
+ if(sec == sec_status_secure)
+ return sec;
+ numchecked ++;
++ numverified ++;
+ if(sec == sec_status_indeterminate)
+ numindeterminate ++;
++ if(numverified > MAX_VALIDATE_RRSIGS) {
++ verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations");
++ *reason = "too many RRSIG validations";
++ if(reason_bogus)
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ return sec_status_bogus;
++ }
+ }
+ verbose(VERB_ALGO, "rrset failed to verify: all signatures are bogus");
+ if(!numchecked) {
+- *reason = "signature missing";
++ *reason = "signature for expected key and algorithm missing";
+ if(reason_bogus)
+- *reason_bogus = LDNS_EDE_RRSIGS_MISSING;
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ } else if(numchecked == numindeterminate) {
+ verbose(VERB_ALGO, "rrset failed to verify due to algorithm "
+ "refusal by cryptolib");
+@@ -1378,44 +1410,6 @@
+ (unsigned)incep, (unsigned)now);
+ }
+
+-/** RFC 1982 comparison, uses unsigned integers, and tries to avoid
+- * compiler optimization (eg. by avoiding a-b<0 comparisons),
+- * this routine matches compare_serial(), for SOA serial number checks */
+-static int
+-compare_1982(uint32_t a, uint32_t b)
+-{
+- /* for 32 bit values */
+- const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
+-
+- if (a == b) {
+- return 0;
+- } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
+- return -1;
+- } else {
+- return 1;
+- }
+-}
+-
+-/** if we know that b is larger than a, return the difference between them,
+- * that is the distance between them. in RFC1982 arith */
+-static uint32_t
+-subtract_1982(uint32_t a, uint32_t b)
+-{
+- /* for 32 bit values */
+- const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
+-
+- if(a == b)
+- return 0;
+- if(a < b && b - a < cutoff) {
+- return b-a;
+- }
+- if(a > b && a - b > cutoff) {
+- return ((uint32_t)0xffffffff) - (a-b-1);
+- }
+- /* wrong case, b smaller than a */
+- return 0;
+-}
+-
+ /** check rrsig dates */
+ static int
+ check_dates(struct val_env* ve, uint32_t unow, uint8_t* expi_p,
+--- contrib/unbound/validator/val_sigcrypt.h.orig
++++ contrib/unbound/validator/val_sigcrypt.h
+@@ -260,6 +260,7 @@
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param section: section of packet where this rrset comes from.
+ * @param qstate: qstate with region.
++ * @param verified: if not NULL the number of RRSIG validations is returned.
+ * @return SECURE if one key in the set verifies one rrsig.
+ * UNCHECKED on allocation errors, unsupported algorithms, malformed data,
+ * and BOGUS on verification failures (no keys match any signatures).
+@@ -268,7 +269,7 @@
+ struct val_env* ve, struct ub_packed_rrset_key* rrset,
+ struct ub_packed_rrset_key* dnskey, uint8_t* sigalg,
+ char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate);
++ sldns_pkt_section section, struct module_qstate* qstate, int* verified);
+
+
+ /**
+--- contrib/unbound/validator/val_utils.c.orig
++++ contrib/unbound/validator/val_utils.c
+@@ -58,6 +58,10 @@
+ #include "sldns/wire2str.h"
+ #include "sldns/parseutil.h"
+
++/** Maximum allowed digest match failures per DS, for DNSKEYs with the same
++ * properties */
++#define MAX_DS_MATCH_FAILURES 4
++
+ enum val_classification
+ val_classify_response(uint16_t query_flags, struct query_info* origqinf,
+ struct query_info* qinf, struct reply_info* rep, size_t skip)
+@@ -336,7 +340,8 @@
+ val_verify_rrset(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys,
+ uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int *verified)
+ {
+ enum sec_status sec;
+ struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
+@@ -346,6 +351,7 @@
+ log_nametypeclass(VERB_ALGO, "verify rrset cached",
+ rrset->rk.dname, ntohs(rrset->rk.type),
+ ntohs(rrset->rk.rrset_class));
++ *verified = 0;
+ return d->security;
+ }
+ /* check in the cache if verification has already been done */
+@@ -354,12 +360,13 @@
+ log_nametypeclass(VERB_ALGO, "verify rrset from cache",
+ rrset->rk.dname, ntohs(rrset->rk.type),
+ ntohs(rrset->rk.rrset_class));
++ *verified = 0;
+ return d->security;
+ }
+ log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname,
+ ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class));
+ sec = dnskeyset_verify_rrset(env, ve, rrset, keys, sigalg, reason,
+- reason_bogus, section, qstate);
++ reason_bogus, section, qstate, verified);
+ verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec));
+ regional_free_all(env->scratch);
+
+@@ -393,7 +400,8 @@
+ val_verify_rrset_entry(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey,
+ char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int* verified)
+ {
+ /* temporary dnskey rrset-key */
+ struct ub_packed_rrset_key dnskey;
+@@ -407,7 +415,7 @@
+ dnskey.entry.key = &dnskey;
+ dnskey.entry.data = kd->rrset_data;
+ sec = val_verify_rrset(env, ve, rrset, &dnskey, kd->algo, reason,
+- reason_bogus, section, qstate);
++ reason_bogus, section, qstate, verified);
+ return sec;
+ }
+
+@@ -439,6 +447,12 @@
+ if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset,
+ ds_idx)) {
+ verbose(VERB_ALGO, "DS match attempt failed");
++ if(numchecked > numhashok + MAX_DS_MATCH_FAILURES) {
++ verbose(VERB_ALGO, "DS match attempt reached "
++ "MAX_DS_MATCH_FAILURES (%d); bogus",
++ MAX_DS_MATCH_FAILURES);
++ return sec_status_bogus;
++ }
+ continue;
+ }
+ numhashok++;
+@@ -587,16 +601,18 @@
+ return key_entry_create_rrset(region,
+ ds_rrset->rk.dname, ds_rrset->rk.dname_len,
+ ntohs(ds_rrset->rk.rrset_class), dnskey_rrset,
+- downprot?sigalg:NULL, *env->now);
++ downprot?sigalg:NULL, LDNS_EDE_NONE, NULL,
++ *env->now);
+ } else if(sec == sec_status_insecure) {
+ return key_entry_create_null(region, ds_rrset->rk.dname,
+- ds_rrset->rk.dname_len,
++ ds_rrset->rk.dname_len,
+ ntohs(ds_rrset->rk.rrset_class),
+- rrset_get_ttl(ds_rrset), *env->now);
++ rrset_get_ttl(ds_rrset), *reason_bogus, *reason,
++ *env->now);
+ }
+ return key_entry_create_bad(region, ds_rrset->rk.dname,
+ ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class),
+- BOGUS_KEY_TTL, *env->now);
++ BOGUS_KEY_TTL, *reason_bogus, *reason, *env->now);
+ }
+
+ enum sec_status
+@@ -694,7 +710,7 @@
+ has_useful_ta = 1;
+
+ sec = dnskey_verify_rrset(env, ve, dnskey_rrset,
+- ta_dnskey, i, reason, NULL, LDNS_SECTION_ANSWER, qstate);
++ ta_dnskey, i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate);
+ if(sec == sec_status_secure) {
+ if(!sigalg || algo_needs_set_secure(&needs,
+ (uint8_t)dnskey_get_algo(ta_dnskey, i))) {
+@@ -743,16 +759,17 @@
+ return key_entry_create_rrset(region,
+ dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len,
+ ntohs(dnskey_rrset->rk.rrset_class), dnskey_rrset,
+- downprot?sigalg:NULL, *env->now);
++ downprot?sigalg:NULL, LDNS_EDE_NONE, NULL, *env->now);
+ } else if(sec == sec_status_insecure) {
+ return key_entry_create_null(region, dnskey_rrset->rk.dname,
+ dnskey_rrset->rk.dname_len,
+ ntohs(dnskey_rrset->rk.rrset_class),
+- rrset_get_ttl(dnskey_rrset), *env->now);
++ rrset_get_ttl(dnskey_rrset), *reason_bogus, *reason,
++ *env->now);
+ }
+ return key_entry_create_bad(region, dnskey_rrset->rk.dname,
+ dnskey_rrset->rk.dname_len, ntohs(dnskey_rrset->rk.rrset_class),
+- BOGUS_KEY_TTL, *env->now);
++ BOGUS_KEY_TTL, *reason_bogus, *reason, *env->now);
+ }
+
+ int
+--- contrib/unbound/validator/val_utils.h.orig
++++ contrib/unbound/validator/val_utils.h
+@@ -124,12 +124,14 @@
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param section: section of packet where this rrset comes from.
+ * @param qstate: qstate with region.
++ * @param verified: if not NULL, the number of RRSIG validations is returned.
+ * @return security status of verification.
+ */
+ enum sec_status val_verify_rrset_entry(struct module_env* env,
+ struct val_env* ve, struct ub_packed_rrset_key* rrset,
+ struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate);
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int* verified);
+
+ /**
+ * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but
+--- contrib/unbound/validator/validator.c.orig
++++ contrib/unbound/validator/validator.c
+@@ -64,22 +64,27 @@
+ #include "sldns/wire2str.h"
+ #include "sldns/str2wire.h"
+
++/** Max number of RRSIGs to validate at once, suspend query for later. */
++#define MAX_VALIDATE_AT_ONCE 8
++/** Max number of validation suspends allowed, error out otherwise. */
++#define MAX_VALIDATION_SUSPENDS 16
++
+ /* forward decl for cache response and normal super inform calls of a DS */
+ static void process_ds_response(struct module_qstate* qstate,
+ struct val_qstate* vq, int id, int rcode, struct dns_msg* msg,
+- struct query_info* qinfo, struct sock_list* origin);
++ struct query_info* qinfo, struct sock_list* origin, int* suspend);
+
+
+-/* Updates the suplied EDE (RFC8914) code selectively so we don't loose
+- * a more specific code
+- */
++/* Updates the suplied EDE (RFC8914) code selectively so we don't lose
++ * a more specific code */
+ static void
+ update_reason_bogus(struct reply_info* rep, sldns_ede_code reason_bogus)
+ {
+- if (rep->reason_bogus == LDNS_EDE_DNSSEC_BOGUS ||
+- rep->reason_bogus == LDNS_EDE_NONE) {
+- rep->reason_bogus = reason_bogus;
+- }
++ if(reason_bogus == LDNS_EDE_NONE) return;
++ if(reason_bogus == LDNS_EDE_DNSSEC_BOGUS
++ && rep->reason_bogus != LDNS_EDE_NONE
++ && rep->reason_bogus != LDNS_EDE_DNSSEC_BOGUS) return;
++ rep->reason_bogus = reason_bogus;
+ }
+
+
+@@ -200,6 +205,17 @@
+ log_err("validator: could not apply configuration settings.");
+ return 0;
+ }
++ if(env->cfg->disable_edns_do) {
++ struct trust_anchor* anchor = anchors_find_any_noninsecure(
++ env->anchors);
++ if(anchor) {
++ char b[LDNS_MAX_DOMAINLEN+2];
++ dname_str(anchor->name, b);
++ log_warn("validator: disable-edns-do is enabled, but there is a trust anchor for '%s'. Since DNSSEC could not work, the disable-edns-do setting is turned off. Continuing without it.", b);
++ lock_basic_unlock(&anchor->lock);
++ env->cfg->disable_edns_do = 0;
++ }
++ }
+
+ return 1;
+ }
+@@ -281,6 +297,21 @@
+ return val_new_getmsg(qstate, vq);
+ }
+
++/** reset validator query state for query restart */
++static void
++val_restart(struct val_qstate* vq)
++{
++ struct comm_timer* temp_timer;
++ int restart_count;
++ if(!vq) return;
++ temp_timer = vq->suspend_timer;
++ restart_count = vq->restart_count+1;
++ memset(vq, 0, sizeof(*vq));
++ vq->suspend_timer = temp_timer;
++ vq->restart_count = restart_count;
++ vq->state = VAL_INIT_STATE;
++}
++
+ /**
+ * Exit validation with an error status
+ *
+@@ -587,30 +618,42 @@
+ * completed.
+ *
+ * @param qstate: query state.
++ * @param vq: validator query state.
+ * @param env: module env for verify.
+ * @param ve: validator env for verify.
+ * @param qchase: query that was made.
+ * @param chase_reply: answer to validate.
+ * @param key_entry: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ * @return false if any of the rrsets in the an or ns sections of the message
+ * fail to verify. The message is then set to bogus.
+ */
+ static int
+-validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
+- struct val_env* ve, struct query_info* qchase,
+- struct reply_info* chase_reply, struct key_entry_key* key_entry)
++validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq,
++ struct module_env* env, struct val_env* ve, struct query_info* qchase,
++ struct reply_info* chase_reply, struct key_entry_key* key_entry,
++ int* suspend)
+ {
+ uint8_t* sname;
+ size_t i, slen;
+ struct ub_packed_rrset_key* s;
+ enum sec_status sec;
+- int dname_seen = 0;
++ int dname_seen = 0, num_verifies = 0, verified, have_state = 0;
+ char* reason = NULL;
+ sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ *suspend = 0;
++ if(vq->msg_signatures_state) {
++ /* Pick up the state, and reset it, may not be needed now. */
++ vq->msg_signatures_state = 0;
++ have_state = 1;
++ }
+
+ /* validate the ANSWER section */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
++ if(have_state && i <= vq->msg_signatures_index)
++ continue;
+ s = chase_reply->rrsets[i];
+ /* Skip the CNAME following a (validated) DNAME.
+ * Because of the normalization routines in the iterator,
+@@ -629,7 +672,7 @@
+
+ /* Verify the answer rrset */
+ sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason,
+- &reason_bogus, LDNS_SECTION_ANSWER, qstate);
++ &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified);
+ /* If the (answer) rrset failed to validate, then this
+ * message is BAD. */
+ if(sec != sec_status_secure) {
+@@ -654,14 +697,33 @@
+ ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) {
+ dname_seen = 1;
+ }
++ num_verifies += verified;
++ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
++ i+1 < (env->cfg->val_clean_additional?
++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets:
++ chase_reply->rrset_count)) {
++ /* If the number of RRSIGs exceeds the maximum in
++ * one go, suspend. Only suspend if there is a next
++ * rrset to verify, i+1<loopmax. Store where to
++ * continue later. */
++ *suspend = 1;
++ vq->msg_signatures_state = 1;
++ vq->msg_signatures_index = i;
++ verbose(VERB_ALGO, "msg signature validation "
++ "suspended");
++ return 0;
++ }
+ }
+
+ /* validate the AUTHORITY section */
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
++ if(have_state && i <= vq->msg_signatures_index)
++ continue;
+ s = chase_reply->rrsets[i];
+ sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason,
+- &reason_bogus, LDNS_SECTION_AUTHORITY, qstate);
++ &reason_bogus, LDNS_SECTION_AUTHORITY, qstate,
++ &verified);
+ /* If anything in the authority section fails to be secure,
+ * we have a bad message. */
+ if(sec != sec_status_secure) {
+@@ -675,6 +737,18 @@
+ update_reason_bogus(chase_reply, reason_bogus);
+ return 0;
+ }
++ num_verifies += verified;
++ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
++ i+1 < (env->cfg->val_clean_additional?
++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets:
++ chase_reply->rrset_count)) {
++ *suspend = 1;
++ vq->msg_signatures_state = 1;
++ vq->msg_signatures_index = i;
++ verbose(VERB_ALGO, "msg signature validation "
++ "suspended");
++ return 0;
++ }
+ }
+
+ /* If set, the validator should clean the additional section of
+@@ -684,22 +758,103 @@
+ /* attempt to validate the ADDITIONAL section rrsets */
+ for(i=chase_reply->an_numrrsets+chase_reply->ns_numrrsets;
+ i<chase_reply->rrset_count; i++) {
++ if(have_state && i <= vq->msg_signatures_index)
++ continue;
+ s = chase_reply->rrsets[i];
+ /* only validate rrs that have signatures with the key */
+ /* leave others unchecked, those get removed later on too */
+ val_find_rrset_signer(s, &sname, &slen);
+
++ verified = 0;
+ if(sname && query_dname_compare(sname, key_entry->name)==0)
+ (void)val_verify_rrset_entry(env, ve, s, key_entry,
+- &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate);
++ &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate,
++ &verified);
+ /* the additional section can fail to be secure,
+ * it is optional, check signature in case we need
+ * to clean the additional section later. */
++ num_verifies += verified;
++ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
++ i+1 < chase_reply->rrset_count) {
++ *suspend = 1;
++ vq->msg_signatures_state = 1;
++ vq->msg_signatures_index = i;
++ verbose(VERB_ALGO, "msg signature validation "
++ "suspended");
++ return 0;
++ }
+ }
+
+ return 1;
+ }
+
++void
++validate_suspend_timer_cb(void* arg)
++{
++ struct module_qstate* qstate = (struct module_qstate*)arg;
++ verbose(VERB_ALGO, "validate_suspend timer, continue");
++ mesh_run(qstate->env->mesh, qstate->mesh_info, module_event_pass,
++ NULL);
++}
++
++/** Setup timer to continue validation of msg signatures later */
++static int
++validate_suspend_setup_timer(struct module_qstate* qstate,
++ struct val_qstate* vq, int id, enum val_state resume_state)
++{
++ struct timeval tv;
++ int usec, slack, base;
++ if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) {
++ verbose(VERB_ALGO, "validate_suspend timer: "
++ "reached MAX_VALIDATION_SUSPENDS (%d); error out",
++ MAX_VALIDATION_SUSPENDS);
++ errinf(qstate, "max validation suspends reached, "
++ "too many RRSIG validations");
++ return 0;
++ }
++ verbose(VERB_ALGO, "validate_suspend timer, set for suspend");
++ vq->state = resume_state;
++ qstate->ext_state[id] = module_wait_reply;
++ if(!vq->suspend_timer) {
++ vq->suspend_timer = comm_timer_create(
++ qstate->env->worker_base,
++ validate_suspend_timer_cb, qstate);
++ if(!vq->suspend_timer) {
++ log_err("validate_suspend_setup_timer: "
++ "out of memory for comm_timer_create");
++ return 0;
++ }
++ }
++ /* The timer is activated later, after other events in the event
++ * loop have been processed. The query state can also be deleted,
++ * when the list is full and query states are dropped. */
++ /* Extend wait time if there are a lot of queries or if this one
++ * is taking long, to keep around cpu time for ordinary queries. */
++ usec = 50000; /* 50 msec */
++ slack = 0;
++ if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states)
++ slack += 3;
++ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/2)
++ slack += 2;
++ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/4)
++ slack += 1;
++ if(vq->suspend_count > 3)
++ slack += 3;
++ else if(vq->suspend_count > 0)
++ slack += vq->suspend_count;
++ if(slack != 0 && slack <= 12 /* No numeric overflow. */) {
++ usec = usec << slack;
++ }
++ /* Spread such timeouts within 90%-100% of the original timer. */
++ base = usec * 9/10;
++ usec = base + ub_random_max(qstate->env->rnd, usec-base);
++ tv.tv_usec = (usec % 1000000);
++ tv.tv_sec = (usec / 1000000);
++ vq->suspend_count ++;
++ comm_timer_set(vq->suspend_timer, &tv);
++ return 1;
++}
++
+ /**
+ * Detect wrong truncated response (say from BIND 9.6.1 that is forwarding
+ * and saw the NS record without signatures from a referral).
+@@ -798,11 +953,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_positive_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ uint8_t* wc = NULL;
+ size_t wl;
+@@ -811,6 +972,7 @@
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
++ *suspend = 0;
+
+ /* validate the ANSWER section - this will be the answer itself */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
+@@ -862,17 +1024,23 @@
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+- enum sec_status sec = nsec3_prove_wildcard(env, ve,
++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
++ enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey, wc);
++ chase_reply->ns_numrrsets, qchase, kkey, wc,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "Positive wildcard response is "
+ "insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ wc_NSEC_ok = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ }
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+@@ -904,11 +1072,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_nodata_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ /* Since we are here, there must be nothing in the ANSWER section to
+ * validate. */
+@@ -925,6 +1099,7 @@
+ int nsec3s_seen = 0; /* nsec3s seen */
+ struct ub_packed_rrset_key* s;
+ size_t i;
++ *suspend = 0;
+
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
+@@ -963,16 +1138,23 @@
+ }
+ }
+
+- if(!has_valid_nsec && nsec3s_seen) {
++ if(!has_valid_nsec && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ enum sec_status sec = nsec3_prove_nodata(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey);
++ chase_reply->ns_numrrsets, qchase, kkey,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "NODATA response is insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ has_valid_nsec = 1;
++ } else if(sec == sec_status_unchecked) {
++ /* check is incomplete; suspend */
++ *suspend = 1;
++ return;
++ }
+ }
+
+ if(!has_valid_nsec) {
+@@ -1004,11 +1186,18 @@
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
+ * @param rcode: adjusted RCODE, in case of RCODE/proof mismatch leniency.
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_nameerror_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey, int* rcode)
++ struct key_entry_key* kkey, int* rcode,
++ struct module_qstate* qstate, struct val_qstate* vq,
++ int* nsec3_calculations, int* suspend)
+ {
+ int has_valid_nsec = 0;
+ int has_valid_wnsec = 0;
+@@ -1018,6 +1207,7 @@
+ uint8_t* ce;
+ int ce_labs = 0;
+ int prev_ce_labs = 0;
++ *suspend = 0;
+
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
+@@ -1047,13 +1237,18 @@
+ nsec3s_seen = 1;
+ }
+
+- if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen) {
++ if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ /* use NSEC3 proof, both answer and auth rrsets, in case
+ * NSEC3s end up in the answer (due to qtype=NSEC3 or so) */
+ chase_reply->security = nsec3_prove_nameerror(env, ve,
+ chase_reply->rrsets, chase_reply->an_numrrsets+
+- chase_reply->ns_numrrsets, qchase, kkey);
+- if(chase_reply->security != sec_status_secure) {
++ chase_reply->ns_numrrsets, qchase, kkey,
++ &vq->nsec3_cache_table, nsec3_calculations);
++ if(chase_reply->security == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ } else if(chase_reply->security != sec_status_secure) {
+ verbose(VERB_QUERY, "NameError response failed nsec, "
+ "nsec3 proof was %s", sec_status_to_string(
+ chase_reply->security));
+@@ -1065,26 +1260,34 @@
+
+ /* If the message fails to prove either condition, it is bogus. */
+ if(!has_valid_nsec) {
++ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
++ qstate, vq, nsec3_calculations, suspend);
++ if(*suspend) return;
+ verbose(VERB_QUERY, "NameError response has failed to prove: "
+ "qname does not exist");
+- chase_reply->security = sec_status_bogus;
+- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ /* Be lenient with RCODE in NSEC NameError responses */
+- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
+- if (chase_reply->security == sec_status_secure)
++ if(chase_reply->security == sec_status_secure) {
+ *rcode = LDNS_RCODE_NOERROR;
++ } else {
++ chase_reply->security = sec_status_bogus;
++ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ }
+ return;
+ }
+
+ if(!has_valid_wnsec) {
++ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
++ qstate, vq, nsec3_calculations, suspend);
++ if(*suspend) return;
+ verbose(VERB_QUERY, "NameError response has failed to prove: "
+ "covering wildcard does not exist");
+- chase_reply->security = sec_status_bogus;
+- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ /* Be lenient with RCODE in NSEC NameError responses */
+- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
+- if (chase_reply->security == sec_status_secure)
++ if (chase_reply->security == sec_status_secure) {
+ *rcode = LDNS_RCODE_NOERROR;
++ } else {
++ chase_reply->security = sec_status_bogus;
++ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ }
+ return;
+ }
+
+@@ -1144,11 +1347,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_any_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ /* all answer and auth rrsets already verified */
+ /* but check if a wildcard response is given, then check NSEC/NSEC3
+@@ -1159,6 +1368,7 @@
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
++ *suspend = 0;
+
+ if(qchase->qtype != LDNS_RR_TYPE_ANY) {
+ log_err("internal error: ANY validation called for non-ANY");
+@@ -1213,19 +1423,25 @@
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ /* look both in answer and auth section for NSEC3s */
+- enum sec_status sec = nsec3_prove_wildcard(env, ve,
++ enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ chase_reply->rrsets,
+- chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
+- qchase, kkey, wc);
++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
++ qchase, kkey, wc, &vq->nsec3_cache_table,
++ nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "Positive ANY wildcard response is "
+ "insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ wc_NSEC_ok = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ }
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+@@ -1258,11 +1474,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_cname_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ uint8_t* wc = NULL;
+ size_t wl;
+@@ -1270,6 +1492,7 @@
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
++ *suspend = 0;
+
+ /* validate the ANSWER section - this will be the CNAME (+DNAME) */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
+@@ -1334,17 +1557,23 @@
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+- enum sec_status sec = nsec3_prove_wildcard(env, ve,
++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
++ enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey, wc);
++ chase_reply->ns_numrrsets, qchase, kkey, wc,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "wildcard CNAME response is "
+ "insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ wc_NSEC_ok = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ }
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+@@ -1375,11 +1604,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/
+ uint8_t* ce = NULL; /* for wildcard nodata responses. This is the
+@@ -1393,6 +1628,7 @@
+ uint8_t* nsec_ce; /* Used to find the NSEC with the longest ce */
+ int ce_labs = 0;
+ int prev_ce_labs = 0;
++ *suspend = 0;
+
+ /* the AUTHORITY section */
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+@@ -1458,11 +1694,13 @@
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ return;
+ }
+- if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) {
++ if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ int nodata;
+ enum sec_status sec = nsec3_prove_nxornodata(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey, &nodata);
++ chase_reply->ns_numrrsets, qchase, kkey, &nodata,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "CNAMEchain to noanswer response "
+ "is insecure");
+@@ -1472,6 +1710,9 @@
+ if(nodata)
+ nodata_valid_nsec = 1;
+ else nxdomain_valid_nsec = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
+ }
+ }
+
+@@ -1672,20 +1913,13 @@
+ vq->state = VAL_FINISHED_STATE;
+ return 1;
+ } else if(key_entry_isbad(vq->key_entry)) {
+- sldns_ede_code ede = LDNS_EDE_DNSSEC_BOGUS;
+-
+- /* the key could have a more spefic EDE than just bogus */
+- if(key_entry_get_reason_bogus(vq->key_entry) != LDNS_EDE_NONE) {
+- ede = key_entry_get_reason_bogus(vq->key_entry);
+- }
+-
++ /* Bad keys should have the relevant EDE code and text */
++ sldns_ede_code ede = key_entry_get_reason_bogus(vq->key_entry);
+ /* key is bad, chain is bad, reply is bogus */
+ errinf_dname(qstate, "key for validation", vq->key_entry->name);
+ errinf_ede(qstate, "is marked as invalid", ede);
+- if(key_entry_get_reason(vq->key_entry)) {
+- errinf(qstate, "because of a previous");
+- errinf(qstate, key_entry_get_reason(vq->key_entry));
+- }
++ errinf(qstate, "because of a previous");
++ errinf(qstate, key_entry_get_reason(vq->key_entry));
+
+ /* no retries, stop bothering the authority until timeout */
+ vq->restart_count = ve->max_restart;
+@@ -1822,13 +2056,37 @@
+ * Uses negative cache for NSEC3 lookup of DS responses. */
+ /* only if cache not blacklisted, of course */
+ struct dns_msg* msg;
+- if(!qstate->blacklist && !vq->chain_blacklist &&
++ int suspend;
++ if(vq->sub_ds_msg) {
++ /* We have a suspended DS reply from a sub-query;
++ * process it. */
++ verbose(VERB_ALGO, "Process suspended sub DS response");
++ msg = vq->sub_ds_msg;
++ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
++ msg, &msg->qinfo, NULL, &suspend);
++ if(suspend) {
++ /* we'll come back here later to continue */
++ if(!validate_suspend_setup_timer(qstate, vq,
++ id, VAL_FINDKEY_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
++ vq->sub_ds_msg = NULL;
++ return 1; /* continue processing ds-response results */
++ } else if(!qstate->blacklist && !vq->chain_blacklist &&
+ (msg=val_find_DS(qstate->env, target_key_name,
+ target_key_len, vq->qchase.qclass, qstate->region,
+ vq->key_entry->name)) ) {
+ verbose(VERB_ALGO, "Process cached DS response");
+ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
+- msg, &msg->qinfo, NULL);
++ msg, &msg->qinfo, NULL, &suspend);
++ if(suspend) {
++ /* we'll come back here later to continue */
++ if(!validate_suspend_setup_timer(qstate, vq,
++ id, VAL_FINDKEY_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ return 1; /* continue processing ds-response results */
+ }
+ if(!generate_request(qstate, id, target_key_name,
+@@ -1871,7 +2129,7 @@
+ struct val_env* ve, int id)
+ {
+ enum val_classification subtype;
+- int rcode;
++ int rcode, suspend, nsec3_calculations = 0;
+
+ if(!vq->key_entry) {
+ verbose(VERB_ALGO, "validate: no key entry, failed");
+@@ -1888,7 +2146,8 @@
+ vq->chase_reply->security = sec_status_insecure;
+ val_mark_insecure(vq->chase_reply, vq->key_entry->name,
+ qstate->env->rrset_cache, qstate->env);
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+ return 1;
+ }
+
+@@ -1897,12 +2156,13 @@
+ "of trust to keys for", vq->key_entry->name,
+ LDNS_RR_TYPE_DNSKEY, vq->key_entry->key_class);
+ vq->chase_reply->security = sec_status_bogus;
+-
+- update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSKEY_MISSING);
++ update_reason_bogus(vq->chase_reply,
++ key_entry_get_reason_bogus(vq->key_entry));
+ errinf_ede(qstate, "while building chain of trust",
+- LDNS_EDE_DNSKEY_MISSING);
++ key_entry_get_reason_bogus(vq->key_entry));
+ if(vq->restart_count >= ve->max_restart)
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+ return 1;
+ }
+
+@@ -1926,8 +2186,14 @@
+
+ /* check signatures in the message;
+ * answer and authority must be valid, additional is only checked. */
+- if(!validate_msg_signatures(qstate, qstate->env, ve, &vq->qchase,
+- vq->chase_reply, vq->key_entry)) {
++ if(!validate_msg_signatures(qstate, vq, qstate->env, ve, &vq->qchase,
++ vq->chase_reply, vq->key_entry, &suspend)) {
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate, vq,
++ id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ /* workaround bad recursor out there that truncates (even
+ * with EDNS4k) to 512 by removing RRSIG from auth section
+ * for positive replies*/
+@@ -1956,7 +2222,14 @@
+ case VAL_CLASS_POSITIVE:
+ verbose(VERB_ALGO, "Validating a positive response");
+ validate_positive_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(positive): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1965,7 +2238,14 @@
+ case VAL_CLASS_NODATA:
+ verbose(VERB_ALGO, "Validating a nodata response");
+ validate_nodata_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(nodata): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1975,7 +2255,14 @@
+ rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags);
+ verbose(VERB_ALGO, "Validating a nxdomain response");
+ validate_nameerror_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry, &rcode);
++ &vq->qchase, vq->chase_reply, vq->key_entry, &rcode,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(nxdomain): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1986,7 +2273,14 @@
+ case VAL_CLASS_CNAME:
+ verbose(VERB_ALGO, "Validating a cname response");
+ validate_cname_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(cname): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1996,7 +2290,14 @@
+ verbose(VERB_ALGO, "Validating a cname noanswer "
+ "response");
+ validate_cname_noanswer_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(cname_noanswer): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -2013,8 +2314,15 @@
+ case VAL_CLASS_ANY:
+ verbose(VERB_ALGO, "Validating a positive ANY "
+ "response");
+- validate_any_response(qstate->env, ve, &vq->qchase,
+- vq->chase_reply, vq->key_entry);
++ validate_any_response(qstate->env, ve, &vq->qchase,
++ vq->chase_reply, vq->key_entry, qstate, vq,
++ &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(positive_any): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -2123,16 +2431,13 @@
+ if(vq->orig_msg->rep->security == sec_status_bogus) {
+ /* see if we can try again to fetch data */
+ if(vq->restart_count < ve->max_restart) {
+- int restart_count = vq->restart_count+1;
+ verbose(VERB_ALGO, "validation failed, "
+ "blacklist and retry to fetch data");
+ val_blacklist(&qstate->blacklist, qstate->region,
+ qstate->reply_origin, 0);
+ qstate->reply_origin = NULL;
+ qstate->errinf = NULL;
+- memset(vq, 0, sizeof(*vq));
+- vq->restart_count = restart_count;
+- vq->state = VAL_INIT_STATE;
++ val_restart(vq);
+ verbose(VERB_ALGO, "pass back to next module");
+ qstate->ext_state[id] = module_restart_next;
+ return 0;
+@@ -2151,9 +2456,19 @@
+ log_query_info(NO_VERBOSE, "validation failure",
+ &qstate->qinfo);
+ else {
+- char* err = errinf_to_str_bogus(qstate);
+- if(err) log_info("%s", err);
+- free(err);
++ char* err_str = errinf_to_str_bogus(qstate);
++ if(err_str) {
++ size_t err_str_len = strlen(err_str);
++ log_info("%s", err_str);
++ /* allocate space and store the error
++ * string */
++ vq->orig_msg->rep->reason_bogus_str = regional_alloc(
++ qstate->region,
++ sizeof(char) * (err_str_len+1));
++ memcpy(vq->orig_msg->rep->reason_bogus_str,
++ err_str, err_str_len+1);
++ }
++ free(err_str);
+ }
+ }
+ /*
+@@ -2195,6 +2510,9 @@
+ }
+ }
+ }
++
++ /* Update rep->reason_bogus as it is the one being cached */
++ update_reason_bogus(vq->orig_msg->rep, errinf_to_reason_bogus(qstate));
+ /* store results in cache */
+ if(qstate->query_flags&BIT_RD) {
+ /* if secure, this will override cache anyway, no need
+@@ -2370,13 +2688,17 @@
+ log_nametypeclass(VERB_OPS, "failed to prime trust anchor -- "
+ "could not fetch DNSKEY rrset",
+ ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass);
++ reason_bogus = LDNS_EDE_DNSKEY_MISSING;
++ reason = "no DNSKEY rrset";
+ if(qstate->env->cfg->harden_dnssec_stripped) {
+- errinf_ede(qstate, "no DNSKEY rrset", LDNS_EDE_DNSKEY_MISSING);
++ errinf_ede(qstate, reason, reason_bogus);
+ kkey = key_entry_create_bad(qstate->region, ta->name,
+ ta->namelen, ta->dclass, BOGUS_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ } else kkey = key_entry_create_null(qstate->region, ta->name,
+ ta->namelen, ta->dclass, NULL_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ if(!kkey) {
+ log_err("out of memory: allocate fail prime key");
+@@ -2409,9 +2731,11 @@
+ errinf_ede(qstate, reason, reason_bogus);
+ kkey = key_entry_create_bad(qstate->region, ta->name,
+ ta->namelen, ta->dclass, BOGUS_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ } else kkey = key_entry_create_null(qstate->region, ta->name,
+ ta->namelen, ta->dclass, NULL_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ if(!kkey) {
+ log_err("out of memory: allocate null prime key");
+@@ -2440,7 +2764,10 @@
+ * DS response indicated an end to secure space, is_good if the DS
+ * validated. It returns ke=NULL if the DS response indicated that the
+ * request wasn't a delegation point.
+- * @return 0 on servfail error (malloc failure).
++ * @return
++ * 0 on success,
++ * 1 on servfail error (malloc failure),
++ * 2 on NSEC3 suspend.
+ */
+ static int
+ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
+@@ -2451,6 +2778,7 @@
+ char* reason = NULL;
+ sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ enum val_classification subtype;
++ int verified;
+ if(rcode != LDNS_RCODE_NOERROR) {
+ char rc[16];
+ rc[0]=0;
+@@ -2458,8 +2786,9 @@
+ /* errors here pretty much break validation */
+ verbose(VERB_DETAIL, "DS response was error, thus bogus");
+ errinf(qstate, rc);
+- errinf_ede(qstate, "no DS", LDNS_EDE_NETWORK_ERROR);
+-
++ reason = "no DS";
++ reason_bogus = LDNS_EDE_NETWORK_ERROR;
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+
+@@ -2473,13 +2802,14 @@
+ if(!ds) {
+ log_warn("internal error: POSITIVE DS response was "
+ "missing DS.");
+- errinf_ede(qstate, "no DS record", LDNS_EDE_DNSSEC_BOGUS);
++ reason = "no DS record";
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+ /* Verify only returns BOGUS or SECURE. If the rrset is
+ * bogus, then we are done. */
+ sec = val_verify_rrset_entry(qstate->env, ve, ds,
+- vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate);
++ vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified);
+ if(sec != sec_status_secure) {
+ verbose(VERB_DETAIL, "DS rrset in DS response did "
+ "not verify");
+@@ -2492,22 +2822,20 @@
+ if(!val_dsset_isusable(ds)) {
+ /* If they aren't usable, then we treat it like
+ * there was no DS. */
+-
+- /* TODO add EDE Unsupported DS Digest Type; this needs
+- * EDE to be added on non SERVFAIL answers. */
+-
+- *ke = key_entry_create_null(qstate->region,
+- qinfo->qname, qinfo->qname_len, qinfo->qclass,
+- ub_packed_rrset_ttl(ds), *qstate->env->now);
+- return (*ke) != NULL;
++ *ke = key_entry_create_null(qstate->region,
++ qinfo->qname, qinfo->qname_len, qinfo->qclass,
++ ub_packed_rrset_ttl(ds),
++ LDNS_EDE_UNSUPPORTED_DS_DIGEST, NULL,
++ *qstate->env->now);
++ return (*ke) == NULL;
+ }
+
+ /* Otherwise, we return the positive response. */
+ log_query_info(VERB_DETAIL, "validated DS", qinfo);
+ *ke = key_entry_create_rrset(qstate->region,
+ qinfo->qname, qinfo->qname_len, qinfo->qclass, ds,
+- NULL, *qstate->env->now);
+- return (*ke) != NULL;
++ NULL, LDNS_EDE_NONE, NULL, *qstate->env->now);
++ return (*ke) == NULL;
+ } else if(subtype == VAL_CLASS_NODATA ||
+ subtype == VAL_CLASS_NAMEERROR) {
+ /* NODATA means that the qname exists, but that there was
+@@ -2518,7 +2846,8 @@
+ /* make sure there are NSECs or NSEC3s with signatures */
+ if(!val_has_signed_nsecs(msg->rep, &reason)) {
+ verbose(VERB_ALGO, "no NSECs: %s", reason);
+- errinf_ede(qstate, reason, LDNS_EDE_NSEC_MISSING);
++ reason_bogus = LDNS_EDE_NSEC_MISSING;
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+
+@@ -2530,7 +2859,7 @@
+ /* Try to prove absence of the DS with NSEC */
+ sec = val_nsec_prove_nodata_dsreply(
+ qstate->env, ve, qinfo, msg->rep, vq->key_entry,
+- &proof_ttl, &reason, qstate);
++ &proof_ttl, &reason, &reason_bogus, qstate);
+ switch(sec) {
+ case sec_status_secure:
+ verbose(VERB_DETAIL, "NSEC RRset for the "
+@@ -2538,13 +2867,14 @@
+ *ke = key_entry_create_null(qstate->region,
+ qinfo->qname, qinfo->qname_len,
+ qinfo->qclass, proof_ttl,
++ LDNS_EDE_NONE, NULL,
+ *qstate->env->now);
+- return (*ke) != NULL;
++ return (*ke) == NULL;
+ case sec_status_insecure:
+ verbose(VERB_DETAIL, "NSEC RRset for the "
+ "referral proved not a delegation point");
+ *ke = NULL;
+- return 1;
++ return 0;
+ case sec_status_bogus:
+ verbose(VERB_DETAIL, "NSEC RRset for the "
+ "referral did not prove no DS.");
+@@ -2556,10 +2886,17 @@
+ break;
+ }
+
++ if(!nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
++ log_err("malloc failure in ds_response_to_ke for "
++ "NSEC3 cache");
++ reason = "malloc failure";
++ errinf_ede(qstate, reason, 0);
++ goto return_bogus;
++ }
+ sec = nsec3_prove_nods(qstate->env, ve,
+ msg->rep->rrsets + msg->rep->an_numrrsets,
+ msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason,
+- &reason_bogus, qstate);
++ &reason_bogus, qstate, &vq->nsec3_cache_table);
+ switch(sec) {
+ case sec_status_insecure:
+ /* case insecure also continues to unsigned
+@@ -2571,19 +2908,21 @@
+ *ke = key_entry_create_null(qstate->region,
+ qinfo->qname, qinfo->qname_len,
+ qinfo->qclass, proof_ttl,
++ LDNS_EDE_NONE, NULL,
+ *qstate->env->now);
+- return (*ke) != NULL;
++ return (*ke) == NULL;
+ case sec_status_indeterminate:
+ verbose(VERB_DETAIL, "NSEC3s for the "
+ "referral proved no delegation");
+ *ke = NULL;
+- return 1;
++ return 0;
+ case sec_status_bogus:
+ verbose(VERB_DETAIL, "NSEC3s for the "
+ "referral did not prove no DS.");
+ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ case sec_status_unchecked:
++ return 2;
+ default:
+ /* NSEC3 proof did not work */
+ break;
+@@ -2593,7 +2932,8 @@
+ * this is BOGUS. */
+ verbose(VERB_DETAIL, "DS %s ran out of options, so return "
+ "bogus", val_classification_to_string(subtype));
+- errinf(qstate, "no DS but also no proof of that");
++ reason = "no DS but also no proof of that";
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ } else if(subtype == VAL_CLASS_CNAME ||
+ subtype == VAL_CLASS_CNAMENOANSWER) {
+@@ -2605,36 +2945,40 @@
+ cname = reply_find_rrset_section_an(msg->rep, qinfo->qname,
+ qinfo->qname_len, LDNS_RR_TYPE_CNAME, qinfo->qclass);
+ if(!cname) {
+- errinf(qstate, "validator classified CNAME but no "
+- "CNAME of the queried name for DS");
++ reason = "validator classified CNAME but no "
++ "CNAME of the queried name for DS";
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+ if(((struct packed_rrset_data*)cname->entry.data)->rrsig_count
+ == 0) {
+ if(msg->rep->an_numrrsets != 0 && ntohs(msg->rep->
+ rrsets[0]->rk.type)==LDNS_RR_TYPE_DNAME) {
+- errinf(qstate, "DS got DNAME answer");
++ reason = "DS got DNAME answer";
+ } else {
+- errinf(qstate, "DS got unsigned CNAME answer");
++ reason = "DS got unsigned CNAME answer";
+ }
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+- sec = val_verify_rrset_entry(qstate->env, ve, cname,
+- vq->key_entry, &reason, NULL, LDNS_SECTION_ANSWER, qstate);
++ sec = val_verify_rrset_entry(qstate->env, ve, cname,
++ vq->key_entry, &reason, &reason_bogus,
++ LDNS_SECTION_ANSWER, qstate, &verified);
+ if(sec == sec_status_secure) {
+ verbose(VERB_ALGO, "CNAME validated, "
+ "proof that DS does not exist");
+ /* and that it is not a referral point */
+ *ke = NULL;
+- return 1;
++ return 0;
+ }
+ errinf(qstate, "CNAME in DS response was not secure.");
+- errinf(qstate, reason);
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ } else {
+ verbose(VERB_QUERY, "Encountered an unhandled type of "
+ "DS response, thus bogus.");
+ errinf(qstate, "no DS and");
++ reason = "no DS";
+ if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR) {
+ char rc[16];
+ rc[0]=0;
+@@ -2647,9 +2991,9 @@
+ }
+ return_bogus:
+ *ke = key_entry_create_bad(qstate->region, qinfo->qname,
+- qinfo->qname_len, qinfo->qclass,
+- BOGUS_KEY_TTL, *qstate->env->now);
+- return (*ke) != NULL;
++ qinfo->qname_len, qinfo->qclass, BOGUS_KEY_TTL,
++ reason_bogus, reason, *qstate->env->now);
++ return (*ke) == NULL;
+ }
+
+ /**
+@@ -2670,17 +3014,31 @@
+ static void
+ process_ds_response(struct module_qstate* qstate, struct val_qstate* vq,
+ int id, int rcode, struct dns_msg* msg, struct query_info* qinfo,
+- struct sock_list* origin)
++ struct sock_list* origin, int* suspend)
+ {
+ struct val_env* ve = (struct val_env*)qstate->env->modinfo[id];
+ struct key_entry_key* dske = NULL;
+ uint8_t* olds = vq->empty_DS_name;
++ int ret;
++ *suspend = 0;
+ vq->empty_DS_name = NULL;
+- if(!ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske)) {
++ ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske);
++ if(ret != 0) {
++ switch(ret) {
++ case 1:
+ log_err("malloc failure in process_ds_response");
+ vq->key_entry = NULL; /* make it error */
+ vq->state = VAL_VALIDATE_STATE;
+ return;
++ case 2:
++ *suspend = 1;
++ return;
++ default:
++ log_err("unhandled error value for ds_response_to_ke");
++ vq->key_entry = NULL; /* make it error */
++ vq->state = VAL_VALIDATE_STATE;
++ return;
++ }
+ }
+ if(dske == NULL) {
+ vq->empty_DS_name = regional_alloc_init(qstate->region,
+@@ -2768,14 +3126,17 @@
+ vq->restart_count++;
+ return;
+ }
+- vq->key_entry = key_entry_create_bad(qstate->region,
++ reason = "No DNSKEY record";
++ reason_bogus = LDNS_EDE_DNSKEY_MISSING;
++ vq->key_entry = key_entry_create_bad(qstate->region,
+ qinfo->qname, qinfo->qname_len, qinfo->qclass,
+- BOGUS_KEY_TTL, *qstate->env->now);
++ BOGUS_KEY_TTL, reason_bogus, reason,
++ *qstate->env->now);
+ if(!vq->key_entry) {
+ log_err("alloc failure in missing dnskey response");
+ /* key_entry is NULL for failure in Validate */
+ }
+- errinf_ede(qstate, "No DNSKEY record", LDNS_EDE_DNSKEY_MISSING);
++ errinf_ede(qstate, reason, reason_bogus);
+ errinf_origin(qstate, origin);
+ errinf_dname(qstate, "for key", qinfo->qname);
+ vq->state = VAL_VALIDATE_STATE;
+@@ -2822,7 +3183,8 @@
+ qstate->errinf = NULL;
+
+ /* The DNSKEY validated, so cache it as a trusted key rrset. */
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+
+ /* If good, we stay in the FINDKEY state. */
+ log_query_info(VERB_DETAIL, "validated DNSKEY", qinfo);
+@@ -2890,7 +3252,8 @@
+ errinf_origin(qstate, origin);
+ errinf_dname(qstate, "for trust anchor", ta->name);
+ /* store the freshly primed entry in the cache */
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+ }
+
+ /* If the result of the prime is a null key, skip the FINDKEY state.*/
+@@ -2927,9 +3290,26 @@
+ return;
+ }
+ if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) {
++ int suspend;
+ process_ds_response(super, vq, id, qstate->return_rcode,
+- qstate->return_msg, &qstate->qinfo,
+- qstate->reply_origin);
++ qstate->return_msg, &qstate->qinfo,
++ qstate->reply_origin, &suspend);
++ /* If NSEC3 was needed during validation, NULL the NSEC3 cache;
++ * it will be re-initiated if needed later on.
++ * Validation (and the cache table) are happening/allocated in
++ * the super qstate whilst the RRs are allocated (and pointed
++ * to) in this sub qstate. */
++ if(vq->nsec3_cache_table.ct) {
++ vq->nsec3_cache_table.ct = NULL;
++ }
++ if(suspend) {
++ /* deep copy the return_msg to vq->sub_ds_msg; it will
++ * be resumed later in the super state with the caveat
++ * that the initial calculations will be re-caclulated
++ * and re-suspended there before continuing. */
++ vq->sub_ds_msg = dns_msg_deepcopy_region(
++ qstate->return_msg, super->region);
++ }
+ return;
+ } else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) {
+ process_dnskey_response(super, vq, id, qstate->return_rcode,
+@@ -2943,8 +3323,15 @@
+ void
+ val_clear(struct module_qstate* qstate, int id)
+ {
++ struct val_qstate* vq;
+ if(!qstate)
+ return;
++ vq = (struct val_qstate*)qstate->minfo[id];
++ if(vq) {
++ if(vq->suspend_timer) {
++ comm_timer_delete(vq->suspend_timer);
++ }
++ }
+ /* everything is allocated in the region, so assign NULL */
+ qstate->minfo[id] = NULL;
+ }
+--- contrib/unbound/validator/validator.h.orig
++++ contrib/unbound/validator/validator.h
+@@ -45,11 +45,13 @@
+ #include "util/module.h"
+ #include "util/data/msgreply.h"
+ #include "validator/val_utils.h"
++#include "validator/val_nsec3.h"
+ struct val_anchors;
+ struct key_cache;
+ struct key_entry_key;
+ struct val_neg_cache;
+ struct config_strlist;
++struct comm_timer;
+
+ /**
+ * This is the TTL to use when a trust anchor fails to prime. A trust anchor
+@@ -215,6 +217,19 @@
+
+ /** true if this state is waiting to prime a trust anchor */
+ int wait_prime_ta;
++
++ /** State to continue with RRSIG validation in a message later */
++ int msg_signatures_state;
++ /** The rrset index for the msg signatures to continue from */
++ size_t msg_signatures_index;
++ /** Cache table for NSEC3 hashes */
++ struct nsec3_cache_table nsec3_cache_table;
++ /** DS message from sub if it got suspended from NSEC3 calculations */
++ struct dns_msg* sub_ds_msg;
++ /** The timer to resume processing msg signatures */
++ struct comm_timer* suspend_timer;
++ /** Number of suspends */
++ int suspend_count;
+ };
+
+ /**
+@@ -262,4 +277,7 @@
+ */
+ size_t val_get_mem(struct module_env* env, int id);
+
++/** Timer callback for msg signatures continue timer */
++void validate_suspend_timer_cb(void* arg);
++
+ #endif /* VALIDATOR_VALIDATOR_H */
+--- lib/libunbound/Makefile.orig
++++ lib/libunbound/Makefile
+@@ -25,8 +25,10 @@
+ msgreply.c net_help.c netevent.c outbound_list.c outside_network.c \
+ packed_rrset.c parse.c parseutil.c proxy_protocol.c \
+ random.c rbtree.c redis.c \
+- regional.c respip.c rpz.c rrdef.c rrset.c rtt.c sbuffer.c slabhash.c \
+- str2wire.c tcp_conn_limit.c timehist.c tube.c ub_event_pluggable.c \
++ regional.c respip.c rfc_1982.c rpz.c rrdef.c rrset.c rtt.c sbuffer.c \
++ siphash.c slabhash.c \
++ str2wire.c tcp_conn_limit.c timehist.c timeval_func.c \
++ tube.c ub_event_pluggable.c \
+ val_anchor.c val_kcache.c val_kentry.c val_neg.c val_nsec.c \
+ val_nsec3.c val_secalgo.c val_sigcrypt.c val_utils.c validator.c \
+ view.c winsock_event.c wire2str.c
+--- usr.sbin/unbound/config.h.orig
++++ usr.sbin/unbound/config.h
+@@ -794,7 +794,7 @@
+ #define PACKAGE_NAME "unbound"
+
+ /* Define to the full name and version of this package. */
+-#define PACKAGE_STRING "unbound 1.17.1"
++#define PACKAGE_STRING "unbound 1.19.1"
+
+ /* Define to the one symbol short name of this package. */
+ #define PACKAGE_TARNAME "unbound"
+@@ -803,7 +803,7 @@
+ #define PACKAGE_URL ""
+
+ /* Define to the version of this package. */
+-#define PACKAGE_VERSION "1.17.1"
++#define PACKAGE_VERSION "1.19.1"
+
+ /* default pidfile location */
+ #define PIDFILE "/var/unbound/unbound.pid"
+@@ -826,7 +826,7 @@
+ #define ROOT_CERT_FILE "/var/unbound/icannbundle.pem"
+
+ /* version number for resource files */
+-#define RSRC_PACKAGE_VERSION 1,17,1,0
++#define RSRC_PACKAGE_VERSION 1,19,0,0
+
+ /* Directory to chdir to */
+ #define RUN_DIR "/var/unbound"
diff --git a/website/static/security/patches/SA-24:03/unbound-13.patch.asc b/website/static/security/patches/SA-24:03/unbound-13.patch.asc
new file mode 100644
index 0000000000..abccff2b72
--- /dev/null
+++ b/website/static/security/patches/SA-24:03/unbound-13.patch.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGa8ACgkQbljekB8A
+Gu8p+A/6Ao+QHwiywhjBw/zxiB+AY+Ho/RGADTObOoUOGF08AMb7uTcGIh6eOwbp
+iW3S9wRuA9+ZsPop5+tghQsWty6lvBPF59CmV0EUEry56eaP7V1Tmgz+5pPZzUcc
+desShoffNgFTYOnmkuQlklOiGzC59GszwdD5rDc+8Xs+95el6Ezg3itF6gTYcrMf
+ryq+dBta9zuYsJ5Ra/bA7pYXjY7lkZvz2uJSE8bk/rwgYizN6WoTYqkbHKBcBW+I
+LlE8IrUJqFL+rCB9mEN8ovBOUPziicz1dpJoMZQJUyLeDayIdRnkURmy+Nb6Hbk0
+P9vu1F6fjHy4p5YVwtKb+tbDaFRxspz1kyQAZO+I+Vv5aPjRoTfKEQL3fl/LTjjm
+LCVOoNSGQNy2EbNOyGMTljh85aJEzfuvN5ONgVM4Stl0bsa+wZU2+22NZg/H6aFP
+8vEzfhxNhHWDZgksbyN0osZNkf3KPptU7/vFg3E++3vBh14ec0mP37A+Sc33+JC0
+56s+XljT3A2AROpecVAkcNKLjhUwkhHjzNpwcA7ftTCecn1mzX9/fssvDhSb5/Y5
+U/Tvg+CdCuaTLs15x1qv2yaIEqICr2a7xRsHxTCGwv0xm9LJYtUm32ZBEzY/l9qz
+N9Pjx/hGo3eEkqjc5/99QQ8pF9oSGDxlM2nGmCJDP4Q4r9yhzuQ=
+=AQ87
+-----END PGP SIGNATURE-----
diff --git a/website/static/security/patches/SA-24:03/unbound-14.patch b/website/static/security/patches/SA-24:03/unbound-14.patch
new file mode 100644
index 0000000000..bc06296a4a
--- /dev/null
+++ b/website/static/security/patches/SA-24:03/unbound-14.patch
@@ -0,0 +1,24911 @@
+--- contrib/unbound/Makefile.in.orig
++++ contrib/unbound/Makefile.in
+@@ -122,15 +122,15 @@
+ iterator/iter_hints.c iterator/iter_priv.c iterator/iter_resptype.c \
+ iterator/iter_scrub.c iterator/iter_utils.c services/listen_dnsport.c \
+ services/localzone.c services/mesh.c services/modstack.c services/view.c \
+-services/rpz.c \
++services/rpz.c util/rfc_1982.c \
+ services/outbound_list.c services/outside_network.c util/alloc.c \
+ util/config_file.c util/configlexer.c util/configparser.c \
+ util/shm_side/shm_main.c services/authzone.c \
+ util/fptr_wlist.c util/locks.c util/log.c util/mini_event.c util/module.c \
+ util/netevent.c util/net_help.c util/random.c util/rbtree.c util/regional.c \
+-util/rtt.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
++util/rtt.c util/siphash.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
+ util/storage/lruhash.c util/storage/slabhash.c util/tcp_conn_limit.c \
+-util/timehist.c util/tube.c util/proxy_protocol.c \
++util/timehist.c util/tube.c util/proxy_protocol.c util/timeval_func.c \
+ util/ub_event.c util/ub_event_pluggable.c util/winsock_event.c \
+ validator/autotrust.c validator/val_anchor.c validator/validator.c \
+ validator/val_kcache.c validator/val_kentry.c validator/val_neg.c \
+@@ -145,14 +145,14 @@
+ iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \
+ iter_scrub.lo iter_utils.lo localzone.lo mesh.lo modstack.lo view.lo \
+ outbound_list.lo alloc.lo config_file.lo configlexer.lo configparser.lo \
+-fptr_wlist.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
++fptr_wlist.lo siphash.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
+ random.lo rbtree.lo regional.lo rtt.lo dnstree.lo lookup3.lo lruhash.lo \
+ slabhash.lo tcp_conn_limit.lo timehist.lo tube.lo winsock_event.lo \
+-autotrust.lo val_anchor.lo rpz.lo proxy_protocol.lo \
++autotrust.lo val_anchor.lo rpz.lo rfc_1982.lo proxy_protocol.lo \
+ validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \
+ val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo $(CACHEDB_OBJ) authzone.lo \
+ $(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \
+-$(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo
++$(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo timeval_func.lo
+ COMMON_OBJ_WITHOUT_UB_EVENT=$(COMMON_OBJ_WITHOUT_NETCALL) netevent.lo listen_dnsport.lo \
+ outside_network.lo
+ COMMON_OBJ=$(COMMON_OBJ_WITHOUT_UB_EVENT) ub_event.lo
+@@ -198,7 +198,7 @@
+ CHECKCONF_OBJ_LINK=$(CHECKCONF_OBJ) $(COMMON_OBJ_ALL_SYMBOLS) $(SLDNS_OBJ) \
+ $(COMPAT_OBJ) @WIN_CHECKCONF_OBJ_LINK@
+ CONTROL_SRC=smallapp/unbound-control.c
+-CONTROL_OBJ=unbound-control.lo
++CONTROL_OBJ=unbound-control.lo
+ CONTROL_OBJ_LINK=$(CONTROL_OBJ) worker_cb.lo $(COMMON_OBJ_ALL_SYMBOLS) \
+ $(SLDNS_OBJ) $(COMPAT_OBJ) @WIN_CONTROL_OBJ_LINK@
+ HOST_SRC=smallapp/unbound-host.c
+@@ -455,6 +455,7 @@
+ dynlibmod.lo dynlibdmod.o: $(srcdir)/dynlibmod/dynlibmod.c config.h $(srcdir)/dynlibmod/dynlibmod.h
+ cachedb.lo cachedb.o: $(srcdir)/cachedb/cachedb.c config.h $(srcdir)/cachedb/cachedb.h
+ redis.lo redis.o: $(srcdir)/cachedb/redis.c config.h $(srcdir)/cachedb/redis.h
++timeval_func.lo timeval_func.o: $(srcdir)/util/timeval_func.c $(srcdir)/util/timeval_func.h
+
+ # dnscrypt
+ dnscrypt.lo dnscrypt.o: $(srcdir)/dnscrypt/dnscrypt.c config.h \
+@@ -498,6 +499,7 @@
+ echo "#include \"util/configyyrename.h\"" >> $@ ;\
+ $(LEX) -t $(srcdir)/util/configlexer.lex >> $@ ;\
+ fi
++ @if test ! -f $@; then echo "No $@ : need flex and bison to compile from source repository"; exit 1; fi
+
+ util/configparser.c util/configparser.h: $(srcdir)/util/configparser.y
+ @-if test ! -d util; then $(INSTALL) -d util; fi
+@@ -516,7 +518,7 @@
+ rm -f doc/example.conf doc/libunbound.3 doc/unbound-anchor.8 doc/unbound-checkconf.8 doc/unbound-control.8 doc/unbound.8 doc/unbound.conf.5 doc/unbound-host.1
+ rm -f smallapp/unbound-control-setup.sh dnstap/dnstap_config.h dnscrypt/dnscrypt_config.h contrib/libunbound.pc contrib/unbound.socket contrib/unbound.service
+ rm -f $(TEST_BIN)
+- rm -f Makefile
++ rm -f Makefile
+
+ maintainer-clean: distclean
+ rm -f util/configlexer.c util/configparser.c util/configparser.h
+@@ -649,7 +651,7 @@
+
+ iana_update:
+ curl -o port-numbers.tmp https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml --compressed
+- if file port-numbers.tmp | grep 'gzip' >/dev/null; then zcat port-numbers.tmp; else cat port-numbers.tmp; fi | awk '/<record>/ {p=0;} /<protocol>udp/ {p=1;} /<protocol>[^u]/ {p=0;} /Decomissioned|Decommissioned|Removed|De-registered|unassigned|Unassigned|Reserved/ {u=1;} /<number>/ { if(u==1) {u=0;} else { if(p==1) { match($$0,/[0-9]+/); print substr($$0, RSTART, RLENGTH) ","}}}' | sort -nu > util/iana_ports.inc
++ if file port-numbers.tmp | grep 'gzip' >/dev/null; then zcat port-numbers.tmp; else cat port-numbers.tmp; fi | awk '/<record>/ {p=0;} /<protocol>udp/ {p=1;} /<protocol>[^u]/ {p=0;} /Decomissioned|Decommissioned|Removed|De-registered|unassigned|Unassigned|Reserved/ {u=1;} /<number>/ { if(u==1) {u=0;} else { if(p==1) { match($$0,/[0-9]+/); print substr($$0, RSTART, RLENGTH) ","}}}' | sort -nu > util/iana_ports.inc
+ rm -f port-numbers.tmp
+
+ # dependency generation
+@@ -736,7 +738,7 @@
+ msgparse.lo msgparse.o: $(srcdir)/util/data/msgparse.c config.h $(srcdir)/util/data/msgparse.h \
+ $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/sldns/pkthdr.h \
+ $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h \
+- $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lookup3.h $(srcdir)/util/regional.h $(srcdir)/sldns/sbuffer.h \
++ $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lookup3.h $(srcdir)/util/regional.h $(srcdir)/util/net_help.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/sldns/parseutil.h $(srcdir)/sldns/wire2str.h
+ msgreply.lo msgreply.o: $(srcdir)/util/data/msgreply.c config.h $(srcdir)/util/data/msgreply.h \
+ $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h \
+@@ -791,7 +793,7 @@
+ $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/storage/dnstree.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/sbuffer.h
+ iter_resptype.lo iter_resptype.o: $(srcdir)/iterator/iter_resptype.c config.h \
+- $(srcdir)/iterator/iter_resptype.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/util/log.h \
++ $(srcdir)/iterator/iter_resptype.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/iterator/iterator.h $(srcdir)/util/log.h \
+ $(srcdir)/services/cache/dns.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
+ $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/data/dname.h $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/pkthdr.h
+@@ -877,7 +879,7 @@
+ outbound_list.lo outbound_list.o: $(srcdir)/services/outbound_list.c config.h \
+ $(srcdir)/services/outbound_list.h $(srcdir)/services/outside_network.h $(srcdir)/util/rbtree.h \
+ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+-
++
+ outside_network.lo outside_network.o: $(srcdir)/services/outside_network.c config.h \
+ $(srcdir)/services/outside_network.h $(srcdir)/util/rbtree.h $(srcdir)/util/netevent.h \
+ $(srcdir)/dnscrypt/dnscrypt.h \
+@@ -915,7 +917,8 @@
+ configlexer.lo configlexer.o: util/configlexer.c config.h $(srcdir)/util/configyyrename.h \
+ $(srcdir)/util/config_file.h util/configparser.h
+ configparser.lo configparser.o: util/configparser.c config.h $(srcdir)/util/configyyrename.h \
+- $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h
++ $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/sldns/str2wire.h \
++ $(srcdir)/sldns/rrdef.h
+ shm_main.lo shm_main.o: $(srcdir)/util/shm_side/shm_main.c config.h $(srcdir)/util/shm_side/shm_main.h \
+ $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/daemon.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
+ $(srcdir)/util/alloc.h $(srcdir)/services/modstack.h \
+@@ -928,7 +931,7 @@
+ $(srcdir)/services/view.h $(srcdir)/util/config_file.h $(srcdir)/services/authzone.h $(srcdir)/respip/respip.h \
+ $(srcdir)/services/cache/rrset.h $(srcdir)/util/storage/slabhash.h $(srcdir)/services/cache/infra.h \
+ $(srcdir)/util/rtt.h $(srcdir)/validator/validator.h $(srcdir)/validator/val_utils.h $(srcdir)/util/fptr_wlist.h \
+- $(srcdir)/util/tube.h
++ $(srcdir)/util/tube.h $(srcdir)/util/timeval_func.h
+ authzone.lo authzone.o: $(srcdir)/services/authzone.c config.h $(srcdir)/services/authzone.h \
+ $(srcdir)/util/rbtree.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/services/mesh.h $(srcdir)/util/netevent.h \
+ $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/data/msgparse.h \
+@@ -983,7 +986,7 @@
+ $(srcdir)/services/modstack.h $(srcdir)/services/rpz.h $(srcdir)/services/localzone.h $(srcdir)/services/view.h \
+ $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h $(srcdir)/services/authzone.h $(srcdir)/daemon/stats.h \
+ $(srcdir)/util/timehist.h $(srcdir)/libunbound/unbound.h $(srcdir)/respip/respip.h $(srcdir)/sldns/str2wire.h \
+- $(srcdir)/dnstap/dnstap.h $(srcdir)/services/listen_dnsport.h
++ $(srcdir)/dnstap/dnstap.h $(srcdir)/services/listen_dnsport.h $(srcdir)/util/timeval_func.h
+ proxy_protocol.lo proxy_protocol.o: $(srcdir)/util/proxy_protocol.c config.h \
+ $(srcdir)/util/proxy_protocol.h $(srcdir)/sldns/sbuffer.h
+ net_help.lo net_help.o: $(srcdir)/util/net_help.c config.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h \
+@@ -1006,6 +1009,8 @@
+ $(srcdir)/services/outbound_list.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/storage/lruhash.h \
+ $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/module.h \
+ $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h
++siphash.lo siphash.o: $(srcdir)/util/siphash.c
++rfc_1982.lo rfc_1982.o: $(srcdir)/util/rfc_1982.c
+ edns.lo edns.o: $(srcdir)/util/edns.c config.h $(srcdir)/util/edns.h $(srcdir)/util/storage/dnstree.h \
+ $(srcdir)/util/rbtree.h $(srcdir)/util/config_file.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+ $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/util/regional.h \
+@@ -1186,7 +1191,7 @@
+ $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+ $(srcdir)/util/random.h $(srcdir)/respip/respip.h \
+ $(srcdir)/services/localzone.h $(srcdir)/services/view.h $(srcdir)/sldns/sbuffer.h \
+- $(srcdir)/services/outside_network.h
++ $(srcdir)/services/outside_network.h
+ unitmsgparse.lo unitmsgparse.o: $(srcdir)/testcode/unitmsgparse.c config.h $(srcdir)/util/log.h \
+ $(srcdir)/testcode/unitmain.h $(srcdir)/util/data/msgparse.h $(srcdir)/util/storage/lruhash.h \
+ $(srcdir)/util/locks.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h \
+@@ -1321,7 +1326,7 @@
+ worker.lo worker.o: $(srcdir)/daemon/worker.c config.h $(srcdir)/util/log.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/random.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
+- $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
++ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/util/alloc.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \
+ $(srcdir)/sldns/rrdef.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h $(srcdir)/libunbound/unbound.h \
+ $(srcdir)/util/module.h $(srcdir)/dnstap/dnstap.h $(srcdir)/daemon/daemon.h \
+@@ -1343,7 +1348,7 @@
+ $(srcdir)/daemon/remote.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
+ $(srcdir)/util/config_file.h $(srcdir)/sldns/keyraw.h $(srcdir)/daemon/unbound.c $(srcdir)/daemon/daemon.h \
+- $(srcdir)/util/alloc.h $(srcdir)/services/modstack.h \
++ $(srcdir)/util/alloc.h $(srcdir)/util/timeval_func.h $(srcdir)/services/modstack.h \
+ $(srcdir)/util/storage/slabhash.h $(srcdir)/services/listen_dnsport.h $(srcdir)/services/cache/rrset.h \
+ $(srcdir)/services/cache/infra.h $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rtt.h \
+ $(srcdir)/util/data/msgreply.h $(srcdir)/util/fptr_wlist.h $(srcdir)/util/module.h \
+@@ -1357,7 +1362,7 @@
+ worker.lo worker.o: $(srcdir)/daemon/worker.c config.h $(srcdir)/util/log.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/random.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
+- $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
++ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/util/alloc.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \
+ $(srcdir)/sldns/rrdef.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h $(srcdir)/libunbound/unbound.h \
+ $(srcdir)/util/module.h $(srcdir)/dnstap/dnstap.h $(srcdir)/daemon/daemon.h \
+@@ -1409,7 +1414,7 @@
+ $(srcdir)/validator/val_kcache.h $(srcdir)/validator/val_neg.h
+ replay.lo replay.o: $(srcdir)/testcode/replay.c config.h $(srcdir)/util/log.h $(srcdir)/util/net_help.h \
+ $(srcdir)/util/config_file.h $(srcdir)/testcode/replay.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+- $(srcdir)/testcode/testpkts.h $(srcdir)/util/rbtree.h \
++ $(srcdir)/testcode/testpkts.h $(srcdir)/util/rbtree.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/testcode/fake_event.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/rrdef.h
+ fake_event.lo fake_event.o: $(srcdir)/testcode/fake_event.c config.h $(srcdir)/testcode/fake_event.h \
+ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
+@@ -1417,7 +1422,7 @@
+ $(srcdir)/util/locks.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h \
+ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgencode.h $(srcdir)/util/data/dname.h \
+ $(srcdir)/util/edns.h $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/util/config_file.h \
+- $(srcdir)/services/listen_dnsport.h $(srcdir)/services/outside_network.h \
++ $(srcdir)/services/listen_dnsport.h $(srcdir)/services/outside_network.h $(srcdir)/util/timeval_func.h \
+ $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h \
+ $(srcdir)/testcode/replay.h $(srcdir)/testcode/testpkts.h $(srcdir)/util/fptr_wlist.h $(srcdir)/util/module.h \
+ $(srcdir)/util/tube.h $(srcdir)/services/mesh.h $(srcdir)/services/modstack.h $(srcdir)/services/rpz.h \
+--- contrib/unbound/README.md.orig
++++ contrib/unbound/README.md
+@@ -1,6 +1,6 @@
+ # Unbound
+
+-[![Travis Build Status](https://travis-ci.org/NLnetLabs/unbound.svg?branch=master)](https://travis-ci.org/NLnetLabs/unbound)
++[![Github Build Status](https://github.com/NLnetLabs/unbound/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/NLnetLabs/unbound/actions)
+ [![Packaging status](https://repology.org/badge/tiny-repos/unbound.svg)](https://repology.org/project/unbound/versions)
+ [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/unbound.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:unbound)
+ [![Documentation Status](https://readthedocs.org/projects/unbound/badge/?version=latest)](https://unbound.readthedocs.io/en/latest/?badge=latest)
+@@ -17,7 +17,9 @@
+ ## Compiling
+
+ Make sure you have the C toolchain, OpenSSL and its include files, and libexpat
+-installed. Unbound can be compiled and installed using:
++installed.
++If building from the repository source you also need flex and bison installed.
++Unbound can be compiled and installed using:
+
+ ```
+ ./configure && make && make install
+@@ -27,7 +29,7 @@
+ outgoing ports. By default max 256 ports are opened at the same time and the
+ builtin alternative is equally capable and a little faster.
+
+-Use the `--with-libevent=dir` configure option to compile Unbound with libevent
++Use the `--with-libevent` configure option to compile Unbound with libevent
+ support.
+
+ ## Unbound configuration
+--- contrib/unbound/acx_nlnetlabs.m4.orig
++++ contrib/unbound/acx_nlnetlabs.m4
+@@ -2,7 +2,9 @@
+ # Copyright 2009, Wouter Wijngaards, NLnet Labs.
+ # BSD licensed.
+ #
+-# Version 44
++# Version 46
++# 2023-05-04 fix to remove unused whitespace.
++# 2023-01-26 fix -Wstrict-prototypes.
+ # 2022-09-01 fix checking if nonblocking sockets work on OpenBSD.
+ # 2021-08-17 fix sed script in ssldir split handling.
+ # 2021-08-17 fix for openssl to detect split version, with ssldir_include
+@@ -187,7 +189,7 @@
+ AC_CACHE_VAL(cv_prog_cc_flag_needed_$cache,
+ [
+ echo '$2' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -233,7 +235,7 @@
+ AC_DEFUN([ACX_DEPFLAG],
+ [
+ AC_MSG_CHECKING([$CC dependency flag])
+-echo 'void f(){}' >conftest.c
++echo 'void f(void){}' >conftest.c
+ if test "`$CC -MM conftest.c 2>&1`" = "conftest.o: conftest.c"; then
+ DEPFLAG="-MM"
+ else
+@@ -272,7 +274,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -309,7 +311,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -335,7 +337,7 @@
+ [
+ #include <stdbool.h>
+ #include <ctype.h>
+-int test() {
++int test(void) {
+ int a = 0;
+ return a;
+ }
+@@ -345,7 +347,7 @@
+ [
+ #include <ctype.h>
+
+-int test() {
++int test(void) {
+ int a;
+ a = isascii(32);
+ return a;
+@@ -356,7 +358,7 @@
+ [
+ #include <netinet/in.h>
+
+-int test() {
++int test(void) {
+ struct in6_pktinfo inf;
+ int a = (int)sizeof(inf);
+ return a;
+@@ -370,7 +372,7 @@
+ [
+ #include <unistd.h>
+
+-int test() {
++int test(void) {
+ int a = setresgid(0,0,0);
+ a = setresuid(0,0,0);
+ return a;
+@@ -385,7 +387,7 @@
+ #endif
+ #include <netdb.h>
+
+-int test() {
++int test(void) {
+ int a = 0;
+ char *t;
+ time_t time = 0;
+@@ -413,7 +415,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -475,7 +477,7 @@
+ dnl Setup ATTR_FORMAT config.h parts.
+ dnl make sure you call ACX_CHECK_FORMAT_ATTRIBUTE also.
+ AC_DEFUN([AHX_CONFIG_FORMAT_ATTRIBUTE],
+-[
++[
+ #ifdef HAVE_ATTR_FORMAT
+ # define ATTR_FORMAT(archetype, string_index, first_to_check) \
+ __attribute__ ((format (archetype, string_index, first_to_check)))
+@@ -834,7 +836,7 @@
+ ACX_CHECK_COMPILER_FLAG_NEEDED(-D_LARGEFILE_SOURCE=1,
+ [
+ #include <stdio.h>
+-int test() {
++int test(void) {
+ int a = fseeko(stdin, 0, 0);
+ return a;
+ }
+@@ -859,7 +861,7 @@
+ #ifdef __cplusplus
+ }
+ #endif
+-int main() {
++int main(void) {
+ ;
+ return 0;
+ }
+@@ -923,7 +925,7 @@
+ AC_CACHE_VAL(cv_cc_deprecated_$cache,
+ [
+ echo '$3' >conftest.c
+-echo 'void f(){ $2 }' >>conftest.c
++echo 'void f(void){ $2 }' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS -c conftest.c 2>&1 | grep -e deprecated -e unavailable`"; then
+ eval "cv_cc_deprecated_$cache=no"
+ else
+@@ -1317,7 +1319,7 @@
+ #ifdef HAVE_WINSOCK2_H
+ #define FD_SET_T (u_int)
+ #else
+-#define FD_SET_T
++#define FD_SET_T
+ #endif
+ ])
+
+@@ -1355,7 +1357,7 @@
+ AC_DEFUN([AHX_CONFIG_FLAG_OMITTED],
+ [#if defined($1) && !defined($2)
+ #define $2 $3
+-[#]endif ])
++[#]endif])
+
+ dnl Wrapper for AHX_CONFIG_FLAG_OMITTED for -D style flags
+ dnl $1: the -DNAME or -DNAME=value string.
+--- contrib/unbound/acx_python.m4.orig
++++ contrib/unbound/acx_python.m4
+@@ -17,33 +17,62 @@
+ PYTHON_VERSION=`$PYTHON -c "import sys; \
+ print(sys.version.split()[[0]])"`
+ fi
++ # calculate the version number components.
++ [
++ v="$PYTHON_VERSION"
++ PYTHON_VERSION_MAJOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MAJOR"; then PYTHON_VERSION_MAJOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_MINOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MINOR"; then PYTHON_VERSION_MINOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_PATCH=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_PATCH"; then PYTHON_VERSION_PATCH="0"; fi
++ ]
+
+- # Check if you have sysconfig
+- AC_MSG_CHECKING([for the sysconfig Python module])
+- if ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`; then
++ # For some systems, sysconfig exists, but has the wrong paths,
++ # on Debian 10, for python 2.7 and 3.7. So, we check the version,
++ # and for older versions try distutils.sysconfig first. For newer
++ # versions>=3.10, where distutils.sysconfig is deprecated, use
++ # sysconfig first and then attempt the other one.
++ py_distutils_first="no"
++ if test $PYTHON_VERSION_MAJOR -lt 3; then
++ py_distutils_first="yes"
++ fi
++ if test $PYTHON_VERSION_MAJOR -eq 3 -a $PYTHON_VERSION_MINOR -lt 10; then
++ py_distutils_first="yes"
++ fi
++
++ # Check if you have the first module
++ if test "$py_distutils_first" = "yes"; then m="distutils"; else m="sysconfig"; fi
++ sysconfig_module=""
++ AC_MSG_CHECKING([for the $m Python module])
++ if ac_modulecheck_result1=`$PYTHON -c "import $m" 2>&1`; then
+ AC_MSG_RESULT([yes])
+- sysconfig_module="sysconfig"
+- # if yes, use sysconfig, because distutils is deprecated.
++ sysconfig_module="$m"
+ else
+ AC_MSG_RESULT([no])
+- # if no, try to use distutils
++ fi
+
+- #
+- # Check if you have distutils, else fail
+- #
+- AC_MSG_CHECKING([for the distutils Python package])
+- if ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`; then
++ # if not found, try the other one.
++ if test -z "$sysconfig_module"; then
++ if test "$py_distutils_first" = "yes"; then m2="sysconfig"; else m2="distutils"; fi
++ AC_MSG_CHECKING([for the $m2 Python module])
++ if ac_modulecheck_result2=`$PYTHON -c "import $m2" 2>&1`; then
+ AC_MSG_RESULT([yes])
++ sysconfig_module="$m2"
+ else
+ AC_MSG_RESULT([no])
+- AC_MSG_ERROR([cannot import Python module "distutils".
+- Please check your Python installation. The error was:
+- $ac_distutils_result])
++ AC_MSG_ERROR([cannot import Python module "$m", or "$m2".
++ Please check your Python installation. The errors are:
++ $m
++ $ac_modulecheck_result1
++ $m2
++ $ac_modulecheck_result2])
+ PYTHON_VERSION=""
+ fi
+-
+- sysconfig_module="distutils.sysconfig"
+ fi
++ if test "$sysconfig_module" = "distutils"; then sysconfig_module="distutils.sysconfig"; fi
+
+ #
+ # Check for Python include path
+--- contrib/unbound/cachedb/cachedb.c.orig
++++ contrib/unbound/cachedb/cachedb.c
+@@ -102,7 +102,6 @@
+ testframe_init(struct module_env* env, struct cachedb_env* cachedb_env)
+ {
+ struct testframe_moddata* d;
+- (void)env;
+ verbose(VERB_ALGO, "testframe_init");
+ d = (struct testframe_moddata*)calloc(1,
+ sizeof(struct testframe_moddata));
+@@ -111,6 +110,15 @@
+ log_err("out of memory");
+ return 0;
+ }
++ /* Register an EDNS option (65534) to bypass the worker cache lookup
++ * for testing */
++ if(!edns_register_option(LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST,
++ 1 /* bypass cache */,
++ 0 /* no aggregation */, env)) {
++ log_err("testframe_init, could not register test opcode");
++ free(d);
++ return 0;
++ }
+ lock_basic_init(&d->lock);
+ lock_protect(&d->lock, d, sizeof(*d));
+ return 1;
+@@ -218,6 +226,8 @@
+ cachedb_apply_cfg(struct cachedb_env* cachedb_env, struct config_file* cfg)
+ {
+ const char* backend_str = cfg->cachedb_backend;
++ if(!backend_str || *backend_str==0)
++ return 1;
+ cachedb_env->backend = cachedb_find_backend(backend_str);
+ if(!cachedb_env->backend) {
+ log_err("cachedb: cannot find backend name '%s'", backend_str);
+@@ -228,7 +238,7 @@
+ return 1;
+ }
+
+-int
++int
+ cachedb_init(struct module_env* env, int id)
+ {
+ struct cachedb_env* cachedb_env = (struct cachedb_env*)calloc(1,
+@@ -255,11 +265,11 @@
+ return 0;
+ }
+ cachedb_env->enabled = 1;
+- if(env->cfg->serve_expired_reply_ttl)
++ if(env->cfg->serve_expired && env->cfg->serve_expired_reply_ttl)
+ log_warn(
+ "cachedb: serve-expired-reply-ttl is set but not working for data "
+- "originating from the external cache; 0 TLL is used for those.");
+- if(env->cfg->serve_expired_client_timeout)
++ "originating from the external cache; 0 TTL is used for those.");
++ if(env->cfg->serve_expired && env->cfg->serve_expired_client_timeout)
+ log_warn(
+ "cachedb: serve-expired-client-timeout is set but not working for "
+ "data originating from the external cache; expired data are used "
+@@ -267,19 +277,16 @@
+ return 1;
+ }
+
+-void
++void
+ cachedb_deinit(struct module_env* env, int id)
+ {
+ struct cachedb_env* cachedb_env;
+ if(!env || !env->modinfo[id])
+ return;
+ cachedb_env = (struct cachedb_env*)env->modinfo[id];
+- /* free contents */
+- /* TODO */
+ if(cachedb_env->enabled) {
+ (*cachedb_env->backend->deinit)(env, cachedb_env);
+ }
+-
+ free(cachedb_env);
+ env->modinfo[id] = NULL;
+ }
+@@ -406,6 +413,14 @@
+ if(qstate->return_msg->rep->ttl == 0 &&
+ !qstate->env->cfg->serve_expired)
+ return 0;
++
++ /* The EDE is added to the out-list so it is encoded in the cached message */
++ if (qstate->env->cfg->ede && qstate->return_msg->rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns.opt_list_out, qstate->env->scratch,
++ qstate->return_msg->rep->reason_bogus,
++ qstate->return_msg->rep->reason_bogus_str);
++ }
++
+ if(verbosity >= VERB_ALGO)
+ log_dns_msg("cachedb encoding", &qstate->return_msg->qinfo,
+ qstate->return_msg->rep);
+@@ -502,6 +517,7 @@
+ {
+ struct msg_parse* prs;
+ struct edns_data edns;
++ struct edns_option* ede;
+ uint64_t timestamp, expiry;
+ time_t adjust;
+ size_t lim = sldns_buffer_limit(buf);
+@@ -539,6 +555,24 @@
+ if(!qstate->return_msg)
+ return 0;
+
++ /* We find the EDE in the in-list after parsing */
++ if(qstate->env->cfg->ede &&
++ (ede = edns_opt_list_find(edns.opt_list_in, LDNS_EDNS_EDE))) {
++ if(ede->opt_len >= 2) {
++ qstate->return_msg->rep->reason_bogus =
++ sldns_read_uint16(ede->opt_data);
++ }
++ /* allocate space and store the error string and it's size */
++ if(ede->opt_len > 2) {
++ size_t ede_len = ede->opt_len - 2;
++ qstate->return_msg->rep->reason_bogus_str = regional_alloc(
++ qstate->region, sizeof(char) * (ede_len+1));
++ memcpy(qstate->return_msg->rep->reason_bogus_str,
++ ede->opt_data+2, ede_len);
++ qstate->return_msg->rep->reason_bogus_str[ede_len] = 0;
++ }
++ }
++
+ qstate->return_rcode = LDNS_RCODE_NOERROR;
+
+ /* see how much of the TTL expired, and remove it */
+@@ -630,11 +664,15 @@
+ * See if unbound's internal cache can answer the query
+ */
+ static int
+-cachedb_intcache_lookup(struct module_qstate* qstate)
++cachedb_intcache_lookup(struct module_qstate* qstate, struct cachedb_env* cde)
+ {
+ uint8_t* dpname=NULL;
+ size_t dpnamelen=0;
+ struct dns_msg* msg;
++ /* for testframe bypass this lookup */
++ if(cde->backend == &testframe_backend) {
++ return 0;
++ }
+ if(iter_stub_fwd_no_cache(qstate, &qstate->qinfo,
+ &dpname, &dpnamelen))
+ return 0; /* no cache for these queries */
+@@ -693,6 +731,7 @@
+ struct cachedb_qstate* ATTR_UNUSED(iq),
+ struct cachedb_env* ie, int id)
+ {
++ qstate->is_cachedb_answer = 0;
+ /* check if we are enabled, and skip if so */
+ if(!ie->enabled) {
+ /* pass request to next module */
+@@ -709,7 +748,7 @@
+
+ /* lookup inside unbound's internal cache.
+ * This does not look for expired entries. */
+- if(cachedb_intcache_lookup(qstate)) {
++ if(cachedb_intcache_lookup(qstate, ie)) {
+ if(verbosity >= VERB_ALGO) {
+ if(qstate->return_msg->rep)
+ log_dns_msg("cachedb internal cache lookup",
+@@ -746,6 +785,7 @@
+ qstate->ext_state[id] = module_wait_module;
+ return;
+ }
++ qstate->is_cachedb_answer = 1;
+ /* we are done with the query */
+ qstate->ext_state[id] = module_finished;
+ return;
+@@ -768,12 +808,18 @@
+ cachedb_handle_response(struct module_qstate* qstate,
+ struct cachedb_qstate* ATTR_UNUSED(iq), struct cachedb_env* ie, int id)
+ {
++ qstate->is_cachedb_answer = 0;
+ /* check if we are not enabled or instructed to not cache, and skip */
+ if(!ie->enabled || qstate->no_cache_store) {
+ /* we are done with the query */
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
++ if(qstate->env->cfg->cachedb_no_store) {
++ /* do not store the item in the external cache */
++ qstate->ext_state[id] = module_finished;
++ return;
++ }
+
+ /* store the item into the backend cache */
+ cachedb_extcache_store(qstate, ie);
+--- contrib/unbound/cachedb/redis.c.orig
++++ contrib/unbound/cachedb/redis.c
+@@ -56,19 +56,43 @@
+ int numctxs; /* number of ctx entries */
+ const char* server_host; /* server's IP address or host name */
+ int server_port; /* server's TCP port */
++ const char* server_path; /* server's unix path, or "", NULL if unused */
++ const char* server_password; /* server's AUTH password, or "", NULL if unused */
+ struct timeval timeout; /* timeout for connection setup and commands */
++ int logical_db; /* the redis logical database to use */
+ };
+
+ static redisReply* redis_command(struct module_env*, struct cachedb_env*,
+ const char*, const uint8_t*, size_t);
+
++static void
++moddata_clean(struct redis_moddata** moddata) {
++ if(!moddata || !*moddata)
++ return;
++ if((*moddata)->ctxs) {
++ int i;
++ for(i = 0; i < (*moddata)->numctxs; i++) {
++ if((*moddata)->ctxs[i])
++ redisFree((*moddata)->ctxs[i]);
++ }
++ free((*moddata)->ctxs);
++ }
++ free(*moddata);
++ *moddata = NULL;
++}
++
+ static redisContext*
+ redis_connect(const struct redis_moddata* moddata)
+ {
+ redisContext* ctx;
+
+- ctx = redisConnectWithTimeout(moddata->server_host,
+- moddata->server_port, moddata->timeout);
++ if(moddata->server_path && moddata->server_path[0]!=0) {
++ ctx = redisConnectUnixWithTimeout(moddata->server_path,
++ moddata->timeout);
++ } else {
++ ctx = redisConnectWithTimeout(moddata->server_host,
++ moddata->server_port, moddata->timeout);
++ }
+ if(!ctx || ctx->err) {
+ const char *errstr = "out of memory";
+ if(ctx)
+@@ -80,9 +104,31 @@
+ log_err("failed to set redis timeout");
+ goto fail;
+ }
++ if(moddata->server_password && moddata->server_password[0]!=0) {
++ redisReply* rep;
++ rep = redisCommand(ctx, "AUTH %s", moddata->server_password);
++ if(!rep || rep->type == REDIS_REPLY_ERROR) {
++ log_err("failed to authenticate with password");
++ freeReplyObject(rep);
++ goto fail;
++ }
++ freeReplyObject(rep);
++ }
++ if(moddata->logical_db > 0) {
++ redisReply* rep;
++ rep = redisCommand(ctx, "SELECT %d", moddata->logical_db);
++ if(!rep || rep->type == REDIS_REPLY_ERROR) {
++ log_err("failed to set logical database (%d)",
++ moddata->logical_db);
++ freeReplyObject(rep);
++ goto fail;
++ }
++ freeReplyObject(rep);
++ }
++ verbose(VERB_OPS, "Connection to Redis established");
+ return ctx;
+
+- fail:
++fail:
+ if(ctx)
+ redisFree(ctx);
+ return NULL;
+@@ -94,28 +140,36 @@
+ int i;
+ struct redis_moddata* moddata = NULL;
+
+- verbose(VERB_ALGO, "redis_init");
++ verbose(VERB_OPS, "Redis initialization");
+
+ moddata = calloc(1, sizeof(struct redis_moddata));
+ if(!moddata) {
+ log_err("out of memory");
+- return 0;
++ goto fail;
+ }
+ moddata->numctxs = env->cfg->num_threads;
+ moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
+ if(!moddata->ctxs) {
+ log_err("out of memory");
+- free(moddata);
+- return 0;
++ goto fail;
+ }
+ /* note: server_host is a shallow reference to configured string.
+ * we don't have to free it in this module. */
+ moddata->server_host = env->cfg->redis_server_host;
+ moddata->server_port = env->cfg->redis_server_port;
++ moddata->server_path = env->cfg->redis_server_path;
++ moddata->server_password = env->cfg->redis_server_password;
+ moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
+ moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
+- for(i = 0; i < moddata->numctxs; i++)
+- moddata->ctxs[i] = redis_connect(moddata);
++ moddata->logical_db = env->cfg->redis_logical_db;
++ for(i = 0; i < moddata->numctxs; i++) {
++ redisContext* ctx = redis_connect(moddata);
++ if(!ctx) {
++ log_err("redis_init: failed to init redis");
++ goto fail;
++ }
++ moddata->ctxs[i] = ctx;
++ }
+ cachedb_env->backend_data = moddata;
+ if(env->cfg->redis_expire_records) {
+ redisReply* rep = NULL;
+@@ -128,7 +182,7 @@
+ log_err("redis_init: failed to init redis, the "
+ "redis-expire-records option requires the SETEX command "
+ "(redis >= 2.0.0)");
+- return 0;
++ goto fail;
+ }
+ redis_reply_type = rep->type;
+ freeReplyObject(rep);
+@@ -140,11 +194,14 @@
+ log_err("redis_init: failed to init redis, the "
+ "redis-expire-records option requires the SETEX command "
+ "(redis >= 2.0.0)");
+- return 0;
++ goto fail;
+ }
+ }
+-
+ return 1;
++
++fail:
++ moddata_clean(&moddata);
++ return 0;
+ }
+
+ static void
+@@ -154,19 +211,8 @@
+ cachedb_env->backend_data;
+ (void)env;
+
+- verbose(VERB_ALGO, "redis_deinit");
+-
+- if(!moddata)
+- return;
+- if(moddata->ctxs) {
+- int i;
+- for(i = 0; i < moddata->numctxs; i++) {
+- if(moddata->ctxs[i])
+- redisFree(moddata->ctxs[i]);
+- }
+- free(moddata->ctxs);
+- }
+- free(moddata);
++ verbose(VERB_OPS, "Redis deinitialization");
++ moddata_clean(&moddata);
+ }
+
+ /*
+--- contrib/unbound/compat/getentropy_solaris.c.orig
++++ contrib/unbound/compat/getentropy_solaris.c
+@@ -47,7 +47,7 @@
+ #define SHA512_Update SHA512Update
+ #define SHA512_Final SHA512Final
+ #else
+-#include "openssl/sha.h"
++#include <openssl/sha.h>
+ #endif
+
+ #include <sys/vfs.h>
+--- contrib/unbound/config.guess.orig
++++ contrib/unbound/config.guess
+@@ -1,10 +1,10 @@
+ #! /bin/sh
+ # Attempt to guess a canonical system name.
+-# Copyright 1992-2022 Free Software Foundation, Inc.
++# Copyright 1992-2024 Free Software Foundation, Inc.
+
+ # shellcheck disable=SC2006,SC2268 # see below for rationale
+
+-timestamp='2022-09-17'
++timestamp='2024-01-01'
+
+ # This file is free software; you can redistribute it and/or modify it
+ # under the terms of the GNU General Public License as published by
+@@ -47,7 +47,7 @@
+ usage="\
+ Usage: $0 [OPTION]
+
+-Output the configuration name of the system \`$me' is run on.
++Output the configuration name of the system '$me' is run on.
+
+ Options:
+ -h, --help print this help, then exit
+@@ -60,13 +60,13 @@
+ GNU config.guess ($timestamp)
+
+ Originally written by Per Bothner.
+-Copyright 1992-2022 Free Software Foundation, Inc.
++Copyright 1992-2024 Free Software Foundation, Inc.
+
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+ help="
+-Try \`$me --help' for more information."
++Try '$me --help' for more information."
+
+ # Parse command line
+ while test $# -gt 0 ; do
+@@ -102,8 +102,8 @@
+ # temporary files to be created and, as you can see below, it is a
+ # headache to deal with in a portable fashion.
+
+-# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+-# use `HOST_CC' if defined, but it is deprecated.
++# Historically, 'CC_FOR_BUILD' used to be named 'HOST_CC'. We still
++# use 'HOST_CC' if defined, but it is deprecated.
+
+ # Portable tmp directory creation inspired by the Autoconf team.
+
+@@ -155,6 +155,9 @@
+
+ set_cc_for_build
+ cat <<-EOF > "$dummy.c"
++ #if defined(__ANDROID__)
++ LIBC=android
++ #else
+ #include <features.h>
+ #if defined(__UCLIBC__)
+ LIBC=uclibc
+@@ -162,6 +165,8 @@
+ LIBC=dietlibc
+ #elif defined(__GLIBC__)
+ LIBC=gnu
++ #elif defined(__LLVM_LIBC__)
++ LIBC=llvm
+ #else
+ #include <stdarg.h>
+ /* First heuristic to detect musl libc. */
+@@ -169,6 +174,7 @@
+ LIBC=musl
+ #endif
+ #endif
++ #endif
+ EOF
+ cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+ eval "$cc_set_libc"
+@@ -459,7 +465,7 @@
+ UNAME_RELEASE=`uname -v`
+ ;;
+ esac
+- # Japanese Language versions have a version number like `4.1.3-JL'.
++ # Japanese Language versions have a version number like '4.1.3-JL'.
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
+ GUESS=sparc-sun-sunos$SUN_REL
+ ;;
+@@ -904,7 +910,7 @@
+ fi
+ ;;
+ *:FreeBSD:*:*)
+- UNAME_PROCESSOR=`/usr/bin/uname -p`
++ UNAME_PROCESSOR=`uname -p`
+ case $UNAME_PROCESSOR in
+ amd64)
+ UNAME_PROCESSOR=x86_64 ;;
+@@ -976,7 +982,27 @@
+ GUESS=$UNAME_MACHINE-unknown-minix
+ ;;
+ aarch64:Linux:*:*)
+- GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
++ set_cc_for_build
++ CPU=$UNAME_MACHINE
++ LIBCABI=$LIBC
++ if test "$CC_FOR_BUILD" != no_compiler_found; then
++ ABI=64
++ sed 's/^ //' << EOF > "$dummy.c"
++ #ifdef __ARM_EABI__
++ #ifdef __ARM_PCS_VFP
++ ABI=eabihf
++ #else
++ ABI=eabi
++ #endif
++ #endif
++EOF
++ cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'`
++ eval "$cc_set_abi"
++ case $ABI in
++ eabi | eabihf) CPU=armv8l; LIBCABI=$LIBC$ABI ;;
++ esac
++ fi
++ GUESS=$CPU-unknown-linux-$LIBCABI
+ ;;
+ aarch64_be:Linux:*:*)
+ UNAME_MACHINE=aarch64_be
+@@ -1042,6 +1068,15 @@
+ k1om:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
++ kvx:Linux:*:*)
++ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
++ ;;
++ kvx:cos:*:*)
++ GUESS=$UNAME_MACHINE-unknown-cos
++ ;;
++ kvx:mbr:*:*)
++ GUESS=$UNAME_MACHINE-unknown-mbr
++ ;;
+ loongarch32:Linux:*:* | loongarch64:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+@@ -1197,7 +1232,7 @@
+ GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
+ ;;
+ i*86:OS/2:*:*)
+- # If we were able to find `uname', then EMX Unix compatibility
++ # If we were able to find 'uname', then EMX Unix compatibility
+ # is probably installed.
+ GUESS=$UNAME_MACHINE-pc-os2-emx
+ ;;
+@@ -1338,7 +1373,7 @@
+ GUESS=ns32k-sni-sysv
+ fi
+ ;;
+- PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
++ PENTIUM:*:4.0*:*) # Unisys 'ClearPath HMP IX 4000' SVR4/MP effort
+ # says <Richard.M.Bartel@ccMail.Census.GOV>
+ GUESS=i586-unisys-sysv4
+ ;;
+@@ -1560,6 +1595,9 @@
+ *:Unleashed:*:*)
+ GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
+ ;;
++ *:Ironclad:*:*)
++ GUESS=$UNAME_MACHINE-unknown-ironclad
++ ;;
+ esac
+
+ # Do we have a guess based on uname results?
+--- contrib/unbound/config.h.in.orig
++++ contrib/unbound/config.h.in
+@@ -364,6 +364,9 @@
+ /* Define if we have LibreSSL */
+ #undef HAVE_LIBRESSL
+
++/* Define to 1 if you have the <linux/net_tstamp.h> header file. */
++#undef HAVE_LINUX_NET_TSTAMP_H
++
+ /* Define to 1 if you have the `localtime_r' function. */
+ #undef HAVE_LOCALTIME_R
+
+@@ -1068,39 +1071,39 @@
+
+ #if defined(OMITTED__D_GNU_SOURCE) && !defined(_GNU_SOURCE)
+ #define _GNU_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_BSD_SOURCE) && !defined(_BSD_SOURCE)
+ #define _BSD_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_DEFAULT_SOURCE) && !defined(_DEFAULT_SOURCE)
+ #define _DEFAULT_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D__EXTENSIONS__) && !defined(__EXTENSIONS__)
+ #define __EXTENSIONS__ 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_POSIX_C_SOURCE_200112) && !defined(_POSIX_C_SOURCE)
+ #define _POSIX_C_SOURCE 200112
+-#endif
++#endif
+
+ #if defined(OMITTED__D_XOPEN_SOURCE_600) && !defined(_XOPEN_SOURCE)
+ #define _XOPEN_SOURCE 600
+-#endif
++#endif
+
+ #if defined(OMITTED__D_XOPEN_SOURCE_EXTENDED_1) && !defined(_XOPEN_SOURCE_EXTENDED)
+ #define _XOPEN_SOURCE_EXTENDED 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_ALL_SOURCE) && !defined(_ALL_SOURCE)
+ #define _ALL_SOURCE 1
+-#endif
++#endif
+
+ #if defined(OMITTED__D_LARGEFILE_SOURCE_1) && !defined(_LARGEFILE_SOURCE)
+ #define _LARGEFILE_SOURCE 1
+-#endif
++#endif
+
+
+
+@@ -1184,7 +1187,7 @@
+ #endif
+
+
+-
++
+ #ifdef HAVE_ATTR_FORMAT
+ # define ATTR_FORMAT(archetype, string_index, first_to_check) \
+ __attribute__ ((format (archetype, string_index, first_to_check)))
+@@ -1294,7 +1297,7 @@
+ #ifdef HAVE_WINSOCK2_H
+ #define FD_SET_T (u_int)
+ #else
+-#define FD_SET_T
++#define FD_SET_T
+ #endif
+
+
+--- contrib/unbound/config.sub.orig
++++ contrib/unbound/config.sub
+@@ -1,10 +1,10 @@
+ #! /bin/sh
+ # Configuration validation subroutine script.
+-# Copyright 1992-2022 Free Software Foundation, Inc.
++# Copyright 1992-2024 Free Software Foundation, Inc.
+
+ # shellcheck disable=SC2006,SC2268 # see below for rationale
+
+-timestamp='2022-09-17'
++timestamp='2024-01-01'
+
+ # This file is free software; you can redistribute it and/or modify it
+ # under the terms of the GNU General Public License as published by
+@@ -76,13 +76,13 @@
+ version="\
+ GNU config.sub ($timestamp)
+
+-Copyright 1992-2022 Free Software Foundation, Inc.
++Copyright 1992-2024 Free Software Foundation, Inc.
+
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+ help="
+-Try \`$me --help' for more information."
++Try '$me --help' for more information."
+
+ # Parse command line
+ while test $# -gt 0 ; do
+@@ -130,7 +130,7 @@
+ # Separate into logical components for further validation
+ case $1 in
+ *-*-*-*-*)
+- echo Invalid configuration \`"$1"\': more than four components >&2
++ echo "Invalid configuration '$1': more than four components" >&2
+ exit 1
+ ;;
+ *-*-*-*)
+@@ -145,7 +145,8 @@
+ nto-qnx* | linux-* | uclinux-uclibc* \
+ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
+ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
+- | storm-chaos* | os2-emx* | rtmk-nova* | managarm-*)
++ | storm-chaos* | os2-emx* | rtmk-nova* | managarm-* \
++ | windows-* )
+ basic_machine=$field1
+ basic_os=$maybe_os
+ ;;
+@@ -943,7 +944,7 @@
+ EOF
+ IFS=$saved_IFS
+ ;;
+- # We use `pc' rather than `unknown'
++ # We use 'pc' rather than 'unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+@@ -1075,7 +1076,7 @@
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ cpu=i586
+ ;;
+- pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
++ pentiumpro-* | p6-* | 6x86-* | athlon-* | athlon_*-*)
+ cpu=i686
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+@@ -1180,7 +1181,7 @@
+ case $cpu in
+ 1750a | 580 \
+ | a29k \
+- | aarch64 | aarch64_be \
++ | aarch64 | aarch64_be | aarch64c | arm64ec \
+ | abacus \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
+@@ -1199,12 +1200,14 @@
+ | d10v | d30v | dlx | dsp16xx \
+ | e2k | elxsi | epiphany \
+ | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
++ | javascript \
+ | h8300 | h8500 \
+ | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | hexagon \
+ | i370 | i*86 | i860 | i960 | ia16 | ia64 \
+ | ip2k | iq2000 \
+ | k1om \
++ | kvx \
+ | le32 | le64 \
+ | lm32 \
+ | loongarch32 | loongarch64 \
+@@ -1213,36 +1216,13 @@
+ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
+ | m88110 | m88k | maxq | mb | mcore | mep | metag \
+ | microblaze | microblazeel \
+- | mips | mipsbe | mipseb | mipsel | mipsle \
+- | mips16 \
+- | mips64 | mips64eb | mips64el \
+- | mips64octeon | mips64octeonel \
+- | mips64orion | mips64orionel \
+- | mips64r5900 | mips64r5900el \
+- | mips64vr | mips64vrel \
+- | mips64vr4100 | mips64vr4100el \
+- | mips64vr4300 | mips64vr4300el \
+- | mips64vr5000 | mips64vr5000el \
+- | mips64vr5900 | mips64vr5900el \
+- | mipsisa32 | mipsisa32el \
+- | mipsisa32r2 | mipsisa32r2el \
+- | mipsisa32r3 | mipsisa32r3el \
+- | mipsisa32r5 | mipsisa32r5el \
+- | mipsisa32r6 | mipsisa32r6el \
+- | mipsisa64 | mipsisa64el \
+- | mipsisa64r2 | mipsisa64r2el \
+- | mipsisa64r3 | mipsisa64r3el \
+- | mipsisa64r5 | mipsisa64r5el \
+- | mipsisa64r6 | mipsisa64r6el \
+- | mipsisa64sb1 | mipsisa64sb1el \
+- | mipsisa64sr71k | mipsisa64sr71kel \
+- | mipsr5900 | mipsr5900el \
+- | mipstx39 | mipstx39el \
++ | mips* \
+ | mmix \
+ | mn10200 | mn10300 \
+ | moxie \
+ | mt \
+ | msp430 \
++ | nanomips* \
+ | nds32 | nds32le | nds32be \
+ | nfp \
+ | nios | nios2 | nios2eb | nios2el \
+@@ -1274,6 +1254,7 @@
+ | ubicom32 \
+ | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
+ | vax \
++ | vc4 \
+ | visium \
+ | w65 \
+ | wasm32 | wasm64 \
+@@ -1285,7 +1266,7 @@
+ ;;
+
+ *)
+- echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
++ echo "Invalid configuration '$1': machine '$cpu-$vendor' not recognized" 1>&2
+ exit 1
+ ;;
+ esac
+@@ -1306,11 +1287,12 @@
+
+ # Decode manufacturer-specific aliases for certain operating systems.
+
+-if test x$basic_os != x
++if test x"$basic_os" != x
+ then
+
+ # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
+ # set os.
++obj=
+ case $basic_os in
+ gnu/linux*)
+ kernel=linux
+@@ -1510,10 +1492,16 @@
+ os=eabi
+ ;;
+ *)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ esac
+ ;;
++ aout* | coff* | elf* | pe*)
++ # These are machine code file formats, not OSes
++ obj=$os
++ os=
++ ;;
+ *)
+ # No normalization, but not necessarily accepted, that comes below.
+ ;;
+@@ -1532,12 +1520,15 @@
+ # system, and we'll never get to this point.
+
+ kernel=
++obj=
+ case $cpu-$vendor in
+ score-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ spu-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ *-acorn)
+ os=riscix1.2
+@@ -1547,28 +1538,35 @@
+ os=gnu
+ ;;
+ arm*-semi)
+- os=aout
++ os=
++ obj=aout
+ ;;
+ c4x-* | tic4x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ c8051-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ clipper-intergraph)
+ os=clix
+ ;;
+ hexagon-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ tic54x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ tic55x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ tic6x-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+@@ -1590,19 +1588,24 @@
+ os=sunos3
+ ;;
+ m68*-cisco)
+- os=aout
++ os=
++ obj=aout
+ ;;
+ mep-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ mips*-cisco)
+- os=elf
++ os=
++ obj=elf
+ ;;
+- mips*-*)
+- os=elf
++ mips*-*|nanomips*-*)
++ os=
++ obj=elf
+ ;;
+ or32-*)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=sysv3
+@@ -1611,7 +1614,8 @@
+ os=sunos4.1.1
+ ;;
+ pru-*)
+- os=elf
++ os=
++ obj=elf
+ ;;
+ *-be)
+ os=beos
+@@ -1692,10 +1696,12 @@
+ os=uxpv
+ ;;
+ *-rom68k)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-*bug)
+- os=coff
++ os=
++ obj=coff
+ ;;
+ *-apple)
+ os=macos
+@@ -1713,10 +1719,11 @@
+
+ fi
+
+-# Now, validate our (potentially fixed-up) OS.
++# Now, validate our (potentially fixed-up) individual pieces (OS, OBJ).
++
+ case $os in
+ # Sometimes we do "kernel-libc", so those need to count as OSes.
+- musl* | newlib* | relibc* | uclibc*)
++ llvm* | musl* | newlib* | relibc* | uclibc*)
+ ;;
+ # Likewise for "kernel-abi"
+ eabi* | gnueabi*)
+@@ -1724,6 +1731,9 @@
+ # VxWorks passes extra cpu info in the 4th filed.
+ simlinux | simwindows | spe)
+ ;;
++ # See `case $cpu-$os` validation below
++ ghcjs)
++ ;;
+ # Now accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST end in a * to match a version number.
+@@ -1732,7 +1742,7 @@
+ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
+ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
+ | hiux* | abug | nacl* | netware* | windows* \
+- | os9* | macos* | osx* | ios* \
++ | os9* | macos* | osx* | ios* | tvos* | watchos* \
+ | mpw* | magic* | mmixware* | mon960* | lnews* \
+ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
+ | aos* | aros* | cloudabi* | sortix* | twizzler* \
+@@ -1741,11 +1751,11 @@
+ | mirbsd* | netbsd* | dicos* | openedition* | ose* \
+ | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
+ | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
+- | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
+- | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
++ | bosx* | nextstep* | cxux* | oabi* \
++ | ptx* | ecoff* | winnt* | domain* | vsta* \
+ | udi* | lites* | ieee* | go32* | aux* | hcos* \
+ | chorusrdb* | cegcc* | glidix* | serenity* \
+- | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
++ | cygwin* | msys* | moss* | proelf* | rtems* \
+ | midipix* | mingw32* | mingw64* | mint* \
+ | uxpv* | beos* | mpeix* | udk* | moxiebox* \
+ | interix* | uwin* | mks* | rhapsody* | darwin* \
+@@ -1758,62 +1768,116 @@
+ | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
+ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
+ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
+- | fiwix* | mlibc* )
++ | fiwix* | mlibc* | cos* | mbr* | ironclad* )
+ ;;
+ # This one is extra strict with allowed versions
+ sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
+ # Don't forget version if it is 3.2v4 or newer.
+ ;;
++ # This refers to builds using the UEFI calling convention
++ # (which depends on the architecture) and PE file format.
++ # Note that this is both a different calling convention and
++ # different file format than that of GNU-EFI
++ # (x86_64-w64-mingw32).
++ uefi)
++ ;;
+ none)
+ ;;
+- kernel* )
++ kernel* | msvc* )
+ # Restricted further below
+ ;;
++ '')
++ if test x"$obj" = x
++ then
++ echo "Invalid configuration '$1': Blank OS only allowed with explicit machine code file format" 1>&2
++ fi
++ ;;
+ *)
+- echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
++ echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2
++ exit 1
++ ;;
++esac
++
++case $obj in
++ aout* | coff* | elf* | pe*)
++ ;;
++ '')
++ # empty is fine
++ ;;
++ *)
++ echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2
++ exit 1
++ ;;
++esac
++
++# Here we handle the constraint that a (synthetic) cpu and os are
++# valid only in combination with each other and nowhere else.
++case $cpu-$os in
++ # The "javascript-unknown-ghcjs" triple is used by GHC; we
++ # accept it here in order to tolerate that, but reject any
++ # variations.
++ javascript-ghcjs)
++ ;;
++ javascript-* | *-ghcjs)
++ echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2
+ exit 1
+ ;;
+ esac
+
+ # As a final step for OS-related things, validate the OS-kernel combination
+ # (given a valid OS), if there is a kernel.
+-case $kernel-$os in
+- linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
+- | linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* )
++case $kernel-$os-$obj in
++ linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \
++ | linux-mlibc*- | linux-musl*- | linux-newlib*- \
++ | linux-relibc*- | linux-uclibc*- )
+ ;;
+- uclinux-uclibc* )
++ uclinux-uclibc*- )
+ ;;
+- managarm-mlibc* | managarm-kernel* )
++ managarm-mlibc*- | managarm-kernel*- )
+ ;;
+- -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* )
++ windows*-msvc*-)
++ ;;
++ -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \
++ | -uclibc*- )
+ # These are just libc implementations, not actual OSes, and thus
+ # require a kernel.
+- echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
++ echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2
+ exit 1
+ ;;
+- -kernel* )
+- echo "Invalid configuration \`$1': \`$os' needs explicit kernel." 1>&2
++ -kernel*- )
++ echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2
+ exit 1
+ ;;
+- *-kernel* )
+- echo "Invalid configuration \`$1': \`$kernel' does not support \`$os'." 1>&2
++ *-kernel*- )
++ echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2
+ exit 1
+ ;;
+- kfreebsd*-gnu* | kopensolaris*-gnu*)
++ *-msvc*- )
++ echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2
++ exit 1
+ ;;
+- vxworks-simlinux | vxworks-simwindows | vxworks-spe)
++ kfreebsd*-gnu*- | kopensolaris*-gnu*-)
+ ;;
+- nto-qnx*)
++ vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-)
+ ;;
+- os2-emx)
++ nto-qnx*-)
++ ;;
++ os2-emx-)
++ ;;
++ *-eabi*- | *-gnueabi*-)
+ ;;
+- *-eabi* | *-gnueabi*)
++ none--*)
++ # None (no kernel, i.e. freestanding / bare metal),
++ # can be paired with an machine code file format
+ ;;
+- -*)
++ -*-)
+ # Blank kernel with real OS is always fine.
+ ;;
+- *-*)
+- echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
++ --*)
++ # Blank kernel and OS with real machine code file format is always fine.
++ ;;
++ *-*-*)
++ echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2
+ exit 1
+ ;;
+ esac
+@@ -1896,7 +1960,7 @@
+ ;;
+ esac
+
+-echo "$cpu-$vendor-${kernel:+$kernel-}$os"
++echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}"
+ exit
+
+ # Local variables:
+--- contrib/unbound/configure.orig
++++ contrib/unbound/configure
+@@ -1,6 +1,6 @@
+ #! /bin/sh
+ # Guess values for system-dependent variables and create Makefiles.
+-# Generated by GNU Autoconf 2.69 for unbound 1.17.1.
++# Generated by GNU Autoconf 2.69 for unbound 1.19.1.
+ #
+ # Report bugs to <unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues>.
+ #
+@@ -591,8 +591,8 @@
+ # Identity of this package.
+ PACKAGE_NAME='unbound'
+ PACKAGE_TARNAME='unbound'
+-PACKAGE_VERSION='1.17.1'
+-PACKAGE_STRING='unbound 1.17.1'
++PACKAGE_VERSION='1.19.1'
++PACKAGE_STRING='unbound 1.19.1'
+ PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
+ PACKAGE_URL=''
+
+@@ -1477,7 +1477,7 @@
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+-\`configure' configures unbound 1.17.1 to adapt to many kinds of systems.
++\`configure' configures unbound 1.19.1 to adapt to many kinds of systems.
+
+ Usage: $0 [OPTION]... [VAR=VALUE]...
+
+@@ -1543,7 +1543,7 @@
+
+ if test -n "$ac_init_help"; then
+ case $ac_init_help in
+- short | recursive ) echo "Configuration of unbound 1.17.1:";;
++ short | recursive ) echo "Configuration of unbound 1.19.1:";;
+ esac
+ cat <<\_ACEOF
+
+@@ -1785,7 +1785,7 @@
+ test -n "$ac_init_help" && exit $ac_status
+ if $ac_init_version; then
+ cat <<\_ACEOF
+-unbound configure 1.17.1
++unbound configure 1.19.1
+ generated by GNU Autoconf 2.69
+
+ Copyright (C) 2012 Free Software Foundation, Inc.
+@@ -2494,7 +2494,7 @@
+ This file contains any messages produced by compilers while
+ running configure, to aid debugging if configure makes a mistake.
+
+-It was created by unbound $as_me 1.17.1, which was
++It was created by unbound $as_me 1.19.1, which was
+ generated by GNU Autoconf 2.69. Invocation command line was
+
+ $ $0 $@
+@@ -2844,13 +2844,13 @@
+
+ UNBOUND_VERSION_MAJOR=1
+
+-UNBOUND_VERSION_MINOR=17
++UNBOUND_VERSION_MINOR=19
+
+ UNBOUND_VERSION_MICRO=1
+
+
+ LIBUNBOUND_CURRENT=9
+-LIBUNBOUND_REVISION=21
++LIBUNBOUND_REVISION=24
+ LIBUNBOUND_AGE=1
+ # 1.0.0 had 0:12:0
+ # 1.0.1 had 0:13:0
+@@ -2939,6 +2939,9 @@
+ # 1.16.3 had 9:19:1
+ # 1.17.0 had 9:20:1
+ # 1.17.1 had 9:21:1
++# 1.18.0 had 9:22:1
++# 1.19.0 had 9:23:1
++# 1.19.1 had 9:24:1
+
+ # Current -- the number of the binary API that we're implementing
+ # Revision -- which iteration of the implementation of the binary
+@@ -4603,450 +4606,186 @@
+
+ default_cflags=yes
+ fi
+-ac_ext=c
+-ac_cpp='$CPP $CPPFLAGS'
+-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+-ac_compiler_gnu=$ac_cv_c_compiler_gnu
+-if test -n "$ac_tool_prefix"; then
+- # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+-set dummy ${ac_tool_prefix}gcc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_CC="${ac_tool_prefix}gcc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+-fi
+-if test -z "$ac_cv_prog_CC"; then
+- ac_ct_CC=$CC
+- # Extract the first word of "gcc", so it can be a program name with args.
+-set dummy gcc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_ac_ct_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$ac_ct_CC"; then
+- ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_ac_ct_CC="gcc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-ac_ct_CC=$ac_cv_prog_ac_ct_CC
+-if test -n "$ac_ct_CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+-$as_echo "$ac_ct_CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+- if test "x$ac_ct_CC" = x; then
+- CC=""
+- else
+- case $cross_compiling:$ac_tool_warned in
+-yes:)
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+-ac_tool_warned=yes ;;
+-esac
+- CC=$ac_ct_CC
+- fi
+-else
+- CC="$ac_cv_prog_CC"
+-fi
+-
+-if test -z "$CC"; then
+- if test -n "$ac_tool_prefix"; then
+- # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+-set dummy ${ac_tool_prefix}cc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_CC="${ac_tool_prefix}cc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+- fi
+-fi
+-if test -z "$CC"; then
+- # Extract the first word of "cc", so it can be a program name with args.
+-set dummy cc; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+- ac_prog_rejected=no
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+- ac_prog_rejected=yes
+- continue
+- fi
+- ac_cv_prog_CC="cc"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-if test $ac_prog_rejected = yes; then
+- # We found a bogon in the path, so make sure we never use it.
+- set dummy $ac_cv_prog_CC
+- shift
+- if test $# != 0; then
+- # We chose a different compiler from the bogus one.
+- # However, it has the same basename, so the bogon will be chosen
+- # first if we set CC to just the basename; use the full file name.
+- shift
+- ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+- fi
+-fi
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+-fi
+-if test -z "$CC"; then
+- if test -n "$ac_tool_prefix"; then
+- for ac_prog in cl.exe
+- do
+- # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+-set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_CC+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- if test -n "$CC"; then
+- ac_cv_prog_CC="$CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
+-
+-fi
+-fi
+-CC=$ac_cv_prog_CC
+-if test -n "$CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+-$as_echo "$CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
+-
+-
+- test -n "$CC" && break
+- done
+-fi
+-if test -z "$CC"; then
+- ac_ct_CC=$CC
+- for ac_prog in cl.exe
+-do
+- # Extract the first word of "$ac_prog", so it can be a program name with args.
+-set dummy $ac_prog; ac_word=$2
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+-$as_echo_n "checking for $ac_word... " >&6; }
+-if ${ac_cv_prog_ac_ct_CC+:} false; then :
++ case $ac_cv_prog_cc_stdc in #(
++ no) :
++ ac_cv_prog_cc_c99=no; ac_cv_prog_cc_c89=no ;; #(
++ *) :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5
++$as_echo_n "checking for $CC option to accept ISO C99... " >&6; }
++if ${ac_cv_prog_cc_c99+:} false; then :
+ $as_echo_n "(cached) " >&6
+ else
+- if test -n "$ac_ct_CC"; then
+- ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+-else
+-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+-for as_dir in $PATH
+-do
+- IFS=$as_save_IFS
+- test -z "$as_dir" && as_dir=.
+- for ac_exec_ext in '' $ac_executable_extensions; do
+- if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+- ac_cv_prog_ac_ct_CC="$ac_prog"
+- $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+- break 2
+- fi
+-done
+- done
+-IFS=$as_save_IFS
++ ac_cv_prog_cc_c99=no
++ac_save_CC=$CC
++cat confdefs.h - <<_ACEOF >conftest.$ac_ext
++/* end confdefs.h. */
++#include <stdarg.h>
++#include <stdbool.h>
++#include <stdlib.h>
++#include <wchar.h>
++#include <stdio.h>
+
+-fi
+-fi
+-ac_ct_CC=$ac_cv_prog_ac_ct_CC
+-if test -n "$ac_ct_CC"; then
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+-$as_echo "$ac_ct_CC" >&6; }
+-else
+- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+-$as_echo "no" >&6; }
+-fi
++// Check varargs macros. These examples are taken from C99 6.10.3.5.
++#define debug(...) fprintf (stderr, __VA_ARGS__)
++#define showlist(...) puts (#__VA_ARGS__)
++#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
++static void
++test_varargs_macros (void)
++{
++ int x = 1234;
++ int y = 5678;
++ debug ("Flag");
++ debug ("X = %d\n", x);
++ showlist (The first, second, and third items.);
++ report (x>y, "x is %d but y is %d", x, y);
++}
+
++// Check long long types.
++#define BIG64 18446744073709551615ull
++#define BIG32 4294967295ul
++#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
++#if !BIG_OK
++ your preprocessor is broken;
++#endif
++#if BIG_OK
++#else
++ your preprocessor is broken;
++#endif
++static long long int bignum = -9223372036854775807LL;
++static unsigned long long int ubignum = BIG64;
+
+- test -n "$ac_ct_CC" && break
+-done
++struct incomplete_array
++{
++ int datasize;
++ double data[];
++};
+
+- if test "x$ac_ct_CC" = x; then
+- CC=""
+- else
+- case $cross_compiling:$ac_tool_warned in
+-yes:)
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+-ac_tool_warned=yes ;;
+-esac
+- CC=$ac_ct_CC
+- fi
+-fi
++struct named_init {
++ int number;
++ const wchar_t *name;
++ double average;
++};
+
+-fi
++typedef const char *ccp;
+
++static inline int
++test_restrict (ccp restrict text)
++{
++ // See if C++-style comments work.
++ // Iterate through items via the restricted pointer.
++ // Also check for declarations in for loops.
++ for (unsigned int i = 0; *(text+i) != '\0'; ++i)
++ continue;
++ return 0;
++}
+
+-test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "no acceptable C compiler found in \$PATH
+-See \`config.log' for more details" "$LINENO" 5; }
++// Check varargs and va_copy.
++static void
++test_varargs (const char *format, ...)
++{
++ va_list args;
++ va_start (args, format);
++ va_list args_copy;
++ va_copy (args_copy, args);
+
+-# Provide some information about the compiler.
+-$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+-set X $ac_compile
+-ac_compiler=$2
+-for ac_option in --version -v -V -qversion; do
+- { { ac_try="$ac_compiler $ac_option >&5"
+-case "(($ac_try" in
+- *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+- *) ac_try_echo=$ac_try;;
+-esac
+-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+-$as_echo "$ac_try_echo"; } >&5
+- (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+- ac_status=$?
+- if test -s conftest.err; then
+- sed '10a\
+-... rest of stderr output deleted ...
+- 10q' conftest.err >conftest.er1
+- cat conftest.er1 >&5
+- fi
+- rm -f conftest.er1 conftest.err
+- $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+- test $ac_status = 0; }
+-done
++ const char *str;
++ int number;
++ float fnumber;
+
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+-$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+-if ${ac_cv_c_compiler_gnu+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ while (*format)
++ {
++ switch (*format++)
++ {
++ case 's': // string
++ str = va_arg (args_copy, const char *);
++ break;
++ case 'd': // int
++ number = va_arg (args_copy, int);
++ break;
++ case 'f': // float
++ fnumber = va_arg (args_copy, double);
++ break;
++ default:
++ break;
++ }
++ }
++ va_end (args_copy);
++ va_end (args);
++}
+
+ int
+ main ()
+ {
+-#ifndef __GNUC__
+- choke me
+-#endif
+
+- ;
+- return 0;
+-}
+-_ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
+- ac_compiler_gnu=yes
+-else
+- ac_compiler_gnu=no
+-fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+-ac_cv_c_compiler_gnu=$ac_compiler_gnu
++ // Check bool.
++ _Bool success = false;
+
+-fi
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+-$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+-if test $ac_compiler_gnu = yes; then
+- GCC=yes
+-else
+- GCC=
+-fi
+-ac_test_CFLAGS=${CFLAGS+set}
+-ac_save_CFLAGS=$CFLAGS
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+-$as_echo_n "checking whether $CC accepts -g... " >&6; }
+-if ${ac_cv_prog_cc_g+:} false; then :
+- $as_echo_n "(cached) " >&6
+-else
+- ac_save_c_werror_flag=$ac_c_werror_flag
+- ac_c_werror_flag=yes
+- ac_cv_prog_cc_g=no
+- CFLAGS="-g"
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ // Check restrict.
++ if (test_restrict ("String literal") == 0)
++ success = true;
++ char *restrict newvar = "Another string";
+
+-int
+-main ()
+-{
++ // Check varargs.
++ test_varargs ("s, d' f .", "string", 65, 34.234);
++ test_varargs_macros ();
+
+- ;
+- return 0;
+-}
+-_ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
+- ac_cv_prog_cc_g=yes
+-else
+- CFLAGS=""
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ // Check flexible array members.
++ struct incomplete_array *ia =
++ malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
++ ia->datasize = 10;
++ for (int i = 0; i < ia->datasize; ++i)
++ ia->data[i] = i * 1.234;
+
+-int
+-main ()
+-{
++ // Check named initializers.
++ struct named_init ni = {
++ .number = 34,
++ .name = L"Test wide string",
++ .average = 543.34343,
++ };
+
+- ;
+- return 0;
+-}
+-_ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
++ ni.number = 58;
+
+-else
+- ac_c_werror_flag=$ac_save_c_werror_flag
+- CFLAGS="-g"
+- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+-/* end confdefs.h. */
++ int dynamic_array[ni.number];
++ dynamic_array[ni.number - 1] = 543;
+
+-int
+-main ()
+-{
++ // work around unused variable warnings
++ return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x'
++ || dynamic_array[ni.number - 1] != 543);
+
+ ;
+ return 0;
+ }
+ _ACEOF
+-if ac_fn_c_try_compile "$LINENO"; then :
+- ac_cv_prog_cc_g=yes
+-fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+-fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
++for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99
++do
++ CC="$ac_save_CC $ac_arg"
++ if ac_fn_c_try_compile "$LINENO"; then :
++ ac_cv_prog_cc_c99=$ac_arg
+ fi
+-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+- ac_c_werror_flag=$ac_save_c_werror_flag
++rm -f core conftest.err conftest.$ac_objext
++ test "x$ac_cv_prog_cc_c99" != "xno" && break
++done
++rm -f conftest.$ac_ext
++CC=$ac_save_CC
++
+ fi
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+-$as_echo "$ac_cv_prog_cc_g" >&6; }
+-if test "$ac_test_CFLAGS" = set; then
+- CFLAGS=$ac_save_CFLAGS
+-elif test $ac_cv_prog_cc_g = yes; then
+- if test "$GCC" = yes; then
+- CFLAGS="-g -O2"
+- else
+- CFLAGS="-g"
+- fi
++# AC_CACHE_VAL
++case "x$ac_cv_prog_cc_c99" in
++ x)
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
++$as_echo "none needed" >&6; } ;;
++ xno)
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
++$as_echo "unsupported" >&6; } ;;
++ *)
++ CC="$CC $ac_cv_prog_cc_c99"
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
++$as_echo "$ac_cv_prog_cc_c99" >&6; } ;;
++esac
++if test "x$ac_cv_prog_cc_c99" != xno; then :
++ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99
+ else
+- if test "$GCC" = yes; then
+- CFLAGS="-O2"
+- else
+- CFLAGS=
+- fi
+-fi
+-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+ $as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+ if ${ac_cv_prog_cc_c89+:} false; then :
+ $as_echo_n "(cached) " >&6
+@@ -5133,19 +4872,36 @@
+ $as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+ esac
+ if test "x$ac_cv_prog_cc_c89" != xno; then :
++ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89
++else
++ ac_cv_prog_cc_stdc=no
++fi
+
++fi
++ ;;
++esac
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO Standard C" >&5
++$as_echo_n "checking for $CC option to accept ISO Standard C... " >&6; }
++ if ${ac_cv_prog_cc_stdc+:} false; then :
++ $as_echo_n "(cached) " >&6
+ fi
+
+-ac_ext=c
+-ac_cpp='$CPP $CPPFLAGS'
+-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+-ac_compiler_gnu=$ac_cv_c_compiler_gnu
++ case $ac_cv_prog_cc_stdc in #(
++ no) :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
++$as_echo "unsupported" >&6; } ;; #(
++ '') :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
++$as_echo "none needed" >&6; } ;; #(
++ *) :
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_stdc" >&5
++$as_echo "$ac_cv_prog_cc_stdc" >&6; } ;;
++esac
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking $CC dependency flag" >&5
+ $as_echo_n "checking $CC dependency flag... " >&6; }
+-echo 'void f(){}' >conftest.c
++echo 'void f(void){}' >conftest.c
+ if test "`$CC -MM conftest.c 2>&1`" = "conftest.o: conftest.c"; then
+ DEPFLAG="-MM"
+ else
+@@ -5327,7 +5083,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -5348,7 +5104,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5418,7 +5174,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -5439,7 +5195,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5498,12 +5254,12 @@
+ echo '
+ #include <stdbool.h>
+ #include <ctype.h>
+-int test() {
++int test(void) {
+ int a = 0;
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5562,13 +5318,13 @@
+ echo '
+ #include <ctype.h>
+
+-int test() {
++int test(void) {
+ int a;
+ a = isascii(32);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5627,13 +5383,13 @@
+ echo '
+ #include <netinet/in.h>
+
+-int test() {
++int test(void) {
+ struct in6_pktinfo inf;
+ int a = (int)sizeof(inf);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5695,13 +5451,13 @@
+ echo '
+ #include <unistd.h>
+
+-int test() {
++int test(void) {
+ int a = setresgid(0,0,0);
+ a = setresuid(0,0,0);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5764,7 +5520,7 @@
+ #endif
+ #include <netdb.h>
+
+-int test() {
++int test(void) {
+ int a = 0;
+ char *t;
+ time_t time = 0;
+@@ -5777,7 +5533,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -5846,7 +5602,7 @@
+ #include <getopt.h>
+ #endif
+
+-int test() {
++int test(void) {
+ int a;
+ char **opts = NULL;
+ struct timeval tv;
+@@ -5859,7 +5615,7 @@
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -6610,6 +6366,11 @@
+ fi
+
+ fi
++if test "$LEX" = "" -o "$LEX" = ":"; then
++ if test ! -f util/configlexer.c; then
++ as_fn_error $? "no lex and no util/configlexer.c: need flex and bison to compile from source repository." "$LINENO" 5
++ fi
++fi
+ for ac_prog in 'bison -y' byacc
+ do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+@@ -6653,6 +6414,11 @@
+ done
+ test -n "$YACC" || YACC="yacc"
+
++if test "$YACC" = "" -o "$YACC" = ":"; then
++ if test ! -f util/configparser.c; then
++ as_fn_error $? "no yacc and no util/configparser.c: need flex and bison to compile from source repository." "$LINENO" 5
++ fi
++fi
+ # Extract the first word of "doxygen", so it can be a program name with args.
+ set dummy doxygen; ac_word=$2
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+@@ -14877,6 +14643,21 @@
+ done
+
+
++# Check for Linux timestamping headers
++for ac_header in linux/net_tstamp.h
++do :
++ ac_fn_c_check_header_compile "$LINENO" "linux/net_tstamp.h" "ac_cv_header_linux_net_tstamp_h" "$ac_includes_default
++"
++if test "x$ac_cv_header_linux_net_tstamp_h" = xyes; then :
++ cat >>confdefs.h <<_ACEOF
++#define HAVE_LINUX_NET_TSTAMP_H 1
++_ACEOF
++
++fi
++
++done
++
++
+ # check for types.
+ # Using own tests for int64* because autoconf builtin only give 32bit.
+ ac_fn_c_check_type "$LINENO" "int8_t" "ac_cv_type_int8_t" "$ac_includes_default"
+@@ -15954,12 +15735,12 @@
+
+ echo '
+ #include <stdio.h>
+-int test() {
++int test(void) {
+ int a = fseeko(stdin, 0, 0);
+ return a;
+ }
+ ' > conftest.c
+-echo 'void f(){}' >>conftest.c
++echo 'void f(void){}' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS $ERRFLAG -c conftest.c 2>&1`"; then
+ eval "cv_prog_cc_flag_needed_$cache=no"
+ else
+@@ -16221,10 +16002,7 @@
+ $as_echo_n "checking whether strptime works... " >&6; }
+ if test c${cross_compiling} = cno; then
+ if test "$cross_compiling" = yes; then :
+- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "cannot run test program while cross compiling
+-See \`config.log' for more details" "$LINENO" 5; }
++ eval "ac_cv_c_strptime_works=maybe"
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ /* end confdefs.h. */
+@@ -17541,39 +17319,68 @@
+ PYTHON_VERSION=`$PYTHON -c "import sys; \
+ print(sys.version.split()[0])"`
+ fi
++ # calculate the version number components.
++
++ v="$PYTHON_VERSION"
++ PYTHON_VERSION_MAJOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MAJOR"; then PYTHON_VERSION_MAJOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_MINOR=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_MINOR"; then PYTHON_VERSION_MINOR="0"; fi
++ v=`echo $v | sed -e 's/^[0-9]*$//' -e 's/[0-9]*[^0-9]//'`
++ PYTHON_VERSION_PATCH=`echo $v | sed 's/[^0-9].*//'`
++ if test -z "$PYTHON_VERSION_PATCH"; then PYTHON_VERSION_PATCH="0"; fi
++
++
++ # For some systems, sysconfig exists, but has the wrong paths,
++ # on Debian 10, for python 2.7 and 3.7. So, we check the version,
++ # and for older versions try distutils.sysconfig first. For newer
++ # versions>=3.10, where distutils.sysconfig is deprecated, use
++ # sysconfig first and then attempt the other one.
++ py_distutils_first="no"
++ if test $PYTHON_VERSION_MAJOR -lt 3; then
++ py_distutils_first="yes"
++ fi
++ if test $PYTHON_VERSION_MAJOR -eq 3 -a $PYTHON_VERSION_MINOR -lt 10; then
++ py_distutils_first="yes"
++ fi
+
+- # Check if you have sysconfig
+- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the sysconfig Python module" >&5
+-$as_echo_n "checking for the sysconfig Python module... " >&6; }
+- if ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`; then
++ # Check if you have the first module
++ if test "$py_distutils_first" = "yes"; then m="distutils"; else m="sysconfig"; fi
++ sysconfig_module=""
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the $m Python module" >&5
++$as_echo_n "checking for the $m Python module... " >&6; }
++ if ac_modulecheck_result1=`$PYTHON -c "import $m" 2>&1`; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+ $as_echo "yes" >&6; }
+- sysconfig_module="sysconfig"
+- # if yes, use sysconfig, because distutils is deprecated.
++ sysconfig_module="$m"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+ $as_echo "no" >&6; }
+- # if no, try to use distutils
+-
+- #
+- # Check if you have distutils, else fail
+- #
+- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the distutils Python package" >&5
+-$as_echo_n "checking for the distutils Python package... " >&6; }
+- if ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`; then
++ fi
++
++ # if not found, try the other one.
++ if test -z "$sysconfig_module"; then
++ if test "$py_distutils_first" = "yes"; then m2="sysconfig"; else m2="distutils"; fi
++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the $m2 Python module" >&5
++$as_echo_n "checking for the $m2 Python module... " >&6; }
++ if ac_modulecheck_result2=`$PYTHON -c "import $m2" 2>&1`; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+ $as_echo "yes" >&6; }
++ sysconfig_module="$m2"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+ $as_echo "no" >&6; }
+- as_fn_error $? "cannot import Python module \"distutils\".
+- Please check your Python installation. The error was:
+- $ac_distutils_result" "$LINENO" 5
++ as_fn_error $? "cannot import Python module \"$m\", or \"$m2\".
++ Please check your Python installation. The errors are:
++ $m
++ $ac_modulecheck_result1
++ $m2
++ $ac_modulecheck_result2" "$LINENO" 5
+ PYTHON_VERSION=""
+ fi
+-
+- sysconfig_module="distutils.sysconfig"
+ fi
++ if test "$sysconfig_module" = "distutils"; then sysconfig_module="distutils.sysconfig"; fi
+
+ #
+ # Check for Python include path
+@@ -17705,7 +17512,14 @@
+ #
+
+ if test ! -z "$PYTHON_VERSION"; then
+- if test `$PYTHON -c "print('$PYTHON_VERSION' >= '2.4.0')"` = "False"; then
++ badversion="no"
++ if test "$PYTHON_VERSION_MAJOR" -lt 2; then
++ badversion="yes"
++ fi
++ if test "$PYTHON_VERSION_MAJOR" -eq 2 -a "$PYTHON_VERSION_MINOR" -lt 4; then
++ badversion="yes"
++ fi
++ if test "$badversion" = "yes"; then
+ as_fn_error $? "Python version >= 2.4.0 is required" "$LINENO" 5
+ fi
+
+@@ -18977,10 +18791,7 @@
+ CFLAGS="$CFLAGS -Wl,-rpath,$ssldir_lib"
+ fi
+ if test "$cross_compiling" = yes; then :
+- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "cannot run test program while cross compiling
+-See \`config.log' for more details" "$LINENO" 5; }
++ eval "ac_cv_c_gost_works=maybe"
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ /* end confdefs.h. */
+@@ -20174,7 +19985,7 @@
+ #ifdef __cplusplus
+ }
+ #endif
+-int main() {
++int main(void) {
+ ;
+ return 0;
+ }
+@@ -20448,7 +20259,7 @@
+ #include <stdlib.h>
+ #include <unistd.h>
+ ' >conftest.c
+-echo 'void f(){ (void)daemon(0, 0); }' >>conftest.c
++echo 'void f(void){ (void)daemon(0, 0); }' >>conftest.c
+ if test -z "`$CC $CPPFLAGS $CFLAGS -c conftest.c 2>&1 | grep -e deprecated -e unavailable`"; then
+ eval "cv_cc_deprecated_$cache=no"
+ else
+@@ -20854,10 +20665,8 @@
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for correct snprintf return value" >&5
+ $as_echo_n "checking for correct snprintf return value... " >&6; }
+ if test "$cross_compiling" = yes; then :
+- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+-as_fn_error $? "cannot run test program while cross compiling
+-See \`config.log' for more details" "$LINENO" 5; }
++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: maybe" >&5
++$as_echo "maybe" >&6; }
+ else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ /* end confdefs.h. */
+@@ -21532,7 +21341,7 @@
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+ else
+- as_fn_error $? "The protobuf-c library was not found. Please install protobuf-c!" "$LINENO" 5
++ as_fn_error $? "The protobuf-c library was not found. Please install the development libraries for protobuf-c!" "$LINENO" 5
+ fi
+
+
+@@ -22086,7 +21895,7 @@
+
+
+
+-version=1.17.1
++version=1.19.1
+
+ date=`date +'%b %e, %Y'`
+
+@@ -22605,7 +22414,7 @@
+ # report actual input values of CONFIG_FILES etc. instead of their
+ # values after options handling.
+ ac_log="
+-This file was extended by unbound $as_me 1.17.1, which was
++This file was extended by unbound $as_me 1.19.1, which was
+ generated by GNU Autoconf 2.69. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+@@ -22671,7 +22480,7 @@
+ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ ac_cs_version="\\
+-unbound config.status 1.17.1
++unbound config.status 1.19.1
+ configured by $0, generated by GNU Autoconf 2.69,
+ with options \\"\$ac_cs_config\\"
+
+--- contrib/unbound/configure.ac.orig
++++ contrib/unbound/configure.ac
+@@ -10,7 +10,7 @@
+
+ # must be numbers. ac_defun because of later processing
+ m4_define([VERSION_MAJOR],[1])
+-m4_define([VERSION_MINOR],[17])
++m4_define([VERSION_MINOR],[19])
+ m4_define([VERSION_MICRO],[1])
+ AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound])
+ AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR])
+@@ -18,7 +18,7 @@
+ AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO])
+
+ LIBUNBOUND_CURRENT=9
+-LIBUNBOUND_REVISION=21
++LIBUNBOUND_REVISION=24
+ LIBUNBOUND_AGE=1
+ # 1.0.0 had 0:12:0
+ # 1.0.1 had 0:13:0
+@@ -107,6 +107,9 @@
+ # 1.16.3 had 9:19:1
+ # 1.17.0 had 9:20:1
+ # 1.17.1 had 9:21:1
++# 1.18.0 had 9:22:1
++# 1.19.0 had 9:23:1
++# 1.19.1 had 9:24:1
+
+ # Current -- the number of the binary API that we're implementing
+ # Revision -- which iteration of the implementation of the binary
+@@ -156,7 +159,7 @@
+
+ # are we on MinGW?
+ if uname -s 2>&1 | grep MINGW >/dev/null; then on_mingw="yes"
+-else
++else
+ if echo $host | grep mingw >/dev/null; then on_mingw="yes"
+ else on_mingw="no"; fi
+ fi
+@@ -185,9 +188,9 @@
+ AC_SUBST(ub_conf_dir)
+
+ # Determine run, chroot directory and pidfile locations
+-AC_ARG_WITH(run-dir,
+- AS_HELP_STRING([--with-run-dir=path],[set default directory to chdir to (by default dir part of cfg file)]),
+- UNBOUND_RUN_DIR="$withval",
++AC_ARG_WITH(run-dir,
++ AS_HELP_STRING([--with-run-dir=path],[set default directory to chdir to (by default dir part of cfg file)]),
++ UNBOUND_RUN_DIR="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_RUN_DIR=`dirname "$ub_conf_file"`
+ else
+@@ -198,9 +201,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_RUN_DIR, hdr_run)
+ AC_DEFINE_UNQUOTED(RUN_DIR, ["$hdr_run"], [Directory to chdir to])
+
+-AC_ARG_WITH(chroot-dir,
+- AS_HELP_STRING([--with-chroot-dir=path],[set default directory to chroot to (by default same as run-dir)]),
+- UNBOUND_CHROOT_DIR="$withval",
++AC_ARG_WITH(chroot-dir,
++ AS_HELP_STRING([--with-chroot-dir=path],[set default directory to chroot to (by default same as run-dir)]),
++ UNBOUND_CHROOT_DIR="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_CHROOT_DIR="$UNBOUND_RUN_DIR"
+ else
+@@ -218,9 +221,9 @@
+ AC_SUBST(UNBOUND_SHARE_DIR)
+ AC_DEFINE_UNQUOTED(SHARE_DIR, ["$UNBOUND_SHARE_DIR"], [Shared data])
+
+-AC_ARG_WITH(pidfile,
+- AS_HELP_STRING([--with-pidfile=filename],[set default pathname to unbound pidfile (default run-dir/unbound.pid)]),
+- UNBOUND_PIDFILE="$withval",
++AC_ARG_WITH(pidfile,
++ AS_HELP_STRING([--with-pidfile=filename],[set default pathname to unbound pidfile (default run-dir/unbound.pid)]),
++ UNBOUND_PIDFILE="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_PIDFILE="$UNBOUND_RUN_DIR/unbound.pid"
+ else
+@@ -231,9 +234,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_PIDFILE, hdr_pid)
+ AC_DEFINE_UNQUOTED(PIDFILE, ["$hdr_pid"], [default pidfile location])
+
+-AC_ARG_WITH(rootkey-file,
+- AS_HELP_STRING([--with-rootkey-file=filename],[set default pathname to root key file (default run-dir/root.key). This file is read and written.]),
+- UNBOUND_ROOTKEY_FILE="$withval",
++AC_ARG_WITH(rootkey-file,
++ AS_HELP_STRING([--with-rootkey-file=filename],[set default pathname to root key file (default run-dir/root.key). This file is read and written.]),
++ UNBOUND_ROOTKEY_FILE="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_ROOTKEY_FILE="$UNBOUND_RUN_DIR/root.key"
+ else
+@@ -244,9 +247,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_ROOTKEY_FILE, hdr_rkey)
+ AC_DEFINE_UNQUOTED(ROOT_ANCHOR_FILE, ["$hdr_rkey"], [default rootkey location])
+
+-AC_ARG_WITH(rootcert-file,
+- AS_HELP_STRING([--with-rootcert-file=filename],[set default pathname to root update certificate file (default run-dir/icannbundle.pem). This file need not exist if you are content with the builtin.]),
+- UNBOUND_ROOTCERT_FILE="$withval",
++AC_ARG_WITH(rootcert-file,
++ AS_HELP_STRING([--with-rootcert-file=filename],[set default pathname to root update certificate file (default run-dir/icannbundle.pem). This file need not exist if you are content with the builtin.]),
++ UNBOUND_ROOTCERT_FILE="$withval",
+ if test $on_mingw = no; then
+ UNBOUND_ROOTCERT_FILE="$UNBOUND_RUN_DIR/icannbundle.pem"
+ else
+@@ -257,9 +260,9 @@
+ ACX_ESCAPE_BACKSLASH($UNBOUND_ROOTCERT_FILE, hdr_rpem)
+ AC_DEFINE_UNQUOTED(ROOT_CERT_FILE, ["$hdr_rpem"], [default rootcert location])
+
+-AC_ARG_WITH(username,
+- AS_HELP_STRING([--with-username=user],[set default user that unbound changes to (default user is unbound)]),
+- UNBOUND_USERNAME="$withval",
++AC_ARG_WITH(username,
++ AS_HELP_STRING([--with-username=user],[set default user that unbound changes to (default user is unbound)]),
++ UNBOUND_USERNAME="$withval",
+ UNBOUND_USERNAME="unbound")
+ AC_SUBST(UNBOUND_USERNAME)
+ AC_DEFINE_UNQUOTED(UB_USERNAME, ["$UNBOUND_USERNAME"], [default username])
+@@ -278,14 +281,14 @@
+ ACX_CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"])
+ default_cflags=yes
+ fi
+-AC_PROG_CC
++m4_version_prereq([2.70], [AC_PROG_CC], [AC_PROG_CC_STDC])
+ ACX_DEPFLAG
+ ACX_DETERMINE_EXT_FLAGS_UNBOUND
+
+ # debug mode flags warnings
+ AC_ARG_ENABLE(checking, AS_HELP_STRING([--enable-checking],[Enable warnings, asserts, makefile-dependencies]))
+ AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[same as enable-checking]))
+-if test "$enable_debug" = "yes"; then debug_enabled="$enable_debug";
++if test "$enable_debug" = "yes"; then debug_enabled="$enable_debug";
+ else debug_enabled="$enable_checking"; fi
+ AC_SUBST(debug_enabled)
+ case "$debug_enabled" in
+@@ -388,7 +391,17 @@
+ if test "$LEX" != "" -a "$LEX" != ":"; then
+ ACX_YYLEX_OPTION
+ fi
++if test "$LEX" = "" -o "$LEX" = ":"; then
++ if test ! -f util/configlexer.c; then
++ AC_MSG_ERROR([no lex and no util/configlexer.c: need flex and bison to compile from source repository.])
++ fi
++fi
+ AC_PROG_YACC
++if test "$YACC" = "" -o "$YACC" = ":"; then
++ if test ! -f util/configparser.c; then
++ AC_MSG_ERROR([no yacc and no util/configparser.c: need flex and bison to compile from source repository.])
++ fi
++fi
+ AC_CHECK_PROG(doxygen, doxygen, doxygen)
+ AC_CHECK_TOOL(STRIP, strip)
+ ACX_LIBTOOL_C_ONLY
+@@ -453,6 +466,9 @@
+ #endif
+ ])
+
++# Check for Linux timestamping headers
++AC_CHECK_HEADERS([linux/net_tstamp.h],,, [AC_INCLUDES_DEFAULT])
++
+ # check for types.
+ # Using own tests for int64* because autoconf builtin only give 32bit.
+ AC_CHECK_TYPE(int8_t, signed char)
+@@ -511,7 +527,8 @@
+ if (!res) return 2;
+ res = strptime("20070207111842", "%Y%m%d%H%M%S", &tm);
+ if (!res) return 1; return 0; }
+-]])] , [eval "ac_cv_c_strptime_works=yes"], [eval "ac_cv_c_strptime_works=no"])
++]])] , [eval "ac_cv_c_strptime_works=yes"], [eval "ac_cv_c_strptime_works=no"],
++[eval "ac_cv_c_strptime_works=maybe"])
+ else
+ eval "ac_cv_c_strptime_works=maybe"
+ fi
+@@ -548,11 +565,11 @@
+ # Include systemd.m4 - end
+
+ # set memory allocation checking if requested
+-AC_ARG_ENABLE(alloc-checks, AS_HELP_STRING([--enable-alloc-checks],[ enable to memory allocation statistics, for debug purposes ]),
++AC_ARG_ENABLE(alloc-checks, AS_HELP_STRING([--enable-alloc-checks],[ enable to memory allocation statistics, for debug purposes ]),
+ , )
+-AC_ARG_ENABLE(alloc-lite, AS_HELP_STRING([--enable-alloc-lite],[ enable for lightweight alloc assertions, for debug purposes ]),
++AC_ARG_ENABLE(alloc-lite, AS_HELP_STRING([--enable-alloc-lite],[ enable for lightweight alloc assertions, for debug purposes ]),
+ , )
+-AC_ARG_ENABLE(alloc-nonregional, AS_HELP_STRING([--enable-alloc-nonregional],[ enable nonregional allocs, slow but exposes regional allocations to other memory purifiers, for debug purposes ]),
++AC_ARG_ENABLE(alloc-nonregional, AS_HELP_STRING([--enable-alloc-nonregional],[ enable nonregional allocs, slow but exposes regional allocations to other memory purifiers, for debug purposes ]),
+ , )
+ if test x_$enable_alloc_nonregional = x_yes; then
+ AC_DEFINE(UNBOUND_ALLOC_NONREGIONAL, 1, [use malloc not regions, for debug use])
+@@ -585,7 +602,7 @@
+ ])],
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_WINDOWS_THREADS, 1, [Using Windows threads])
+-,
++,
+ AC_MSG_RESULT(no)
+ )
+
+@@ -596,7 +613,7 @@
+ # check this first, so that the pthread lib does not get linked in via
+ # libssl or libpython, and thus distorts the tests, and we end up using
+ # the non-threadsafe C libraries.
+-AC_ARG_WITH(pthreads, AS_HELP_STRING([--with-pthreads],[use pthreads library, or --without-pthreads to disable threading support.]),
++AC_ARG_WITH(pthreads, AS_HELP_STRING([--with-pthreads],[use pthreads library, or --without-pthreads to disable threading support.]),
+ [ ],[ withval="yes" ])
+ ub_have_pthreads=no
+ if test x_$withval != x_no; then
+@@ -623,7 +640,7 @@
+ # first compile
+ echo "$CC $CFLAGS -c conftest.c -o conftest.o" >&AS_MESSAGE_LOG_FD
+ $CC $CFLAGS -c conftest.c -o conftest.o 2>&AS_MESSAGE_LOG_FD >&AS_MESSAGE_LOG_FD
+- if test $? = 0; then
++ if test $? = 0; then
+ # then link
+ echo "$CC $CFLAGS -Werror $LDFLAGS $LIBS -o conftest contest.o" >&AS_MESSAGE_LOG_FD
+ $CC $CFLAGS -Werror $LDFLAGS $LIBS -o conftest conftest.o 2>&AS_MESSAGE_LOG_FD >&AS_MESSAGE_LOG_FD
+@@ -644,7 +661,7 @@
+ ])
+ fi
+
+-# check solaris thread library
++# check solaris thread library
+ AC_ARG_WITH(solaris-threads, AS_HELP_STRING([--with-solaris-threads],[use solaris native thread library.]), [ ],[ withval="no" ])
+ ub_have_sol_threads=no
+ if test x_$withval != x_no; then
+@@ -658,8 +675,8 @@
+ ACX_CHECK_COMPILER_FLAG(mt, [CFLAGS="$CFLAGS -mt"],
+ [CFLAGS="$CFLAGS -D_REENTRANT"])
+ ub_have_sol_threads=yes
+- ] , [
+- AC_MSG_ERROR([no solaris threads found.])
++ ] , [
++ AC_MSG_ERROR([no solaris threads found.])
+ ])
+ fi
+ fi
+@@ -734,7 +751,14 @@
+ ac_save_LIBS="$LIBS" dnl otherwise AC_PYTHON_DEVEL thrashes $LIBS
+ AC_PYTHON_DEVEL
+ if test ! -z "$PYTHON_VERSION"; then
+- if test `$PYTHON -c "print('$PYTHON_VERSION' >= '2.4.0')"` = "False"; then
++ badversion="no"
++ if test "$PYTHON_VERSION_MAJOR" -lt 2; then
++ badversion="yes"
++ fi
++ if test "$PYTHON_VERSION_MAJOR" -eq 2 -a "$PYTHON_VERSION_MINOR" -lt 4; then
++ badversion="yes"
++ fi
++ if test "$badversion" = "yes"; then
+ AC_MSG_ERROR([Python version >= 2.4.0 is required])
+ fi
+
+@@ -1085,7 +1109,7 @@
+ EVP_PKEY_asn1_get0_info(&gost_id, NULL, NULL, NULL, NULL, meth);
+ return gost_id;
+ }
+-int main(void) {
++int main(void) {
+ EVP_MD_CTX* ctx;
+ const EVP_MD* md;
+ unsigned char digest[64]; /* its a 256-bit digest, so uses 32 bytes */
+@@ -1116,7 +1140,8 @@
+ return 6;
+ return 0;
+ }
+-]])] , [eval "ac_cv_c_gost_works=yes"], [eval "ac_cv_c_gost_works=no"])
++]])] , [eval "ac_cv_c_gost_works=yes"], [eval "ac_cv_c_gost_works=no"],
++[eval "ac_cv_c_gost_works=maybe"])
+ CFLAGS="$BAKCFLAGS"
+ else
+ eval "ac_cv_c_gost_works=maybe"
+@@ -1529,7 +1554,7 @@
+ fi
+
+ # set lock checking if requested
+-AC_ARG_ENABLE(lock_checks, AS_HELP_STRING([--enable-lock-checks],[ enable to check lock and unlock calls, for debug purposes ]),
++AC_ARG_ENABLE(lock_checks, AS_HELP_STRING([--enable-lock-checks],[ enable to check lock and unlock calls, for debug purposes ]),
+ , )
+ if test x_$enable_lock_checks = x_yes; then
+ AC_DEFINE(ENABLE_LOCK_CHECKS, 1, [Define if you want to use debug lock checking (slow).])
+@@ -1693,7 +1718,7 @@
+ AC_MSG_RESULT(no)
+ AC_DEFINE([SNPRINTF_RET_BROKEN], [], [define if (v)snprintf does not return length needed, (but length used)])
+ AC_LIBOBJ(snprintf)
+- ])
++ ], [AC_MSG_RESULT(maybe)])
+ fi
+ fi
+ AC_REPLACE_FUNCS(strlcat)
+@@ -1923,7 +1948,7 @@
+ esac
+
+ if echo "$host" | $GREP -i -e linux >/dev/null; then
+- AC_ARG_ENABLE(linux-ip-local-port-range, AC_HELP_STRING([--enable-linux-ip-local-port-range], [Define this to enable use of /proc/sys/net/ipv4/ip_local_port_range as a default outgoing port range. This is only for the libunbound on Linux and does not affect unbound resolving daemon itself. This may severely limit the number of available outgoing ports and thus decrease randomness. Define this only when the target system restricts (e.g. some of SELinux enabled distributions) the use of non-ephemeral ports.]))
++ AC_ARG_ENABLE(linux-ip-local-port-range, AS_HELP_STRING([--enable-linux-ip-local-port-range], [Define this to enable use of /proc/sys/net/ipv4/ip_local_port_range as a default outgoing port range. This is only for the libunbound on Linux and does not affect unbound resolving daemon itself. This may severely limit the number of available outgoing ports and thus decrease randomness. Define this only when the target system restricts (e.g. some of SELinux enabled distributions) the use of non-ephemeral ports.]))
+ case "$enable_linux_ip_local_port_range" in
+ yes)
+ AC_DEFINE([USE_LINUX_IP_LOCAL_PORT_RANGE], [1], [Define this to enable use of /proc/sys/net/ipv4/ip_local_port_range as a default outgoing port range. This is only for the libunbound on Linux and does not affect unbound resolving daemon itself. This may severely limit the number of available outgoing ports and thus decrease randomness. Define this only when the target system restricts (e.g. some of SELinux enabled distributions) the use of non-ephemeral ports.])
+@@ -1980,11 +2005,11 @@
+ fi
+ ])
+ if test $ALLTARGET = "alltargets"; then
+- if test $USE_NSS = "yes"; then
+- AC_MSG_ERROR([--with-nss can only be used in combination with --with-libunbound-only.])
++ if test $USE_NSS = "yes"; then
++ AC_MSG_ERROR([--with-nss can only be used in combination with --with-libunbound-only.])
+ fi
+ if test $USE_NETTLE = "yes"; then
+- AC_MSG_ERROR([--with-nettle can only be used in combination with --with-libunbound-only.])
++ AC_MSG_ERROR([--with-nettle can only be used in combination with --with-libunbound-only.])
+ fi
+ fi
+
+@@ -1995,7 +2020,7 @@
+ if test -n "$LATE_LDFLAGS"; then
+ LDFLAGS="$LATE_LDFLAGS $LDFLAGS"
+ fi
+-# remove start spaces
++# remove start spaces
+ LDFLAGS=`echo "$LDFLAGS"|sed -e 's/^ *//'`
+ LIBS=`echo "$LIBS"|sed -e 's/^ *//'`
+
+--- contrib/unbound/contrib/Dockerfile.tests.orig
++++ contrib/unbound/contrib/Dockerfile.tests
+@@ -1,10 +1,8 @@
+ FROM gcc:latest
+ WORKDIR /usr/src/unbound
+-RUN apt-get update
+ # install semantic parser & lexical analyzer
+-RUN apt-get install -y bison flex
+ # install packages used in tests
+-RUN apt-get install -y ldnsutils dnsutils xxd splint doxygen netcat
++RUN apt-get update && apt-get install -y bison flex ldnsutils dnsutils xxd splint doxygen netcat-openbsd
+ # accept short rsa keys, which are used in tests
+ RUN sed -i 's/SECLEVEL=2/SECLEVEL=1/g' /usr/lib/ssl/openssl.cnf
+
+--- contrib/unbound/contrib/README.orig
++++ contrib/unbound/contrib/README
+@@ -55,3 +55,6 @@
+ contributed by Andreas Schulze.
+ * metrics.awk: awk script that can convert unbound-control stats to
+ Prometheus metrics format output.
++* unbound.init_yocto: An init script to start and stop the server. Put it
++ in /etc/init.d/unbound to use it. It is for the Yocto Project, in
++ embedded systems, contributed by beni-sandu.
+--- contrib/unbound/contrib/aaaa-filter-iterator.patch.orig
++++ contrib/unbound/contrib/aaaa-filter-iterator.patch
+@@ -105,9 +105,9 @@
+ --- a/iterator/iter_utils.c
+ +++ b/iterator/iter_utils.c
+ @@ -177,6 +177,7 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+- iter_env->supports_ipv6 = cfg->do_ip6;
+- iter_env->supports_ipv4 = cfg->do_ip4;
+ iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
++ iter_env->max_sent_count = cfg->max_sent_count;
++ iter_env->max_query_restarts = cfg->max_query_restarts;
+ + iter_env->aaaa_filter = cfg->aaaa_filter;
+ return 1;
+ }
+--- /dev/null
++++ contrib/unbound/contrib/unbound.init_yocto
+@@ -0,0 +1,139 @@
++#!/bin/sh
++#
++# unbound This shell script takes care of starting and stopping
++# unbound (DNS server).
++#
++# chkconfig: - 14 86
++# description: unbound is a Domain Name Server (DNS) \
++# that is used to resolve host names to IP addresses.
++
++### BEGIN INIT INFO
++# Provides: $named unbound
++# Required-Start: $network $local_fs
++# Required-Stop: $network $local_fs
++# Should-Start: $syslog
++# Should-Stop: $syslog
++# Short-Description: unbound recursive Domain Name Server.
++# Description: unbound is a Domain Name Server (DNS)
++# that is used to resolve host names to IP addresses.
++### END INIT INFO
++
++# Source function library.
++. /etc/init.d/functions
++
++exec="/usr/sbin/unbound"
++prog="unbound"
++config="/etc/unbound/unbound.conf"
++pidfile="/var/unbound/unbound.pid"
++rootdir="/var/unbound"
++
++[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
++
++lockfile=/var/lock/subsys/$prog
++
++start() {
++ [ -x $exec ] || exit 5
++ [ -f $config ] || exit 6
++ echo -n $"Starting $prog: "
++
++ # setup root jail
++ if [ -s /etc/localtime ]; then
++ [ -d ${rootdir}/etc ] || mkdir -p ${rootdir}/etc ;
++ if [ ! -e ${rootdir}/etc/localtime ] || ! /usr/bin/cmp -s /etc/localtime ${rootdir}/etc/localtime; then
++ cp -fp /etc/localtime ${rootdir}/etc/localtime
++ fi;
++ fi;
++ if [ -s /etc/resolv.conf ]; then
++ [ -d ${rootdir}/etc ] || mkdir -p ${rootdir}/etc ;
++ if [ ! -e ${rootdir}/etc/resolv.conf ] || ! /usr/bin/cmp -s /etc/resolv.conf ${rootdir}/etc/resolv.conf; then
++ cp -fp /etc/resolv.conf ${rootdir}/etc/resolv.conf
++ fi;
++ fi;
++ if ! egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/log' /proc/mounts; then
++ [ -d ${rootdir}/dev ] || mkdir -p ${rootdir}/dev ;
++ [ -e ${rootdir}/dev/log ] || touch ${rootdir}/dev/log
++ mount --bind -n /dev/log ${rootdir}/dev/log >/dev/null 2>&1;
++ fi;
++ if ! egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/random' /proc/mounts; then
++ [ -d ${rootdir}/dev ] || mkdir -p ${rootdir}/dev ;
++ [ -e ${rootdir}/dev/random ] || touch ${rootdir}/dev/random
++ mount --bind -n /dev/random ${rootdir}/dev/random >/dev/null 2>&1;
++ fi;
++
++ # if not running, start it up here
++ daemonize $exec
++ retval=$?
++ echo
++ [ $retval -eq 0 ] && touch $lockfile
++ return $retval
++}
++
++stop() {
++ echo -n $"Stopping $prog: "
++ # stop it here, often "killproc $prog"
++ killproc $prog
++ retval=$?
++ echo
++ [ $retval -eq 0 ] && rm -f $lockfile
++ if egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/log' /proc/mounts; then
++ umount ${rootdir}/dev/log >/dev/null 2>&1
++ fi;
++ if egrep -q '^/[^[:space:]]+[[:space:]]+'${rootdir}'/dev/random' /proc/mounts; then
++ umount ${rootdir}/dev/random >/dev/null 2>&1
++ fi;
++ return $retval
++}
++
++restart() {
++ stop
++ start
++}
++
++reload() {
++ kill -HUP `cat $pidfile`
++}
++
++force_reload() {
++ restart
++}
++
++rh_status() {
++ # run checks to determine if the service is running or use generic status
++ status $prog
++}
++
++rh_status_q() {
++ rh_status -p $pidfile >/dev/null 2>&1
++}
++
++case "$1" in
++ start)
++ rh_status_q && exit 0
++ $1
++ ;;
++ stop)
++ rh_status_q || exit 0
++ $1
++ ;;
++ restart)
++ $1
++ ;;
++ reload)
++ rh_status_q || exit 7
++ $1
++ ;;
++ force-reload)
++ force_reload
++ ;;
++ status)
++ rh_status
++ ;;
++ condrestart|try-restart)
++ rh_status_q || exit 0
++ restart
++ ;;
++ *)
++ echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
++ exit 2
++esac
++exit $?
+--- contrib/unbound/daemon/acl_list.c.orig
++++ contrib/unbound/daemon/acl_list.c
+@@ -109,6 +109,8 @@
+ *control = acl_allow_snoop;
+ else if(strcmp(str, "allow_setrd") == 0)
+ *control = acl_allow_setrd;
++ else if (strcmp(str, "allow_cookie") == 0)
++ *control = acl_allow_cookie;
+ else {
+ log_err("access control type %s unknown", str);
+ return 0;
+--- contrib/unbound/daemon/acl_list.h.orig
++++ contrib/unbound/daemon/acl_list.h
+@@ -64,8 +64,12 @@
+ acl_allow,
+ /** allow full access for all queries, recursion and cache snooping */
+ acl_allow_snoop,
+- /** allow full access for recursion queries and set RD flag regardless of request */
+- acl_allow_setrd
++ /** allow full access for recursion queries and set RD flag regardless
++ * of request */
++ acl_allow_setrd,
++ /** allow full access for recursion (+RD) queries if valid cookie
++ * present or stateful transport */
++ acl_allow_cookie
+ };
+
+ /**
+--- contrib/unbound/daemon/cachedump.c.orig
++++ contrib/unbound/daemon/cachedump.c
+@@ -166,8 +166,7 @@
+
+ /** dump message entry */
+ static int
+-dump_msg(RES* ssl, struct query_info* k, struct reply_info* d,
+- time_t now)
++dump_msg(RES* ssl, struct query_info* k, struct reply_info* d, time_t now)
+ {
+ size_t i;
+ char* nm, *tp, *cl;
+@@ -192,13 +191,15 @@
+ }
+
+ /* meta line */
+- if(!ssl_printf(ssl, "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u\n",
++ if(!ssl_printf(ssl, "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n",
+ nm, cl, tp,
+ (int)d->flags, (int)d->qdcount,
+ (long long)(d->ttl-now), (int)d->security,
+- (unsigned)d->an_numrrsets,
++ (unsigned)d->an_numrrsets,
+ (unsigned)d->ns_numrrsets,
+- (unsigned)d->ar_numrrsets)) {
++ (unsigned)d->ar_numrrsets,
++ (int)d->reason_bogus,
++ d->reason_bogus_str?d->reason_bogus_str:"")) {
+ free(nm);
+ free(tp);
+ free(cl);
+@@ -633,6 +634,9 @@
+ long long ttl;
+ size_t i;
+ int go_on = 1;
++ int ede;
++ int consumed = 0;
++ char* ede_str = NULL;
+
+ regional_free_all(region);
+
+@@ -647,11 +651,16 @@
+ }
+
+ /* read remainder of line */
+- if(sscanf(s, " %u %u " ARG_LL "d %u %u %u %u", &flags, &qdcount, &ttl,
+- &security, &an, &ns, &ar) != 7) {
++ /* note the last space before any possible EDE text */
++ if(sscanf(s, " %u %u " ARG_LL "d %u %u %u %u %d %n", &flags, &qdcount, &ttl,
++ &security, &an, &ns, &ar, &ede, &consumed) != 8) {
+ log_warn("error cannot parse numbers: %s", s);
+ return 0;
+ }
++ /* there may be EDE text after the numbers */
++ if(consumed > 0 && (size_t)consumed < strlen(s))
++ ede_str = s + consumed;
++ memset(&rep, 0, sizeof(rep));
+ rep.flags = (uint16_t)flags;
+ rep.qdcount = (uint16_t)qdcount;
+ rep.ttl = (time_t)ttl;
+@@ -666,6 +675,8 @@
+ rep.ns_numrrsets = (size_t)ns;
+ rep.ar_numrrsets = (size_t)ar;
+ rep.rrset_count = (size_t)an+(size_t)ns+(size_t)ar;
++ rep.reason_bogus = (sldns_ede_code)ede;
++ rep.reason_bogus_str = ede_str?(char*)regional_strdup(region, ede_str):NULL;
+ rep.rrsets = (struct ub_packed_rrset_key**)regional_alloc_zero(
+ region, sizeof(struct ub_packed_rrset_key*)*rep.rrset_count);
+
+@@ -860,7 +871,8 @@
+ /* go up? */
+ if(iter_dp_is_useless(&qinfo, BIT_RD, dp,
+ (worker->env.cfg->do_ip4 && worker->back->num_ip4 != 0),
+- (worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0))) {
++ (worker->env.cfg->do_ip6 && worker->back->num_ip6 != 0),
++ worker->env.cfg->do_nat64)) {
+ print_dp_main(ssl, dp, msg);
+ print_dp_details(ssl, worker, dp);
+ if(!ssl_printf(ssl, "cache delegation was "
+--- contrib/unbound/daemon/remote.c.orig
++++ contrib/unbound/daemon/remote.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -38,7 +38,7 @@
+ *
+ * This file contains the remote control functionality for the daemon.
+ * The remote control can be performed using either the commandline
+- * unbound-control tool, or a TLS capable web browser.
++ * unbound-control tool, or a TLS capable web browser.
+ * The channel is secured using TLSv1, and certificates.
+ * Both the server and the client(control tool) have their own keys.
+ */
+@@ -87,6 +87,7 @@
+ #include "sldns/parseutil.h"
+ #include "sldns/wire2str.h"
+ #include "sldns/sbuffer.h"
++#include "util/timeval_func.h"
+
+ #ifdef HAVE_SYS_TYPES_H
+ # include <sys/types.h>
+@@ -106,47 +107,6 @@
+ /** what to put on statistics lines between var and value, ": " or "=" */
+ #define SQ "="
+
+-/** subtract timers and the values do not overflow or become negative */
+-static void
+-timeval_subtract(struct timeval* d, const struct timeval* end,
+- const struct timeval* start)
+-{
+-#ifndef S_SPLINT_S
+- time_t end_usec = end->tv_usec;
+- d->tv_sec = end->tv_sec - start->tv_sec;
+- if(end_usec < start->tv_usec) {
+- end_usec += 1000000;
+- d->tv_sec--;
+- }
+- d->tv_usec = end_usec - start->tv_usec;
+-#endif
+-}
+-
+-/** divide sum of timers to get average */
+-static void
+-timeval_divide(struct timeval* avg, const struct timeval* sum, long long d)
+-{
+-#ifndef S_SPLINT_S
+- size_t leftover;
+- if(d <= 0) {
+- avg->tv_sec = 0;
+- avg->tv_usec = 0;
+- return;
+- }
+- avg->tv_sec = sum->tv_sec / d;
+- avg->tv_usec = sum->tv_usec / d;
+- /* handle fraction from seconds divide */
+- leftover = sum->tv_sec - avg->tv_sec*d;
+- if(leftover <= 0)
+- leftover = 0;
+- avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
+- if(avg->tv_sec < 0)
+- avg->tv_sec = 0;
+- if(avg->tv_usec < 0)
+- avg->tv_usec = 0;
+-#endif
+-}
+-
+ static int
+ remote_setup_ctx(struct daemon_remote* rc, struct config_file* cfg)
+ {
+@@ -201,7 +161,7 @@
+ struct daemon_remote*
+ daemon_remote_create(struct config_file* cfg)
+ {
+- struct daemon_remote* rc = (struct daemon_remote*)calloc(1,
++ struct daemon_remote* rc = (struct daemon_remote*)calloc(1,
+ sizeof(*rc));
+ if(!rc) {
+ log_err("out of memory in daemon_remote_create");
+@@ -410,7 +370,7 @@
+ n->next = rc->accept_list;
+ rc->accept_list = n;
+ /* open commpt */
+- n->com = comm_point_create_raw(rc->worker->base, fd, 0,
++ n->com = comm_point_create_raw(rc->worker->base, fd, 0,
+ &remote_accept_callback, rc);
+ if(!n->com)
+ return 0;
+@@ -419,7 +379,7 @@
+ return 1;
+ }
+
+-int daemon_remote_open_accept(struct daemon_remote* rc,
++int daemon_remote_open_accept(struct daemon_remote* rc,
+ struct listen_port* ports, struct worker* worker)
+ {
+ struct listen_port* p;
+@@ -437,7 +397,7 @@
+ {
+ struct listen_list* p;
+ for(p=rc->accept_list; p; p=p->next) {
+- comm_point_stop_listening(p->com);
++ comm_point_stop_listening(p->com);
+ }
+ }
+
+@@ -445,11 +405,11 @@
+ {
+ struct listen_list* p;
+ for(p=rc->accept_list; p; p=p->next) {
+- comm_point_start_listening(p->com, -1, -1);
++ comm_point_start_listening(p->com, -1, -1);
+ }
+ }
+
+-int remote_accept_callback(struct comm_point* c, void* arg, int err,
++int remote_accept_callback(struct comm_point* c, void* arg, int err,
+ struct comm_reply* ATTR_UNUSED(rep))
+ {
+ struct daemon_remote* rc = (struct daemon_remote*)arg;
+@@ -481,7 +441,7 @@
+ }
+ n->fd = newfd;
+ /* start in reading state */
+- n->c = comm_point_create_raw(rc->worker->base, newfd, 0,
++ n->c = comm_point_create_raw(rc->worker->base, newfd, 0,
+ &remote_control_callback, n);
+ if(!n->c) {
+ log_err("out of memory");
+@@ -521,7 +481,7 @@
+ rc->busy_list = n;
+ rc->active ++;
+
+- /* perform the first nonblocking read already, for windows,
++ /* perform the first nonblocking read already, for windows,
+ * so it can return wouldblock. could be faster too. */
+ (void)remote_control_callback(n->c, n, NETEVENT_NOERROR, NULL);
+ return 0;
+@@ -558,17 +518,18 @@
+ ssl_print_text(RES* res, const char* text)
+ {
+ int r;
+- if(!res)
++ if(!res)
+ return 0;
+ if(res->ssl) {
+ ERR_clear_error();
+ if((r=SSL_write(res->ssl, text, (int)strlen(text))) <= 0) {
+- if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
++ int r2;
++ if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN) {
+ verbose(VERB_QUERY, "warning, in SSL_write, peer "
+ "closed connection");
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", r2);
+ return 0;
+ }
+ } else {
+@@ -619,11 +580,12 @@
+ if(res->ssl) {
+ ERR_clear_error();
+ if((r=SSL_read(res->ssl, buf+len, 1)) <= 0) {
+- if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
++ int r2;
++ if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN) {
+ buf[len] = 0;
+ return 1;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", r2);
+ return 0;
+ }
+ } else {
+@@ -636,7 +598,7 @@
+ }
+ if(errno == EINTR || errno == EAGAIN)
+ continue;
+- log_err("could not recv: %s",
++ if(rr < 0) log_err("could not recv: %s",
+ sock_strerror(errno));
+ return 0;
+ }
+@@ -660,7 +622,7 @@
+ skipwhite(char* str)
+ {
+ /* EOS \0 is not a space */
+- while( isspace((unsigned char)*str) )
++ while( isspace((unsigned char)*str) )
+ str++;
+ return str;
+ }
+@@ -708,20 +670,30 @@
+ print_stats(RES* ssl, const char* nm, struct ub_stats_info* s)
+ {
+ struct timeval sumwait, avg;
+- if(!ssl_printf(ssl, "%s.num.queries"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.queries"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries)) return 0;
+ if(!ssl_printf(ssl, "%s.num.queries_ip_ratelimited"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries_ip_ratelimited)) return 0;
+- if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm,
+- (unsigned long)(s->svr.num_queries
++ if(!ssl_printf(ssl, "%s.num.queries_cookie_valid"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_cookie_valid)) return 0;
++ if(!ssl_printf(ssl, "%s.num.queries_cookie_client"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_cookie_client)) return 0;
++ if(!ssl_printf(ssl, "%s.num.queries_cookie_invalid"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_cookie_invalid)) return 0;
++ if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm,
++ (unsigned long)(s->svr.num_queries
+ - s->svr.num_queries_missed_cache))) return 0;
+- if(!ssl_printf(ssl, "%s.num.cachemiss"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.cachemiss"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries_missed_cache)) return 0;
+- if(!ssl_printf(ssl, "%s.num.prefetch"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.prefetch"SQ"%lu\n", nm,
+ (unsigned long)s->svr.num_queries_prefetch)) return 0;
++ if(!ssl_printf(ssl, "%s.num.queries_timed_out"SQ"%lu\n", nm,
++ (unsigned long)s->svr.num_queries_timed_out)) return 0;
++ if(!ssl_printf(ssl, "%s.query.queue_time_us.max"SQ"%lu\n", nm,
++ (unsigned long)s->svr.max_query_time_us)) return 0;
+ if(!ssl_printf(ssl, "%s.num.expired"SQ"%lu\n", nm,
+ (unsigned long)s->svr.ans_expired)) return 0;
+- if(!ssl_printf(ssl, "%s.num.recursivereplies"SQ"%lu\n", nm,
++ if(!ssl_printf(ssl, "%s.num.recursivereplies"SQ"%lu\n", nm,
+ (unsigned long)s->mesh_replies_sent)) return 0;
+ #ifdef USE_DNSCRYPT
+ if(!ssl_printf(ssl, "%s.num.dnscrypt.crypted"SQ"%lu\n", nm,
+@@ -755,7 +727,7 @@
+ timeval_divide(&avg, &sumwait, s->mesh_replies_sent);
+ if(!ssl_printf(ssl, "%s.recursion.time.avg"SQ ARG_LL "d.%6.6d\n", nm,
+ (long long)avg.tv_sec, (int)avg.tv_usec)) return 0;
+- if(!ssl_printf(ssl, "%s.recursion.time.median"SQ"%g\n", nm,
++ if(!ssl_printf(ssl, "%s.recursion.time.median"SQ"%g\n", nm,
+ s->mesh_time_median)) return 0;
+ if(!ssl_printf(ssl, "%s.tcpusage"SQ"%lu\n", nm,
+ (unsigned long)s->svr.tcp_accept_usage)) return 0;
+@@ -780,7 +752,7 @@
+ /* more than a Gb */
+ size_t front = x / (size_t)1000000;
+ size_t back = x % (size_t)1000000;
+- return ssl_printf(ssl, "%s%u%6.6u\n", desc,
++ return ssl_printf(ssl, "%s%u%6.6u\n", desc,
+ (unsigned)front, (unsigned)back);
+ } else {
+ return ssl_printf(ssl, "%s%lu\n", desc, (unsigned long)x);
+@@ -880,11 +852,11 @@
+ timeval_subtract(&dt, &now, &worker->daemon->time_last_stat);
+ if(reset)
+ worker->daemon->time_last_stat = now;
+- if(!ssl_printf(ssl, "time.now"SQ ARG_LL "d.%6.6d\n",
++ if(!ssl_printf(ssl, "time.now"SQ ARG_LL "d.%6.6d\n",
+ (long long)now.tv_sec, (unsigned)now.tv_usec)) return 0;
+- if(!ssl_printf(ssl, "time.up"SQ ARG_LL "d.%6.6d\n",
++ if(!ssl_printf(ssl, "time.up"SQ ARG_LL "d.%6.6d\n",
+ (long long)up.tv_sec, (unsigned)up.tv_usec)) return 0;
+- if(!ssl_printf(ssl, "time.elapsed"SQ ARG_LL "d.%6.6d\n",
++ if(!ssl_printf(ssl, "time.elapsed"SQ ARG_LL "d.%6.6d\n",
+ (long long)dt.tv_sec, (unsigned)dt.tv_usec)) return 0;
+ return 1;
+ }
+@@ -902,7 +874,7 @@
+ }
+ timehist_import(hist, s->svr.hist, NUM_BUCKETS_HIST);
+ for(i=0; i<hist->num; i++) {
+- if(!ssl_printf(ssl,
++ if(!ssl_printf(ssl,
+ "histogram.%6.6d.%6.6d.to.%6.6d.%6.6d=%lu\n",
+ (int)hist->buckets[i].lower.tv_sec,
+ (int)hist->buckets[i].lower.tv_usec,
+@@ -945,11 +917,11 @@
+ } else {
+ snprintf(nm, sizeof(nm), "TYPE%d", i);
+ }
+- if(!ssl_printf(ssl, "num.query.type.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.type.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.qtype[i])) return 0;
+ }
+ if(!inhibit_zero || s->svr.qtype_big) {
+- if(!ssl_printf(ssl, "num.query.type.other"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.type.other"SQ"%lu\n",
+ (unsigned long)s->svr.qtype_big)) return 0;
+ }
+ /* CLASS */
+@@ -962,11 +934,11 @@
+ } else {
+ snprintf(nm, sizeof(nm), "CLASS%d", i);
+ }
+- if(!ssl_printf(ssl, "num.query.class.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.class.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.qclass[i])) return 0;
+ }
+ if(!inhibit_zero || s->svr.qclass_big) {
+- if(!ssl_printf(ssl, "num.query.class.other"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.class.other"SQ"%lu\n",
+ (unsigned long)s->svr.qclass_big)) return 0;
+ }
+ /* OPCODE */
+@@ -979,44 +951,44 @@
+ } else {
+ snprintf(nm, sizeof(nm), "OPCODE%d", i);
+ }
+- if(!ssl_printf(ssl, "num.query.opcode.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.opcode.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.qopcode[i])) return 0;
+ }
+ /* transport */
+- if(!ssl_printf(ssl, "num.query.tcp"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tcp"SQ"%lu\n",
+ (unsigned long)s->svr.qtcp)) return 0;
+- if(!ssl_printf(ssl, "num.query.tcpout"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tcpout"SQ"%lu\n",
+ (unsigned long)s->svr.qtcp_outgoing)) return 0;
+ if(!ssl_printf(ssl, "num.query.udpout"SQ"%lu\n",
+ (unsigned long)s->svr.qudp_outgoing)) return 0;
+- if(!ssl_printf(ssl, "num.query.tls"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tls"SQ"%lu\n",
+ (unsigned long)s->svr.qtls)) return 0;
+- if(!ssl_printf(ssl, "num.query.tls.resume"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.tls.resume"SQ"%lu\n",
+ (unsigned long)s->svr.qtls_resume)) return 0;
+- if(!ssl_printf(ssl, "num.query.ipv6"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.ipv6"SQ"%lu\n",
+ (unsigned long)s->svr.qipv6)) return 0;
+ if(!ssl_printf(ssl, "num.query.https"SQ"%lu\n",
+ (unsigned long)s->svr.qhttps)) return 0;
+ /* flags */
+- if(!ssl_printf(ssl, "num.query.flags.QR"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.QR"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_QR)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.AA"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.AA"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_AA)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.TC"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.TC"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_TC)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.RD"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.RD"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_RD)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.RA"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.RA"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_RA)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.Z"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.Z"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_Z)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.AD"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.AD"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_AD)) return 0;
+- if(!ssl_printf(ssl, "num.query.flags.CD"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.flags.CD"SQ"%lu\n",
+ (unsigned long)s->svr.qbit_CD)) return 0;
+- if(!ssl_printf(ssl, "num.query.edns.present"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.edns.present"SQ"%lu\n",
+ (unsigned long)s->svr.qEDNS)) return 0;
+- if(!ssl_printf(ssl, "num.query.edns.DO"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.edns.DO"SQ"%lu\n",
+ (unsigned long)s->svr.qEDNS_DO)) return 0;
+
+ /* RCODE */
+@@ -1030,31 +1002,31 @@
+ } else {
+ snprintf(nm, sizeof(nm), "RCODE%d", i);
+ }
+- if(!ssl_printf(ssl, "num.answer.rcode.%s"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.rcode.%s"SQ"%lu\n",
+ nm, (unsigned long)s->svr.ans_rcode[i])) return 0;
+ }
+ if(!inhibit_zero || s->svr.ans_rcode_nodata) {
+- if(!ssl_printf(ssl, "num.answer.rcode.nodata"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.rcode.nodata"SQ"%lu\n",
+ (unsigned long)s->svr.ans_rcode_nodata)) return 0;
+ }
+ /* iteration */
+- if(!ssl_printf(ssl, "num.query.ratelimited"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.ratelimited"SQ"%lu\n",
+ (unsigned long)s->svr.queries_ratelimited)) return 0;
+ /* validation */
+- if(!ssl_printf(ssl, "num.answer.secure"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.secure"SQ"%lu\n",
+ (unsigned long)s->svr.ans_secure)) return 0;
+- if(!ssl_printf(ssl, "num.answer.bogus"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.answer.bogus"SQ"%lu\n",
+ (unsigned long)s->svr.ans_bogus)) return 0;
+- if(!ssl_printf(ssl, "num.rrset.bogus"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.rrset.bogus"SQ"%lu\n",
+ (unsigned long)s->svr.rrset_bogus)) return 0;
+- if(!ssl_printf(ssl, "num.query.aggressive.NOERROR"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.aggressive.NOERROR"SQ"%lu\n",
+ (unsigned long)s->svr.num_neg_cache_noerror)) return 0;
+- if(!ssl_printf(ssl, "num.query.aggressive.NXDOMAIN"SQ"%lu\n",
++ if(!ssl_printf(ssl, "num.query.aggressive.NXDOMAIN"SQ"%lu\n",
+ (unsigned long)s->svr.num_neg_cache_nxdomain)) return 0;
+ /* threat detection */
+- if(!ssl_printf(ssl, "unwanted.queries"SQ"%lu\n",
++ if(!ssl_printf(ssl, "unwanted.queries"SQ"%lu\n",
+ (unsigned long)s->svr.unwanted_queries)) return 0;
+- if(!ssl_printf(ssl, "unwanted.replies"SQ"%lu\n",
++ if(!ssl_printf(ssl, "unwanted.replies"SQ"%lu\n",
+ (unsigned long)s->svr.unwanted_replies)) return 0;
+ /* cache counts */
+ if(!ssl_printf(ssl, "msg.cache.count"SQ"%u\n",
+@@ -1065,6 +1037,11 @@
+ (unsigned)s->svr.infra_cache_count)) return 0;
+ if(!ssl_printf(ssl, "key.cache.count"SQ"%u\n",
+ (unsigned)s->svr.key_cache_count)) return 0;
++ /* max collisions */
++ if(!ssl_printf(ssl, "msg.cache.max_collisions"SQ"%u\n",
++ (unsigned)s->svr.msg_cache_max_collisions)) return 0;
++ if(!ssl_printf(ssl, "rrset.cache.max_collisions"SQ"%u\n",
++ (unsigned)s->svr.rrset_cache_max_collisions)) return 0;
+ /* applied RPZ actions */
+ for(i=0; i<UB_STATS_RPZ_ACTION_NUM; i++) {
+ if(i == RPZ_NO_OVERRIDE_ACTION)
+@@ -1095,6 +1072,10 @@
+ if(!ssl_printf(ssl, "num.query.subnet_cache"SQ"%lu\n",
+ (unsigned long)s->svr.num_query_subnet_cache)) return 0;
+ #endif /* CLIENT_SUBNET */
++#ifdef USE_CACHEDB
++ if(!ssl_printf(ssl, "num.query.cachedb"SQ"%lu\n",
++ (unsigned long)s->svr.num_query_cachedb)) return 0;
++#endif /* USE_CACHEDB */
+ return 1;
+ }
+
+@@ -1119,7 +1100,7 @@
+ }
+ /* print the thread statistics */
+ total.mesh_time_median /= (double)daemon->num;
+- if(!print_stats(ssl, "total", &total))
++ if(!print_stats(ssl, "total", &total))
+ return;
+ if(!print_uptime(ssl, worker, reset))
+ return;
+@@ -1208,7 +1189,7 @@
+ return 0;
+ }
+ lock_rw_wrlock(&zones->lock);
+- if((z=local_zones_find(zones, nm, nmlen,
++ if((z=local_zones_find(zones, nm, nmlen,
+ nmlabs, LDNS_RR_CLASS_IN))) {
+ /* already present in tree */
+ lock_rw_wrlock(&z->lock);
+@@ -1218,7 +1199,7 @@
+ lock_rw_unlock(&zones->lock);
+ return 1;
+ }
+- if(!local_zones_add_zone(zones, nm, nmlen,
++ if(!local_zones_add_zone(zones, nm, nmlen,
+ nmlabs, LDNS_RR_CLASS_IN, t)) {
+ lock_rw_unlock(&zones->lock);
+ ssl_printf(ssl, "error out of memory\n");
+@@ -1244,8 +1225,8 @@
+ char buf[2048];
+ int num = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ if(!perform_zone_add(ssl, zones, buf)) {
+ if(!ssl_printf(ssl, "error for input line: %s\n", buf))
+ return;
+@@ -1267,7 +1248,7 @@
+ if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs))
+ return 0;
+ lock_rw_wrlock(&zones->lock);
+- if((z=local_zones_find(zones, nm, nmlen,
++ if((z=local_zones_find(zones, nm, nmlen,
+ nmlabs, LDNS_RR_CLASS_IN))) {
+ /* present in tree */
+ local_zones_del_zone(zones, z);
+@@ -1293,8 +1274,8 @@
+ char buf[2048];
+ int num = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ if(!perform_zone_remove(ssl, zones, buf)) {
+ if(!ssl_printf(ssl, "error for input line: %s\n", buf))
+ return;
+@@ -1357,8 +1338,8 @@
+ char buf[2048];
+ int num = 0, line = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ line++;
+ if(perform_data_add(ssl, zones, buf, line))
+ num++;
+@@ -1397,8 +1378,8 @@
+ char buf[2048];
+ int num = 0;
+ while(ssl_read_line(ssl, buf, sizeof(buf))) {
+- if(buf[0] == 0x04 && buf[1] == 0)
+- break; /* end of transmission */
++ if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
++ break; /* zero byte line or end of transmission */
+ if(!perform_data_remove(ssl, zones, buf)) {
+ if(!ssl_printf(ssl, "error for input line: %s\n", buf))
+ return;
+@@ -1608,8 +1589,11 @@
+ if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs))
+ return;
+ t = sldns_get_rr_type_by_name(arg2);
++ if(t == 0 && strcmp(arg2, "TYPE0") != 0) {
++ return;
++ }
+ do_cache_remove(worker, nm, nmlen, t, LDNS_RR_CLASS_IN);
+-
++
+ free(nm);
+ send_ok(ssl);
+ }
+@@ -1719,7 +1703,7 @@
+ struct del_info* inf = (struct del_info*)arg;
+ struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key;
+ if(dname_subdomain_c(k->rk.dname, inf->name)) {
+- struct packed_rrset_data* d =
++ struct packed_rrset_data* d =
+ (struct packed_rrset_data*)e->data;
+ if(d->ttl > inf->expired) {
+ d->ttl = inf->expired;
+@@ -1783,21 +1767,21 @@
+ inf.num_rrsets = 0;
+ inf.num_msgs = 0;
+ inf.num_keys = 0;
+- slabhash_traverse(&worker->env.rrset_cache->table, 1,
++ slabhash_traverse(&worker->env.rrset_cache->table, 1,
+ &zone_del_rrset, &inf);
+
+ slabhash_traverse(worker->env.msg_cache, 1, &zone_del_msg, &inf);
+
+ /* and validator cache */
+ if(worker->env.key_cache) {
+- slabhash_traverse(worker->env.key_cache->slab, 1,
++ slabhash_traverse(worker->env.key_cache->slab, 1,
+ &zone_del_kcache, &inf);
+ }
+
+ free(nm);
+
+ (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages "
+- "and %lu key entries\n", (unsigned long)inf.num_rrsets,
++ "and %lu key entries\n", (unsigned long)inf.num_rrsets,
+ (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys);
+ }
+
+@@ -1852,19 +1836,19 @@
+ inf.num_rrsets = 0;
+ inf.num_msgs = 0;
+ inf.num_keys = 0;
+- slabhash_traverse(&worker->env.rrset_cache->table, 1,
++ slabhash_traverse(&worker->env.rrset_cache->table, 1,
+ &bogus_del_rrset, &inf);
+
+ slabhash_traverse(worker->env.msg_cache, 1, &bogus_del_msg, &inf);
+
+ /* and validator cache */
+ if(worker->env.key_cache) {
+- slabhash_traverse(worker->env.key_cache->slab, 1,
++ slabhash_traverse(worker->env.key_cache->slab, 1,
+ &bogus_del_kcache, &inf);
+ }
+
+ (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages "
+- "and %lu key entries\n", (unsigned long)inf.num_rrsets,
++ "and %lu key entries\n", (unsigned long)inf.num_rrsets,
+ (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys);
+ }
+
+@@ -1927,19 +1911,19 @@
+ inf.num_rrsets = 0;
+ inf.num_msgs = 0;
+ inf.num_keys = 0;
+- slabhash_traverse(&worker->env.rrset_cache->table, 1,
++ slabhash_traverse(&worker->env.rrset_cache->table, 1,
+ &negative_del_rrset, &inf);
+
+ slabhash_traverse(worker->env.msg_cache, 1, &negative_del_msg, &inf);
+
+ /* and validator cache */
+ if(worker->env.key_cache) {
+- slabhash_traverse(worker->env.key_cache->slab, 1,
++ slabhash_traverse(worker->env.key_cache->slab, 1,
+ &negative_del_kcache, &inf);
+ }
+
+ (void)ssl_printf(ssl, "ok removed %lu rrsets, %lu messages "
+- "and %lu key entries\n", (unsigned long)inf.num_rrsets,
++ "and %lu key entries\n", (unsigned long)inf.num_rrsets,
+ (unsigned long)inf.num_msgs, (unsigned long)inf.num_keys);
+ }
+
+@@ -1964,7 +1948,7 @@
+ do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_NAPTR, LDNS_RR_CLASS_IN);
+ do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_SVCB, LDNS_RR_CLASS_IN);
+ do_cache_remove(w, nm, nmlen, LDNS_RR_TYPE_HTTPS, LDNS_RR_CLASS_IN);
+-
++
+ free(nm);
+ send_ok(ssl);
+ }
+@@ -2334,7 +2318,7 @@
+ uptime = (time_t)time(NULL) - (time_t)worker->daemon->time_boot.tv_sec;
+ if(!ssl_printf(ssl, "uptime: " ARG_LL "d seconds\n", (long long)uptime))
+ return;
+- if(!ssl_printf(ssl, "options:%s%s%s%s\n" ,
++ if(!ssl_printf(ssl, "options:%s%s%s%s\n" ,
+ (worker->daemon->reuseport?" reuseport":""),
+ (worker->daemon->rc->accept_list?" control":""),
+ (worker->daemon->rc->accept_list && worker->daemon->rc->use_cert?"(ssl)":""),
+@@ -2348,7 +2332,7 @@
+
+ /** get age for the mesh state */
+ static void
+-get_mesh_age(struct mesh_state* m, char* buf, size_t len,
++get_mesh_age(struct mesh_state* m, char* buf, size_t len,
+ struct module_env* env)
+ {
+ if(m->reply_list) {
+@@ -2367,7 +2351,7 @@
+
+ /** get status of a mesh state */
+ static void
+-get_mesh_status(struct mesh_area* mesh, struct mesh_state* m,
++get_mesh_status(struct mesh_area* mesh, struct mesh_state* m,
+ char* buf, size_t len)
+ {
+ enum module_ext_state s = m->s.ext_state[m->s.curmod];
+@@ -2389,7 +2373,7 @@
+ snprintf(buf, len, " ");
+ l = strlen(buf);
+ buf += l; len -= l;
+- addr_to_str(&e->qsent->addr, e->qsent->addrlen,
++ addr_to_str(&e->qsent->addr, e->qsent->addrlen,
+ buf, len);
+ l = strlen(buf);
+ buf += l; len -= l;
+@@ -2442,7 +2426,7 @@
+ dname_str(m->s.qinfo.qname, buf);
+ get_mesh_age(m, timebuf, sizeof(timebuf), &worker->env);
+ get_mesh_status(mesh, m, statbuf, sizeof(statbuf));
+- if(!ssl_printf(ssl, "%3d %4s %2s %s %s %s\n",
++ if(!ssl_printf(ssl, "%3d %4s %2s %s %s %s\n",
+ num, (t?t:"TYPE??"), (c?c:"CLASS??"), buf, timebuf,
+ statbuf)) {
+ free(t);
+@@ -2632,7 +2616,7 @@
+ free(nm);
+ send_ok(ssl);
+ }
+-
++
+ /** do the set_option command */
+ static void
+ do_set_option(RES* ssl, struct worker* worker, char* arg)
+@@ -2770,7 +2754,7 @@
+ RBTREE_FOR(z, struct local_zone*, &zones->ztree) {
+ lock_rw_rdlock(&z->lock);
+ dname_str(z->name, buf);
+- if(!ssl_printf(ssl, "%s %s\n", buf,
++ if(!ssl_printf(ssl, "%s %s\n", buf,
+ local_zone_type2str(z->type))) {
+ /* failure to print */
+ lock_rw_unlock(&z->lock);
+@@ -2999,7 +2983,7 @@
+ distribute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd)
+ {
+ int i;
+- if(!cmd || !ssl)
++ if(!cmd || !ssl)
+ return;
+ /* skip i=0 which is me */
+ for(i=1; i<rc->worker->daemon->num; i++) {
+@@ -3022,7 +3006,7 @@
+
+ /** execute a remote control command */
+ static void
+-execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
++execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
+ struct worker* worker)
+ {
+ char* p = skipwhite(cmd);
+@@ -3206,7 +3190,7 @@
+ }
+ }
+
+-void
++void
+ daemon_remote_exec(struct worker* worker)
+ {
+ /* read the cmd string */
+@@ -3240,9 +3224,10 @@
+ if(res->ssl) {
+ ERR_clear_error();
+ if((r=SSL_read(res->ssl, magic, (int)sizeof(magic)-1)) <= 0) {
+- if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN)
++ int r2;
++ if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN)
+ return;
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", r2);
+ return;
+ }
+ } else {
+@@ -3309,13 +3294,13 @@
+ log_err("remote control connection closed prematurely");
+ log_addr(VERB_OPS, "failed connection from",
+ &s->c->repinfo.remote_addr, s->c->repinfo.remote_addrlen);
+- log_crypto_err("remote control failed ssl");
++ log_crypto_err_io("remote control failed ssl", r2);
+ clean_point(rc, s);
+ }
+ return 0;
+ }
+
+-int remote_control_callback(struct comm_point* c, void* arg, int err,
++int remote_control_callback(struct comm_point* c, void* arg, int err,
+ struct comm_reply* ATTR_UNUSED(rep))
+ {
+ RES res;
+@@ -3323,7 +3308,7 @@
+ struct daemon_remote* rc = s->rc;
+ int r;
+ if(err != NETEVENT_NOERROR) {
+- if(err==NETEVENT_TIMEOUT)
++ if(err==NETEVENT_TIMEOUT)
+ log_err("remote control timed out");
+ clean_point(rc, s);
+ return 0;
+--- contrib/unbound/daemon/remote.h.orig
++++ contrib/unbound/daemon/remote.h
+@@ -46,7 +46,7 @@
+ #ifndef DAEMON_REMOTE_H
+ #define DAEMON_REMOTE_H
+ #ifdef HAVE_OPENSSL_SSL_H
+-#include "openssl/ssl.h"
++#include <openssl/ssl.h>
+ #endif
+ struct config_file;
+ struct listen_list;
+--- contrib/unbound/daemon/stats.c.orig
++++ contrib/unbound/daemon/stats.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -116,8 +116,8 @@
+ log_info("server stats for thread %d: %u queries, "
+ "%u answers from cache, %u recursions, %u prefetch, %u rejected by "
+ "ip ratelimiting",
+- threadnum, (unsigned)stats->num_queries,
+- (unsigned)(stats->num_queries -
++ threadnum, (unsigned)stats->num_queries,
++ (unsigned)(stats->num_queries -
+ stats->num_queries_missed_cache),
+ (unsigned)stats->num_queries_missed_cache,
+ (unsigned)stats->num_queries_prefetch,
+@@ -279,7 +279,7 @@
+ s->svr.ans_rcode[i] += (long long)worker->env.mesh->ans_rcode[i];
+ for(i=0; i<UB_STATS_RPZ_ACTION_NUM; i++)
+ s->svr.rpz_action[i] += (long long)worker->env.mesh->rpz_action[i];
+- timehist_export(worker->env.mesh->histogram, s->svr.hist,
++ timehist_export(worker->env.mesh->histogram, s->svr.hist,
+ NUM_BUCKETS_HIST);
+ /* values from outside network */
+ s->svr.unwanted_replies = (long long)worker->back->unwanted_replies;
+@@ -293,8 +293,10 @@
+ s->svr.queries_ratelimited = (long long)get_queries_ratelimit(worker, reset);
+
+ /* get cache sizes */
+- s->svr.msg_cache_count = (long long)count_slabhash_entries(worker->env.msg_cache);
+- s->svr.rrset_cache_count = (long long)count_slabhash_entries(&worker->env.rrset_cache->table);
++ get_slabhash_stats(worker->env.msg_cache,
++ &s->svr.msg_cache_count, &s->svr.msg_cache_max_collisions);
++ get_slabhash_stats(&worker->env.rrset_cache->table,
++ &s->svr.rrset_cache_count, &s->svr.rrset_cache_max_collisions);
+ s->svr.infra_cache_count = (long long)count_slabhash_entries(worker->env.infra_cache->hosts);
+ if(worker->env.key_cache)
+ s->svr.key_cache_count = (long long)count_slabhash_entries(worker->env.key_cache->slab);
+@@ -354,6 +356,11 @@
+ s->svr.num_query_subnet = 0;
+ s->svr.num_query_subnet_cache = 0;
+ #endif
++#ifdef USE_CACHEDB
++ s->svr.num_query_cachedb = (long long)worker->env.mesh->ans_cachedb;
++#else
++ s->svr.num_query_cachedb = 0;
++#endif
+
+ /* get tcp accept usage */
+ s->svr.tcp_accept_usage = 0;
+@@ -419,7 +426,7 @@
+ struct ub_stats_info s;
+ server_stats_compile(worker, &s, reset);
+ verbose(VERB_ALGO, "write stats replymsg");
+- if(!tube_write_msg(worker->daemon->workers[0]->cmd,
++ if(!tube_write_msg(worker->daemon->workers[0]->cmd,
+ (uint8_t*)&s, sizeof(s), 0))
+ fatal_exit("could not write stat values over cmd channel");
+ }
+@@ -428,8 +435,14 @@
+ {
+ total->svr.num_queries += a->svr.num_queries;
+ total->svr.num_queries_ip_ratelimited += a->svr.num_queries_ip_ratelimited;
++ total->svr.num_queries_cookie_valid += a->svr.num_queries_cookie_valid;
++ total->svr.num_queries_cookie_client += a->svr.num_queries_cookie_client;
++ total->svr.num_queries_cookie_invalid += a->svr.num_queries_cookie_invalid;
+ total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache;
+ total->svr.num_queries_prefetch += a->svr.num_queries_prefetch;
++ total->svr.num_queries_timed_out += a->svr.num_queries_timed_out;
++ if (total->svr.max_query_time_us < a->svr.max_query_time_us)
++ total->svr.max_query_time_us = a->svr.max_query_time_us;
+ total->svr.sum_query_list_size += a->svr.sum_query_list_size;
+ total->svr.ans_expired += a->svr.ans_expired;
+ #ifdef USE_DNSCRYPT
+@@ -471,6 +484,9 @@
+ total->svr.unwanted_replies += a->svr.unwanted_replies;
+ total->svr.unwanted_queries += a->svr.unwanted_queries;
+ total->svr.tcp_accept_usage += a->svr.tcp_accept_usage;
++#ifdef USE_CACHEDB
++ total->svr.num_query_cachedb += a->svr.num_query_cachedb;
++#endif
+ for(i=0; i<UB_STATS_QTYPE_NUM; i++)
+ total->svr.qtype[i] += a->svr.qtype[i];
+ for(i=0; i<UB_STATS_QCLASS_NUM; i++)
+@@ -514,7 +530,7 @@
+ if(c->ssl != NULL) {
+ stats->qtls++;
+ #ifdef HAVE_SSL
+- if(SSL_session_reused(c->ssl))
++ if(SSL_session_reused(c->ssl))
+ stats->qtls_resume++;
+ #endif
+ if(c->type == comm_http)
+@@ -555,3 +571,16 @@
+ stats->ans_rcode_nodata ++;
+ }
+ }
++
++void server_stats_downstream_cookie(struct ub_server_stats* stats,
++ struct edns_data* edns)
++{
++ if(!(edns->edns_present && edns->cookie_present)) return;
++ if(edns->cookie_valid) {
++ stats->num_queries_cookie_valid++;
++ } else if(edns->cookie_client) {
++ stats->num_queries_cookie_client++;
++ } else {
++ stats->num_queries_cookie_invalid++;
++ }
++}
+--- contrib/unbound/daemon/stats.h.orig
++++ contrib/unbound/daemon/stats.h
+@@ -126,4 +126,11 @@
+ */
+ void server_stats_insrcode(struct ub_server_stats* stats, struct sldns_buffer* buf);
+
++/**
++ * Add DNS Cookie stats for this query
++ * @param stats: the stats
++ * @param edns: edns record
++ */
++void server_stats_downstream_cookie(struct ub_server_stats* stats,
++ struct edns_data* edns);
+ #endif /* DAEMON_STATS_H */
+--- contrib/unbound/daemon/worker.c.orig
++++ contrib/unbound/daemon/worker.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -66,8 +66,10 @@
+ #include "util/data/msgencode.h"
+ #include "util/data/dname.h"
+ #include "util/fptr_wlist.h"
++#include "util/proxy_protocol.h"
+ #include "util/tube.h"
+ #include "util/edns.h"
++#include "util/timeval_func.h"
+ #include "iterator/iter_fwd.h"
+ #include "iterator/iter_hints.h"
+ #include "iterator/iter_utils.h"
+@@ -112,7 +114,7 @@
+
+ /** Report on memory usage by this thread and global */
+ static void
+-worker_mem_report(struct worker* ATTR_UNUSED(worker),
++worker_mem_report(struct worker* ATTR_UNUSED(worker),
+ struct serviced_query* ATTR_UNUSED(cur_serv))
+ {
+ #ifdef UNBOUND_ALLOC_STATS
+@@ -125,7 +127,7 @@
+ #ifdef CLIENT_SUBNET
+ size_t subnet = 0;
+ #endif /* CLIENT_SUBNET */
+- if(verbosity < VERB_ALGO)
++ if(verbosity < VERB_ALGO)
+ return;
+ front = listen_get_mem(worker->front);
+ back = outnet_get_mem(worker->back);
+@@ -154,10 +156,10 @@
+ (&worker->env, i);
+ }
+ me = sizeof(*worker) + sizeof(*worker->base) + sizeof(*worker->comsig)
+- + comm_point_get_mem(worker->cmd_com)
+- + sizeof(worker->rndstate)
+- + regional_get_mem(worker->scratchpad)
+- + sizeof(*worker->env.scratch_buffer)
++ + comm_point_get_mem(worker->cmd_com)
++ + sizeof(worker->rndstate)
++ + regional_get_mem(worker->scratchpad)
++ + sizeof(*worker->env.scratch_buffer)
+ + sldns_buffer_capacity(worker->env.scratch_buffer)
+ + forwards_get_mem(worker->env.fwds)
+ + hints_get_mem(worker->env.hints);
+@@ -172,7 +174,7 @@
+ log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u "
+ "rrset=%u infra=%u iter=%u val=%u subnet=%u anchors=%u "
+ "alloccache=%u globalalloccache=%u me=%u",
+- (unsigned)total, (unsigned)front, (unsigned)back,
++ (unsigned)total, (unsigned)front, (unsigned)back,
+ (unsigned)mesh, (unsigned)msg, (unsigned)rrset, (unsigned)infra,
+ (unsigned)iter, (unsigned)val,
+ (unsigned)subnet, (unsigned)anch, (unsigned)ac,
+@@ -181,13 +183,13 @@
+ log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u "
+ "rrset=%u infra=%u iter=%u val=%u anchors=%u "
+ "alloccache=%u globalalloccache=%u me=%u",
+- (unsigned)total, (unsigned)front, (unsigned)back,
+- (unsigned)mesh, (unsigned)msg, (unsigned)rrset,
++ (unsigned)total, (unsigned)front, (unsigned)back,
++ (unsigned)mesh, (unsigned)msg, (unsigned)rrset,
+ (unsigned)infra, (unsigned)iter, (unsigned)val, (unsigned)anch,
+ (unsigned)ac, (unsigned)superac, (unsigned)me);
+ #endif /* CLIENT_SUBNET */
+ log_info("Total heap memory estimate: %u total-alloc: %u "
+- "total-free: %u", (unsigned)total,
++ "total-free: %u", (unsigned)total,
+ (unsigned)unbound_mem_alloc, (unsigned)unbound_mem_freed);
+ #else /* no UNBOUND_ALLOC_STATS */
+ size_t val = 0;
+@@ -227,7 +229,7 @@
+ #endif /* UNBOUND_ALLOC_STATS */
+ }
+
+-void
++void
+ worker_send_cmd(struct worker* worker, enum worker_commands cmd)
+ {
+ uint32_t c = (uint32_t)htonl(cmd);
+@@ -236,8 +238,8 @@
+ }
+ }
+
+-int
+-worker_handle_service_reply(struct comm_point* c, void* arg, int error,
++int
++worker_handle_service_reply(struct comm_point* c, void* arg, int error,
+ struct comm_reply* reply_info)
+ {
+ struct outbound_entry* e = (struct outbound_entry*)arg;
+@@ -252,13 +254,13 @@
+ }
+ /* sanity check. */
+ if(!LDNS_QR_WIRE(sldns_buffer_begin(c->buffer))
+- || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) !=
++ || LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) !=
+ LDNS_PACKET_QUERY
+ || LDNS_QDCOUNT(sldns_buffer_begin(c->buffer)) > 1) {
+ /* error becomes timeout for the module as if this reply
+ * never arrived. */
+ verbose(VERB_ALGO, "worker: bad reply handled as timeout");
+- mesh_report_reply(worker->env.mesh, e, reply_info,
++ mesh_report_reply(worker->env.mesh, e, reply_info,
+ NETEVENT_TIMEOUT);
+ worker_mem_report(worker, sq);
+ return 0;
+@@ -288,64 +290,86 @@
+ return err;
+ }
+
++/**
++ * Structure holding the result of the worker_check_request function.
++ * Based on configuration it could be called up to four times; ideally should
++ * be called once.
++ */
++struct check_request_result {
++ int checked;
++ int value;
++};
+ /** check request sanity.
+ * @param pkt: the wire packet to examine for sanity.
+ * @param worker: parameters for checking.
+- * @return error code, 0 OK, or -1 discard.
++ * @param out: struct to update with the result.
+ */
+-static int
+-worker_check_request(sldns_buffer* pkt, struct worker* worker)
++static void
++worker_check_request(sldns_buffer* pkt, struct worker* worker,
++ struct check_request_result* out)
+ {
++ if(out->checked) return;
++ out->checked = 1;
+ if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) {
+ verbose(VERB_QUERY, "request too short, discarded");
+- return -1;
++ out->value = -1;
++ return;
+ }
+- if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE &&
++ if(sldns_buffer_limit(pkt) > NORMAL_UDP_SIZE &&
+ worker->daemon->cfg->harden_large_queries) {
+ verbose(VERB_QUERY, "request too large, discarded");
+- return -1;
++ out->value = -1;
++ return;
+ }
+ if(LDNS_QR_WIRE(sldns_buffer_begin(pkt))) {
+ verbose(VERB_QUERY, "request has QR bit on, discarded");
+- return -1;
++ out->value = -1;
++ return;
+ }
+ if(LDNS_TC_WIRE(sldns_buffer_begin(pkt))) {
+ LDNS_TC_CLR(sldns_buffer_begin(pkt));
+ verbose(VERB_QUERY, "request bad, has TC bit on");
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+ if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY &&
+ LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_NOTIFY) {
+- verbose(VERB_QUERY, "request unknown opcode %d",
++ verbose(VERB_QUERY, "request unknown opcode %d",
+ LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_NOTIMPL);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_NOTIMPL);
++ return;
+ }
+ if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) {
+- verbose(VERB_QUERY, "request wrong nr qd=%d",
++ verbose(VERB_QUERY, "request wrong nr qd=%d",
+ LDNS_QDCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+- if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0 &&
++ if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0 &&
+ (LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 1 ||
+ LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_NOTIFY)) {
+- verbose(VERB_QUERY, "request wrong nr an=%d",
++ verbose(VERB_QUERY, "request wrong nr an=%d",
+ LDNS_ANCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+ if(LDNS_NSCOUNT(sldns_buffer_begin(pkt)) != 0) {
+- verbose(VERB_QUERY, "request wrong nr ns=%d",
++ verbose(VERB_QUERY, "request wrong nr ns=%d",
+ LDNS_NSCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+ if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) > 1) {
+- verbose(VERB_QUERY, "request wrong nr ar=%d",
++ verbose(VERB_QUERY, "request wrong nr ar=%d",
+ LDNS_ARCOUNT(sldns_buffer_begin(pkt)));
+- return worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ out->value = worker_err_ratelimit(worker, LDNS_RCODE_FORMERR);
++ return;
+ }
+- return 0;
++ out->value = 0;
++ return;
+ }
+
+-void
++void
+ worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg,
+ size_t len, int error, void* arg)
+ {
+@@ -388,7 +412,7 @@
+
+ /** check if a delegation is secure */
+ static enum sec_status
+-check_delegation_secure(struct reply_info *rep)
++check_delegation_secure(struct reply_info *rep)
+ {
+ /* return smallest security status */
+ size_t i;
+@@ -424,10 +448,10 @@
+ s = ((struct packed_rrset_data*)rep->rrsets[i]->entry.data)
+ ->security;
+ if(s != sec_status_secure) {
+- memmove(rep->rrsets+i, rep->rrsets+i+1,
+- sizeof(struct ub_packed_rrset_key*)*
++ memmove(rep->rrsets+i, rep->rrsets+i+1,
++ sizeof(struct ub_packed_rrset_key*)*
+ (rep->rrset_count - i - 1));
+- rep->ar_numrrsets--;
++ rep->ar_numrrsets--;
+ rep->rrset_count--;
+ i--;
+ }
+@@ -437,27 +461,28 @@
+ /** answer nonrecursive query from the cache */
+ static int
+ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
+- uint16_t id, uint16_t flags, struct comm_reply* repinfo,
++ uint16_t id, uint16_t flags, struct comm_reply* repinfo,
+ struct edns_data* edns)
+ {
+ /* for a nonrecursive query return either:
+ * o an error (servfail; we try to avoid this)
+ * o a delegation (closest we have; this routine tries that)
+- * o the answer (checked by answer_from_cache)
++ * o the answer (checked by answer_from_cache)
+ *
+- * So, grab a delegation from the rrset cache.
++ * So, grab a delegation from the rrset cache.
+ * Then check if it needs validation, if so, this routine fails,
+ * so that iterator can prime and validator can verify rrsets.
+ */
+ uint16_t udpsize = edns->udp_size;
+ int secure = 0;
+ time_t timenow = *worker->env.now;
+- int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd)
++ int has_cd_bit = (flags&BIT_CD);
++ int must_validate = (!has_cd_bit || worker->env.cfg->ignore_cd)
+ && worker->env.need_to_validate;
+ struct dns_msg *msg = NULL;
+ struct delegpt *dp;
+
+- dp = dns_cache_find_delegation(&worker->env, qinfo->qname,
++ dp = dns_cache_find_delegation(&worker->env, qinfo->qname,
+ qinfo->qname_len, qinfo->qtype, qinfo->qclass,
+ worker->scratchpad, &msg, timenow, 0, NULL, 0);
+ if(!dp) { /* no delegation, need to reprime */
+@@ -470,7 +495,7 @@
+ if(must_validate) {
+ switch(check_delegation_secure(msg->rep)) {
+ case sec_status_unchecked:
+- /* some rrsets have not been verified yet, go and
++ /* some rrsets have not been verified yet, go and
+ * let validator do that */
+ return 0;
+ case sec_status_bogus:
+@@ -484,13 +509,14 @@
+ msg->rep, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ return 0;
+- /* TODO store the reason for the bogus reply in cache
+- * and implement in here instead of the hardcoded EDE */
+- if (worker->env.cfg->ede) {
+- EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+- worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
++ /* Attach the cached EDE (RFC8914) */
++ if(worker->env.cfg->ede &&
++ msg->rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, msg->rep->reason_bogus,
++ msg->rep->reason_bogus_str);
+ }
+- error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
++ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ &msg->qinfo, id, flags, edns);
+ if(worker->stats.extended) {
+ worker->stats.ans_bogus++;
+@@ -517,11 +543,23 @@
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
++ if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
++ edns->edns_present = 0;
+ if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, msg->rep,
+ (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ return 0;
+ msg->rep->flags |= BIT_QR|BIT_RA;
++ /* Attach the cached EDE (RFC8914) if CD bit is set and the answer is
++ * bogus. */
++ if(worker->env.cfg->ede && has_cd_bit &&
++ (check_delegation_secure(msg->rep) == sec_status_bogus ||
++ check_delegation_secure(msg->rep) == sec_status_secure_sentinel_fail) &&
++ msg->rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, msg->rep->reason_bogus,
++ msg->rep->reason_bogus_str);
++ }
+ if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags,
+ repinfo->c->buffer, 0, 1, worker->scratchpad,
+ udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) {
+@@ -529,7 +567,7 @@
+ LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ edns->opt_list_inplace_cb_out = NULL;
+- error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
++ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ &msg->qinfo, id, flags, edns);
+ }
+ if(worker->stats.extended) {
+@@ -565,9 +603,10 @@
+
+ /* xxx_deny actions mean dropping the reply, unless the original reply
+ * was redirected to response-ip data. */
+- if((actinfo.action == respip_deny ||
++ if(actinfo.action == respip_always_deny ||
++ ((actinfo.action == respip_deny ||
+ actinfo.action == respip_inform_deny) &&
+- *encode_repp == rep)
++ *encode_repp == rep))
+ *encode_repp = NULL;
+
+ /* If address info is returned, it means the action should be an
+@@ -611,7 +650,8 @@
+ uint16_t udpsize = edns->udp_size;
+ struct reply_info* encode_rep = rep;
+ struct reply_info* partial_rep = *partial_repp;
+- int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd)
++ int has_cd_bit = (flags&BIT_CD);
++ int must_validate = (!has_cd_bit || worker->env.cfg->ignore_cd)
+ && worker->env.need_to_validate;
+ *partial_repp = NULL; /* avoid accidental further pass */
+
+@@ -665,15 +705,17 @@
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
++ if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
++ edns->edns_present = 0;
+ if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, rep,
+ LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
+ worker->env.now_tv))
+ goto bail_out;
+- /* TODO store the reason for the bogus reply in cache
+- * and implement in here instead of the hardcoded EDE */
+- if (worker->env.cfg->ede) {
+- EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+- worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
++ /* Attach the cached EDE (RFC8914) */
++ if(worker->env.cfg->ede && rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, rep->reason_bogus,
++ rep->reason_bogus_str);
+ }
+ error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
+ qinfo, id, flags, edns);
+@@ -705,10 +747,8 @@
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
+- if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, rep,
+- (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
+- worker->env.now_tv))
+- goto bail_out;
++ if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
++ edns->edns_present = 0;
+ *alias_rrset = NULL; /* avoid confusion if caller set it to non-NULL */
+ if((worker->daemon->use_response_ip || worker->daemon->use_rpz) &&
+ !partial_rep && !apply_respip_action(worker, qinfo, cinfo, rep,
+@@ -738,11 +778,24 @@
+ goto bail_out;
+ }
+ } else {
+- if (*is_expired_answer == 1 &&
++ if(*is_expired_answer == 1 &&
+ worker->env.cfg->ede_serve_expired && worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
+ worker->scratchpad, LDNS_EDE_STALE_ANSWER, "");
+ }
++ /* Attach the cached EDE (RFC8914) if CD bit is set and the
++ * answer is bogus. */
++ if(*is_secure_answer == 0 &&
++ worker->env.cfg->ede && has_cd_bit &&
++ encode_rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&edns->opt_list_out,
++ worker->scratchpad, encode_rep->reason_bogus,
++ encode_rep->reason_bogus_str);
++ }
++ if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, encode_rep,
++ (int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
++ worker->env.now_tv))
++ goto bail_out;
+ if(!reply_info_answer_encode(qinfo, encode_rep, id, flags,
+ repinfo->c->buffer, timenow, 1, worker->scratchpad,
+ udpsize, edns, (int)(edns->bits & EDNS_DO),
+@@ -763,7 +816,7 @@
+ return 1;
+
+ bail_out:
+- rrset_array_unlock_touch(worker->env.rrset_cache,
++ rrset_array_unlock_touch(worker->env.rrset_cache,
+ worker->scratchpad, rep->ref, rep->rrset_count);
+ return 0;
+ }
+@@ -793,7 +846,8 @@
+ if(modstack_find(&worker->env.mesh->mods, "subnetcache") != -1
+ && worker->env.unique_mesh) {
+ mesh_new_prefetch(worker->env.mesh, qinfo, flags, leeway +
+- PREFETCH_EXPIRY_ADD, rpz_passthru, repinfo, opt_list);
++ PREFETCH_EXPIRY_ADD, rpz_passthru,
++ &repinfo->client_addr, opt_list);
+ return;
+ }
+ #endif
+@@ -947,12 +1001,12 @@
+ struct config_file* cfg = w->env.cfg;
+ if(qinfo->qtype != LDNS_RR_TYPE_ANY && qinfo->qtype != LDNS_RR_TYPE_TXT)
+ return 0;
+- if(query_dname_compare(qinfo->qname,
++ if(query_dname_compare(qinfo->qname,
+ (uint8_t*)"\002id\006server") == 0 ||
+- query_dname_compare(qinfo->qname,
++ query_dname_compare(qinfo->qname,
+ (uint8_t*)"\010hostname\004bind") == 0)
+ {
+- if(cfg->hide_identity)
++ if(cfg->hide_identity)
+ return 0;
+ if(cfg->identity==NULL || cfg->identity[0]==0) {
+ char buf[MAXHOSTNAMELEN+1];
+@@ -967,12 +1021,12 @@
+ else chaos_replyonestr(pkt, cfg->identity, edns, w, repinfo);
+ return 1;
+ }
+- if(query_dname_compare(qinfo->qname,
++ if(query_dname_compare(qinfo->qname,
+ (uint8_t*)"\007version\006server") == 0 ||
+- query_dname_compare(qinfo->qname,
++ query_dname_compare(qinfo->qname,
+ (uint8_t*)"\007version\004bind") == 0)
+ {
+- if(cfg->hide_version)
++ if(cfg->hide_version)
+ return 0;
+ if(cfg->version==NULL || cfg->version[0]==0)
+ chaos_replyonestr(pkt, PACKAGE_STRING, edns, w, repinfo);
+@@ -1056,7 +1110,8 @@
+ deny_refuse(struct comm_point* c, enum acl_access acl,
+ enum acl_access deny, enum acl_access refuse,
+ struct worker* worker, struct comm_reply* repinfo,
+- struct acl_addr* acladdr, int ede)
++ struct acl_addr* acladdr, int ede,
++ struct check_request_result* check_result)
+ {
+ if(acl == deny) {
+ if(verbosity >= VERB_ALGO) {
+@@ -1079,9 +1134,16 @@
+
+ if(worker->stats.extended)
+ worker->stats.unwanted_queries++;
+- if(worker_check_request(c->buffer, worker) == -1) {
++ worker_check_request(c->buffer, worker, check_result);
++ if(check_result->value != 0) {
++ if(check_result->value != -1) {
++ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ check_result->value);
++ return 1;
++ }
+ comm_point_drop_reply(repinfo);
+- return 0; /* discard this */
++ return 0;
+ }
+ /* worker_check_request() above guarantees that the buffer contains at
+ * least a header and that qdcount == 1
+@@ -1131,7 +1193,7 @@
+ return 1;
+ }
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_REFUSED);
+
+ sldns_buffer_skip(c->buffer, (ssize_t)sizeof(uint16_t)); /* skip qtype */
+@@ -1146,7 +1208,7 @@
+ /* Skip through the RR records */
+ if(LDNS_ANCOUNT(sldns_buffer_begin(c->buffer)) != 0 ||
+ LDNS_NSCOUNT(sldns_buffer_begin(c->buffer)) != 0) {
+- if(!skip_pkt_rrs(c->buffer,
++ if(!skip_pkt_rrs(c->buffer,
+ ((int)LDNS_ANCOUNT(sldns_buffer_begin(c->buffer)))+
+ ((int)LDNS_NSCOUNT(sldns_buffer_begin(c->buffer))))) {
+ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+@@ -1235,7 +1297,8 @@
+ static int
+ deny_refuse_all(struct comm_point* c, enum acl_access* acl,
+ struct worker* worker, struct comm_reply* repinfo,
+- struct acl_addr** acladdr, int ede, int check_proxy)
++ struct acl_addr** acladdr, int ede, int check_proxy,
++ struct check_request_result* check_result)
+ {
+ if(check_proxy) {
+ *acladdr = acl_addr_lookup(worker->daemon->acl,
+@@ -1250,16 +1313,51 @@
+ }
+ *acl = acl_get_control(*acladdr);
+ return deny_refuse(c, *acl, acl_deny, acl_refuse, worker, repinfo,
+- *acladdr, ede);
++ *acladdr, ede, check_result);
+ }
+
+ static int
+ deny_refuse_non_local(struct comm_point* c, enum acl_access acl,
+ struct worker* worker, struct comm_reply* repinfo,
+- struct acl_addr* acladdr, int ede)
++ struct acl_addr* acladdr, int ede,
++ struct check_request_result* check_result)
+ {
+ return deny_refuse(c, acl, acl_deny_non_local, acl_refuse_non_local,
+- worker, repinfo, acladdr, ede);
++ worker, repinfo, acladdr, ede, check_result);
++}
++
++/* Returns 1 if the ip rate limit check can happen before EDNS parsing,
++ * else 0 */
++static int
++pre_edns_ip_ratelimit_check(enum acl_access acl)
++{
++ if(acl == acl_allow_cookie) return 0;
++ return 1;
++}
++
++/* Check if the query is blocked by source IP rate limiting.
++ * Returns 1 if it passes the check, 0 otherwise. */
++static int
++check_ip_ratelimit(struct worker* worker, struct sockaddr_storage* addr,
++ socklen_t addrlen, int has_cookie, sldns_buffer* pkt)
++{
++ if(!infra_ip_ratelimit_inc(worker->env.infra_cache, addr, addrlen,
++ *worker->env.now, has_cookie,
++ worker->env.cfg->ip_ratelimit_backoff, pkt)) {
++ /* See if we can pass through with slip factor */
++ if(!has_cookie && worker->env.cfg->ip_ratelimit_factor != 0 &&
++ ub_random_max(worker->env.rnd,
++ worker->env.cfg->ip_ratelimit_factor) == 0) {
++ char addrbuf[128];
++ addr_to_str(addr, addrlen, addrbuf, sizeof(addrbuf));
++ verbose(VERB_QUERY, "ip_ratelimit allowed through for "
++ "ip address %s because of slip in "
++ "ip_ratelimit_factor", addrbuf);
++ return 1;
++ }
++ return 0;
++ }
++ return 1;
+ }
+
+ int
+@@ -1275,11 +1373,13 @@
+ struct edns_option* original_edns_list = NULL;
+ enum acl_access acl;
+ struct acl_addr* acladdr;
++ int pre_edns_ip_ratelimit = 1;
+ int rc = 0;
+ int need_drop = 0;
+ int is_expired_answer = 0;
+ int is_secure_answer = 0;
+ int rpz_passthru = 0;
++ long long wait_queue_time = 0;
+ /* We might have to chase a CNAME chain internally, in which case
+ * we'll have up to two replies and combine them to build a complete
+ * answer. These variables control this case. */
+@@ -1288,6 +1388,8 @@
+ struct query_info* lookup_qinfo = &qinfo;
+ struct query_info qinfo_tmp; /* placeholder for lookup_qinfo */
+ struct respip_client_info* cinfo = NULL, cinfo_tmp;
++ struct timeval wait_time;
++ struct check_request_result check_result = {0,0};
+ memset(&qinfo, 0, sizeof(qinfo));
+
+ if((error != NETEVENT_NOERROR && error != NETEVENT_DONE)|| !repinfo) {
+@@ -1295,6 +1397,20 @@
+ verbose(VERB_ALGO, "handle request called with err=%d", error);
+ return 0;
+ }
++
++ if (worker->env.cfg->sock_queue_timeout && timeval_isset(&c->recv_tv)) {
++ timeval_subtract(&wait_time, worker->env.now_tv, &c->recv_tv);
++ wait_queue_time = wait_time.tv_sec * 1000000 + wait_time.tv_usec;
++ if (worker->stats.max_query_time_us < wait_queue_time)
++ worker->stats.max_query_time_us = wait_queue_time;
++ if(wait_queue_time >
++ (long long)(worker->env.cfg->sock_queue_timeout * 1000000)) {
++ /* count and drop queries that were sitting in the socket queue too long */
++ worker->stats.num_queries_timed_out++;
++ return 0;
++ }
++ }
++
+ #ifdef USE_DNSCRYPT
+ repinfo->max_udp_size = worker->daemon->cfg->max_udp_size;
+ if(!dnsc_handle_curved_request(worker->daemon->dnscenv, repinfo)) {
+@@ -1304,7 +1420,8 @@
+ if(c->dnscrypt && !repinfo->is_dnscrypted) {
+ char buf[LDNS_MAX_DOMAINLEN+1];
+ /* Check if this is unencrypted and asking for certs */
+- if(worker_check_request(c->buffer, worker) != 0) {
++ worker_check_request(c->buffer, worker, &check_result);
++ if(check_result.value != 0) {
+ verbose(VERB_ALGO,
+ "dnscrypt: worker check request: bad query.");
+ log_addr(VERB_CLIENT,"from",&repinfo->client_addr,
+@@ -1346,31 +1463,34 @@
+ if(worker->dtenv.log_client_query_messages) {
+ log_addr(VERB_ALGO, "request from client", &repinfo->client_addr, repinfo->client_addrlen);
+ log_addr(VERB_ALGO, "to local addr", (void*)repinfo->c->socket->addr->ai_addr, repinfo->c->socket->addr->ai_addrlen);
+- dt_msg_send_client_query(&worker->dtenv, &repinfo->client_addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer);
++ dt_msg_send_client_query(&worker->dtenv, &repinfo->client_addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer,
++ ((worker->env.cfg->sock_queue_timeout && timeval_isset(&c->recv_tv))?&c->recv_tv:NULL));
+ }
+ #endif
+ /* Check deny/refuse ACLs */
+ if(repinfo->is_proxied) {
+ if((ret=deny_refuse_all(c, &acl, worker, repinfo, &acladdr,
+- worker->env.cfg->ede, 1)) != -1) {
++ worker->env.cfg->ede, 1, &check_result)) != -1) {
+ if(ret == 1)
+ goto send_reply;
+ return ret;
+ }
+ }
+ if((ret=deny_refuse_all(c, &acl, worker, repinfo, &acladdr,
+- worker->env.cfg->ede, 0)) != -1) {
++ worker->env.cfg->ede, 0, &check_result)) != -1) {
+ if(ret == 1)
+ goto send_reply;
+ return ret;
+ }
+
+- if((ret=worker_check_request(c->buffer, worker)) != 0) {
++ worker_check_request(c->buffer, worker, &check_result);
++ if(check_result.value != 0) {
+ verbose(VERB_ALGO, "worker check request: bad query.");
+ log_addr(VERB_CLIENT,"from",&repinfo->client_addr, repinfo->client_addrlen);
+- if(ret != -1) {
++ if(check_result.value != -1) {
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ check_result.value);
+ return 1;
+ }
+ comm_point_drop_reply(repinfo);
+@@ -1378,33 +1498,21 @@
+ }
+
+ worker->stats.num_queries++;
+-
+- /* check if this query should be dropped based on source ip rate limiting
+- * NOTE: we always check the repinfo->client_address. IP ratelimiting is
+- * implicitly disabled for proxies. */
+- if(!infra_ip_ratelimit_inc(worker->env.infra_cache,
+- &repinfo->client_addr, repinfo->client_addrlen,
+- *worker->env.now,
+- worker->env.cfg->ip_ratelimit_backoff, c->buffer)) {
+- /* See if we are passed through with slip factor */
+- if(worker->env.cfg->ip_ratelimit_factor != 0 &&
+- ub_random_max(worker->env.rnd,
+- worker->env.cfg->ip_ratelimit_factor) == 0) {
+- char addrbuf[128];
+- addr_to_str(&repinfo->client_addr,
+- repinfo->client_addrlen, addrbuf,
+- sizeof(addrbuf));
+- verbose(VERB_QUERY, "ip_ratelimit allowed through for "
+- "ip address %s because of slip in "
+- "ip_ratelimit_factor", addrbuf);
+- } else {
++ pre_edns_ip_ratelimit = pre_edns_ip_ratelimit_check(acl);
++
++ /* If the IP rate limiting check needs extra EDNS information (e.g.,
++ * DNS Cookies) postpone the check until after EDNS is parsed. */
++ if(pre_edns_ip_ratelimit) {
++ /* NOTE: we always check the repinfo->client_address.
++ * IP ratelimiting is implicitly disabled for proxies. */
++ if(!check_ip_ratelimit(worker, &repinfo->client_addr,
++ repinfo->client_addrlen, 0, c->buffer)) {
+ worker->stats.num_queries_ip_ratelimited++;
+ comm_point_drop_reply(repinfo);
+ return 0;
+ }
+ }
+
+- /* see if query is in the cache */
+ if(!query_info_parse(&qinfo, c->buffer)) {
+ verbose(VERB_ALGO, "worker parse request: formerror.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+@@ -1416,7 +1524,7 @@
+ }
+ sldns_buffer_rewind(c->buffer);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ goto send_reply;
+ }
+@@ -1425,21 +1533,21 @@
+ addr_to_str(&repinfo->client_addr, repinfo->client_addrlen, ip, sizeof(ip));
+ log_query_in(ip, qinfo.qname, qinfo.qtype, qinfo.qclass);
+ }
+- if(qinfo.qtype == LDNS_RR_TYPE_AXFR ||
++ if(qinfo.qtype == LDNS_RR_TYPE_AXFR ||
+ qinfo.qtype == LDNS_RR_TYPE_IXFR) {
+ verbose(VERB_ALGO, "worker request: refused zone transfer.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+ repinfo->client_addrlen);
+ sldns_buffer_rewind(c->buffer);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_REFUSED);
+ if(worker->stats.extended) {
+ worker->stats.qtype[qinfo.qtype]++;
+ }
+ goto send_reply;
+ }
+- if(qinfo.qtype == LDNS_RR_TYPE_OPT ||
++ if(qinfo.qtype == LDNS_RR_TYPE_OPT ||
+ qinfo.qtype == LDNS_RR_TYPE_TSIG ||
+ qinfo.qtype == LDNS_RR_TYPE_TKEY ||
+ qinfo.qtype == LDNS_RR_TYPE_MAILA ||
+@@ -1454,23 +1562,23 @@
+ }
+ sldns_buffer_rewind(c->buffer);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_FORMERR);
+ if(worker->stats.extended) {
+ worker->stats.qtype[qinfo.qtype]++;
+ }
+ goto send_reply;
+ }
+- if((ret=parse_edns_from_query_pkt(c->buffer, &edns, worker->env.cfg, c,
+- worker->scratchpad)) != 0) {
++ if((ret=parse_edns_from_query_pkt(
++ c->buffer, &edns, worker->env.cfg, c, repinfo,
++ (worker->env.now ? *worker->env.now : time(NULL)),
++ worker->scratchpad)) != 0) {
+ struct edns_data reply_edns;
+ verbose(VERB_ALGO, "worker parse edns: formerror.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+ repinfo->client_addrlen);
+ memset(&reply_edns, 0, sizeof(reply_edns));
+ reply_edns.edns_present = 1;
+- reply_edns.udp_size = EDNS_ADVERTISED_SIZE;
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
+ error_encode(c->buffer, ret, &qinfo,
+ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+ sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns);
+@@ -1479,23 +1587,15 @@
+ }
+ if(edns.edns_present) {
+ if(edns.edns_version != 0) {
+- edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4);
+- edns.edns_version = EDNS_ADVERTISED_VERSION;
+- edns.udp_size = EDNS_ADVERTISED_SIZE;
+- edns.bits &= EDNS_DO;
+ edns.opt_list_in = NULL;
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+- edns.padding_block_size = 0;
+ verbose(VERB_ALGO, "query with bad edns version.");
+ log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
+ repinfo->client_addrlen);
+- error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo,
++ extended_error_encode(c->buffer, EDNS_RCODE_BADVERS, &qinfo,
+ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+- sldns_buffer_read_u16_at(c->buffer, 2), NULL);
+- if(sldns_buffer_capacity(c->buffer) >=
+- sldns_buffer_limit(c->buffer)+calc_edns_field_size(&edns))
+- attach_edns_record(c->buffer, &edns);
++ sldns_buffer_read_u16_at(c->buffer, 2), 0, &edns);
+ regional_free_all(worker->scratchpad);
+ goto send_reply;
+ }
+@@ -1508,6 +1608,62 @@
+ edns.udp_size = NORMAL_UDP_SIZE;
+ }
+ }
++
++ /* Get stats for cookies */
++ server_stats_downstream_cookie(&worker->stats, &edns);
++
++ /* If the IP rate limiting check was postponed, check now. */
++ if(!pre_edns_ip_ratelimit) {
++ /* NOTE: we always check the repinfo->client_address.
++ * IP ratelimiting is implicitly disabled for proxies. */
++ if(!check_ip_ratelimit(worker, &repinfo->client_addr,
++ repinfo->client_addrlen, edns.cookie_valid,
++ c->buffer)) {
++ worker->stats.num_queries_ip_ratelimited++;
++ comm_point_drop_reply(repinfo);
++ return 0;
++ }
++ }
++
++ /* "if, else if" sequence below deals with downstream DNS Cookies */
++ if(acl != acl_allow_cookie)
++ ; /* pass; No cookie downstream processing whatsoever */
++
++ else if(edns.cookie_valid)
++ ; /* pass; Valid cookie is good! */
++
++ else if(c->type != comm_udp)
++ ; /* pass; Stateful transport */
++
++ else if(edns.cookie_present) {
++ /* Cookie present, but not valid: Cookie was bad! */
++ extended_error_encode(c->buffer,
++ LDNS_EXT_RCODE_BADCOOKIE, &qinfo,
++ *(uint16_t*)(void *)
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_read_u16_at(c->buffer, 2),
++ 0, &edns);
++ regional_free_all(worker->scratchpad);
++ goto send_reply;
++ } else {
++ /* Cookie required, but no cookie present on UDP */
++ verbose(VERB_ALGO, "worker request: "
++ "need cookie or stateful transport");
++ log_addr(VERB_ALGO, "from",&repinfo->remote_addr
++ , repinfo->remote_addrlen);
++ EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
++ worker->scratchpad, LDNS_EDE_OTHER,
++ "DNS Cookie needed for UDP replies");
++ error_encode(c->buffer,
++ (LDNS_RCODE_REFUSED|BIT_TC), &qinfo,
++ *(uint16_t*)(void *)
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_read_u16_at(c->buffer, 2),
++ &edns);
++ regional_free_all(worker->scratchpad);
++ goto send_reply;
++ }
++
+ if(edns.udp_size > worker->daemon->cfg->max_udp_size &&
+ c->type == comm_udp) {
+ verbose(VERB_QUERY,
+@@ -1523,10 +1679,10 @@
+ repinfo->client_addrlen);
+ LDNS_QR_SET(sldns_buffer_begin(c->buffer));
+ LDNS_TC_SET(sldns_buffer_begin(c->buffer));
+- LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
++ LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
+ LDNS_RCODE_SERVFAIL);
+ sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
+- sldns_buffer_write_at(c->buffer, 4,
++ sldns_buffer_write_at(c->buffer, 4,
+ (uint8_t*)"\0\0\0\0\0\0\0\0", 8);
+ sldns_buffer_flip(c->buffer);
+ regional_free_all(worker->scratchpad);
+@@ -1593,7 +1749,7 @@
+ /* We've looked in our local zones. If the answer isn't there, we
+ * might need to bail out based on ACLs now. */
+ if((ret=deny_refuse_non_local(c, acl, worker, repinfo, acladdr,
+- worker->env.cfg->ede)) != -1)
++ worker->env.cfg->ede, &check_result)) != -1)
+ {
+ regional_free_all(worker->scratchpad);
+ if(ret == 1)
+@@ -1612,7 +1768,7 @@
+ * ACLs allow the snooping. */
+ if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) &&
+ acl != acl_allow_snoop ) {
+- if (worker->env.cfg->ede) {
++ if(worker->env.cfg->ede) {
+ EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
+ worker->scratchpad, LDNS_EDE_NOT_AUTHORITATIVE, "");
+ }
+@@ -1745,8 +1901,8 @@
+
+ if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) {
+ if(answer_norec_from_cache(worker, &qinfo,
+- *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
+- sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
++ *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
++ sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
+ &edns)) {
+ regional_free_all(worker->scratchpad);
+ goto send_reply;
+@@ -1825,10 +1981,10 @@
+ return rc;
+ }
+
+-void
++void
+ worker_sighandler(int sig, void* arg)
+ {
+- /* note that log, print, syscalls here give race conditions.
++ /* note that log, print, syscalls here give race conditions.
+ * And cause hangups if the log-lock is held by the application. */
+ struct worker* worker = (struct worker*)arg;
+ switch(sig) {
+@@ -1903,13 +2059,13 @@
+ comm_timer_set(worker->env.probe_timer, &tv);
+ }
+
+-struct worker*
++struct worker*
+ worker_create(struct daemon* daemon, int id, int* ports, int n)
+ {
+ unsigned int seed;
+- struct worker* worker = (struct worker*)calloc(1,
++ struct worker* worker = (struct worker*)calloc(1,
+ sizeof(struct worker));
+- if(!worker)
++ if(!worker)
+ return NULL;
+ worker->numports = n;
+ worker->ports = (int*)memdup(ports, sizeof(int)*n);
+@@ -1937,7 +2093,7 @@
+ }
+
+ int
+-worker_init(struct worker* worker, struct config_file *cfg,
++worker_init(struct worker* worker, struct config_file *cfg,
+ struct listen_port* ports, int do_sigs)
+ {
+ #ifdef USE_DNSTAP
+@@ -1970,9 +2126,9 @@
+ #endif
+ ub_thread_sig_unblock(SIGTERM);
+ #ifndef LIBEVENT_SIGNAL_PROBLEM
+- worker->comsig = comm_signal_create(worker->base,
++ worker->comsig = comm_signal_create(worker->base,
+ worker_sighandler, worker);
+- if(!worker->comsig
++ if(!worker->comsig
+ #ifdef SIGHUP
+ || !comm_signal_bind(worker->comsig, SIGHUP)
+ #endif
+@@ -1989,7 +2145,7 @@
+ return 0;
+ }
+ #endif /* LIBEVENT_SIGNAL_PROBLEM */
+- if(!daemon_remote_open_accept(worker->daemon->rc,
++ if(!daemon_remote_open_accept(worker->daemon->rc,
+ worker->daemon->rc_ports, worker)) {
+ worker_delete(worker);
+ return 0;
+@@ -2023,8 +2179,8 @@
+ return 0;
+ }
+ worker->back = outside_network_create(worker->base,
+- cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports,
+- cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6,
++ cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports,
++ cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6,
+ cfg->do_tcp?cfg->outgoing_num_tcp:0, cfg->ip_dscp,
+ worker->daemon->env->infra_cache, worker->rndstate,
+ cfg->use_caps_bits_for_id, worker->ports, worker->numports,
+@@ -2049,13 +2205,13 @@
+ worker_delete(worker);
+ return 0;
+ }
+- worker->stat_timer = comm_timer_create(worker->base,
++ worker->stat_timer = comm_timer_create(worker->base,
+ worker_stat_timer_cb, worker);
+ if(!worker->stat_timer) {
+ log_err("could not create statistics timer");
+ }
+
+- /* we use the msg_buffer_size as a good estimate for what the
++ /* we use the msg_buffer_size as a good estimate for what the
+ * user wants for memory usage sizes */
+ worker->scratchpad = regional_create_custom(cfg->msg_buffer_size);
+ if(!worker->scratchpad) {
+@@ -2164,23 +2320,24 @@
+ worker_mem_report(worker, NULL);
+ /* if statistics enabled start timer */
+ if(worker->env.cfg->stat_interval > 0) {
+- verbose(VERB_ALGO, "set statistics interval %d secs",
++ verbose(VERB_ALGO, "set statistics interval %d secs",
+ worker->env.cfg->stat_interval);
+ worker_restart_timer(worker);
+ }
++ pp_init(&sldns_write_uint16, &sldns_write_uint32);
+ return 1;
+ }
+
+-void
++void
+ worker_work(struct worker* worker)
+ {
+ comm_base_dispatch(worker->base);
+ }
+
+-void
++void
+ worker_delete(struct worker* worker)
+ {
+- if(!worker)
++ if(!worker)
+ return;
+ if(worker->env.mesh && verbosity >= VERB_OPS) {
+ server_stats_log(&worker->stats, worker, worker->thread_num);
+@@ -2232,7 +2389,7 @@
+ struct worker* worker = q->env->worker;
+ struct outbound_entry* e = (struct outbound_entry*)regional_alloc(
+ q->region, sizeof(*e));
+- if(!e)
++ if(!e)
+ return NULL;
+ e->qstate = q;
+ e->qsent = outnet_serviced_query(worker->back, qinfo, flags, dnssec,
+@@ -2246,7 +2403,7 @@
+ return e;
+ }
+
+-void
++void
+ worker_alloc_cleanup(void* arg)
+ {
+ struct worker* worker = (struct worker*)arg;
+@@ -2294,7 +2451,7 @@
+ return 0;
+ }
+
+-int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
++int libworker_handle_service_reply(struct comm_point* ATTR_UNUSED(c),
+ void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+ struct comm_reply* ATTR_UNUSED(reply_info))
+ {
+--- contrib/unbound/dns64/dns64.c.orig
++++ contrib/unbound/dns64/dns64.c
+@@ -59,7 +59,7 @@
+ ******************************************************************************/
+
+ /**
+- * This is the default DNS64 prefix that is used whent he dns64 module is listed
++ * This is the default DNS64 prefix that is used when the dns64 module is listed
+ * in module-config but when the dns64-prefix variable is not present.
+ */
+ static const char DEFAULT_DNS64_PREFIX[] = "64:ff9b::/96";
+@@ -573,28 +573,29 @@
+ handle_event_pass(struct module_qstate* qstate, int id)
+ {
+ struct dns64_qstate* iq = (struct dns64_qstate*)qstate->minfo[id];
+- if (iq && iq->state == DNS64_NEW_QUERY
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
+- && qstate->qinfo.qname_len == 74
+- && !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa"))
+- /* Handle PTR queries for IPv6 addresses. */
+- return handle_ipv6_ptr(qstate, id);
+-
+- if (qstate->env->cfg->dns64_synthall &&
+- iq && iq->state == DNS64_NEW_QUERY
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA)
+- return generate_type_A_query(qstate, id);
++ int synth_all_cfg = qstate->env->cfg->dns64_synthall;
++ int synth_qname = 0;
++
++ if(iq && iq->state == DNS64_NEW_QUERY
++ && qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
++ && qstate->qinfo.qname_len == 74
++ && !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa")) {
++ /* Handle PTR queries for IPv6 addresses. */
++ return handle_ipv6_ptr(qstate, id);
++ }
+
+- if(dns64_always_synth_for_qname(qstate, id) &&
+- iq && iq->state == DNS64_NEW_QUERY
+- && !(qstate->query_flags & BIT_CD)
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) {
+- verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
++ if(iq && iq->state == DNS64_NEW_QUERY &&
++ qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
++ (synth_all_cfg ||
++ (synth_qname=(dns64_always_synth_for_qname(qstate, id)
++ && !(qstate->query_flags & BIT_CD))))) {
++ if(synth_qname)
++ verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
+ return generate_type_A_query(qstate, id);
+ }
+
+ /* We are finished when our sub-query is finished. */
+- if (iq && iq->state == DNS64_SUBQUERY_FINISHED)
++ if(iq && iq->state == DNS64_SUBQUERY_FINISHED)
+ return module_finished;
+
+ /* Otherwise, pass request to next module. */
+@@ -627,32 +628,37 @@
+ * synthesize in (sec 5.1.2 of RFC6147).
+ * - A successful AAAA query with an answer.
+ */
+- if((!iq || iq->state != DNS64_INTERNAL_QUERY)
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA
+- && !(qstate->query_flags & BIT_CD)
+- && !(qstate->return_msg &&
+- qstate->return_msg->rep &&
+- reply_find_answer_rrset(&qstate->qinfo,
+- qstate->return_msg->rep)))
+- /* not internal, type AAAA, not CD, and no answer RRset,
+- * So, this is a AAAA noerror/nodata answer */
+- return generate_type_A_query(qstate, id);
+
+- if((!iq || iq->state != DNS64_INTERNAL_QUERY)
+- && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA
+- && !(qstate->query_flags & BIT_CD)
+- && dns64_always_synth_for_qname(qstate, id)) {
+- /* if it is not internal, AAAA, not CD and listed domain,
+- * generate from A record and ignore AAAA */
+- verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
++ /* When an AAAA query completes check if we want to perform DNS64
++ * synthesis. We skip queries with DNSSEC enabled (!CD) and
++ * ones generated by us to retrive the A/PTR record to use for
++ * synth. */
++ int could_synth =
++ qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
++ (!iq || iq->state != DNS64_INTERNAL_QUERY) &&
++ !(qstate->query_flags & BIT_CD);
++ int has_data = /* whether query returned non-empty rrset */
++ qstate->return_msg &&
++ qstate->return_msg->rep &&
++ reply_find_answer_rrset(&qstate->qinfo, qstate->return_msg->rep);
++ int synth_qname = 0;
++
++ if(could_synth &&
++ (!has_data ||
++ (synth_qname=dns64_always_synth_for_qname(qstate, id)))) {
++ if(synth_qname)
++ verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
+ return generate_type_A_query(qstate, id);
+ }
+
+ /* Store the response in cache. */
+- if ( (!iq || !iq->started_no_cache_store) &&
+- qstate->return_msg && qstate->return_msg->rep &&
+- !dns_cache_store(qstate->env, &qstate->qinfo, qstate->return_msg->rep,
+- 0, 0, 0, NULL, qstate->query_flags, qstate->qstarttime))
++ if( (!iq || !iq->started_no_cache_store) &&
++ qstate->return_msg &&
++ qstate->return_msg->rep &&
++ !dns_cache_store(
++ qstate->env, &qstate->qinfo, qstate->return_msg->rep,
++ 0, 0, 0, NULL,
++ qstate->query_flags, qstate->qstarttime))
+ log_err("out of memory");
+
+ /* do nothing */
+@@ -841,7 +847,7 @@
+ cp = construct_reply_info_base(super->region, rep->flags, rep->qdcount,
+ rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl,
+ rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets,
+- rep->rrset_count, rep->security);
++ rep->rrset_count, rep->security, LDNS_EDE_NONE);
+ if(!cp)
+ return;
+
+@@ -969,10 +975,19 @@
+ }
+ super_dq->state = DNS64_SUBQUERY_FINISHED;
+
+- /* If there is no successful answer, we're done. */
+- if (qstate->return_rcode != LDNS_RCODE_NOERROR
+- || !qstate->return_msg
+- || !qstate->return_msg->rep) {
++ /* If there is no successful answer, we're done.
++ * Guarantee that we have at least a NOERROR reply further on. */
++ if(qstate->return_rcode != LDNS_RCODE_NOERROR
++ || !qstate->return_msg
++ || !qstate->return_msg->rep) {
++ return;
++ }
++
++ /* When no A record is found for synthesis fall back to AAAA again. */
++ if(qstate->qinfo.qtype == LDNS_RR_TYPE_A &&
++ !reply_find_answer_rrset(&qstate->qinfo,
++ qstate->return_msg->rep)) {
++ super_dq->state = DNS64_INTERNAL_QUERY;
+ return;
+ }
+
+--- contrib/unbound/dnstap/dnstap.c.orig
++++ contrib/unbound/dnstap/dnstap.c
+@@ -388,12 +388,15 @@
+ struct sockaddr_storage *qsock,
+ struct sockaddr_storage *rsock,
+ enum comm_point_type cptype,
+- sldns_buffer *qmsg)
++ sldns_buffer *qmsg,
++ struct timeval* tstamp)
+ {
+ struct dt_msg dm;
+ struct timeval qtime;
+
+- gettimeofday(&qtime, NULL);
++ if(tstamp)
++ memcpy(&qtime, tstamp, sizeof(qtime));
++ else gettimeofday(&qtime, NULL);
+
+ /* type */
+ dt_msg_init(env, &dm, DNSTAP__MESSAGE__TYPE__CLIENT_QUERY);
+--- contrib/unbound/dnstap/dnstap.h.orig
++++ contrib/unbound/dnstap/dnstap.h
+@@ -126,13 +126,15 @@
+ * @param rsock: local (service) address/port.
+ * @param cptype: comm_udp or comm_tcp.
+ * @param qmsg: query message.
++ * @param tstamp: timestamp or NULL if none provided.
+ */
+ void
+ dt_msg_send_client_query(struct dt_env *env,
+ struct sockaddr_storage *qsock,
+ struct sockaddr_storage *rsock,
+ enum comm_point_type cptype,
+- struct sldns_buffer *qmsg);
++ struct sldns_buffer *qmsg,
++ struct timeval* tstamp);
+
+ /**
+ * Create and send a new dnstap "Message" event of type CLIENT_RESPONSE.
+--- contrib/unbound/dnstap/dnstap.m4.orig
++++ contrib/unbound/dnstap/dnstap.m4
+@@ -41,7 +41,7 @@
+ fi
+ ])
+ AC_SEARCH_LIBS([protobuf_c_message_pack], [protobuf-c], [],
+- AC_MSG_ERROR([The protobuf-c library was not found. Please install protobuf-c!]))
++ AC_MSG_ERROR([The protobuf-c library was not found. Please install the development libraries for protobuf-c!]))
+ $2
+ else
+ $3
+--- contrib/unbound/dnstap/dtstream.c.orig
++++ contrib/unbound/dnstap/dtstream.c
+@@ -788,7 +788,7 @@
+ }
+ return -1;
+ }
+- log_crypto_err("dnstap io, could not SSL_write");
++ log_crypto_err_io("dnstap io, could not SSL_write", want);
+ return -1;
+ }
+ return r;
+@@ -1029,7 +1029,7 @@
+ "other side");
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ verbose(VERB_DETAIL, "dnstap io: output closed by the "
+ "other side");
+ return 0;
+@@ -1431,8 +1431,8 @@
+ } else {
+ unsigned long err = ERR_get_error();
+ if(!squelch_err_ssl_handshake(err)) {
+- log_crypto_err_code("dnstap io, ssl handshake failed",
+- err);
++ log_crypto_err_io_code("dnstap io, ssl handshake failed",
++ want, err);
+ verbose(VERB_OPS, "dnstap io, ssl handshake failed "
+ "from %s", dtio->ip_str);
+ }
+--- contrib/unbound/dnstap/unbound-dnstap-socket.c.orig
++++ contrib/unbound/dnstap/unbound-dnstap-socket.c
+@@ -61,6 +61,7 @@
+ #include "services/listen_dnsport.h"
+ #include "sldns/sbuffer.h"
+ #include "sldns/wire2str.h"
++#include "sldns/pkthdr.h"
+ #ifdef USE_DNSTAP
+ #include <protobuf-c/protobuf-c.h>
+ #include "dnstap/dnstap.pb-c.h"
+@@ -448,6 +449,7 @@
+ char buf[300];
+ /* header, name, type, class minimum to get the query tuple */
+ if(message.len < 12 + 1 + 4 + 4) return NULL;
++ if(LDNS_QDCOUNT(message.data) < 1) return NULL;
+ if(sldns_wire2str_rrquestion_buf(message.data+12, message.len-12,
+ buf, sizeof(buf)) != 0) {
+ /* remove trailing newline, tabs to spaces */
+@@ -502,7 +504,7 @@
+ time_t time_t_sec;
+ memset(&tv, 0, sizeof(tv));
+ if(has_time_sec) tv.tv_sec = time_sec;
+- if(has_time_nsec) tv.tv_usec = time_nsec;
++ if(has_time_nsec) tv.tv_usec = time_nsec/1000;
+
+ buf[0]=0;
+ time_t_sec = tv.tv_sec;
+@@ -706,7 +708,7 @@
+ (data->id?data->id:""));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ if(verbosity) log_info("dnstap client stream closed from %s",
+ (data->id?data->id:""));
+ return 0;
+@@ -758,10 +760,11 @@
+ fd_set_block(data->fd);
+ if(data->ssl) {
+ if((r=SSL_write(data->ssl, acceptframe, len)) <= 0) {
+- if(SSL_get_error(data->ssl, r) == SSL_ERROR_ZERO_RETURN)
++ int r2;
++ if((r2=SSL_get_error(data->ssl, r)) == SSL_ERROR_ZERO_RETURN)
+ log_err("SSL_write, peer closed connection");
+ else
+- log_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", r2);
+ fd_set_nonblock(data->fd);
+ free(acceptframe);
+ return 0;
+@@ -789,7 +792,7 @@
+
+ /** reply with FINISH control frame to bidirectional client,
+ * returns 0 on error */
+-static int reply_with_finish(int fd)
++static int reply_with_finish(struct tap_data* data)
+ {
+ #ifdef USE_DNSTAP
+ size_t len = 0;
+@@ -799,21 +802,35 @@
+ return 0;
+ }
+
+- fd_set_block(fd);
+- if(send(fd, finishframe, len, 0) == -1) {
+- log_err("send failed: %s", sock_strerror(errno));
+- fd_set_nonblock(fd);
+- free(finishframe);
+- return 0;
++ fd_set_block(data->fd);
++ if(data->ssl) {
++ int r;
++ if((r=SSL_write(data->ssl, finishframe, len)) <= 0) {
++ int r2;
++ if((r2=SSL_get_error(data->ssl, r)) == SSL_ERROR_ZERO_RETURN)
++ log_err("SSL_write, peer closed connection");
++ else
++ log_crypto_err_io("could not SSL_write", r2);
++ fd_set_nonblock(data->fd);
++ free(finishframe);
++ return 0;
++ }
++ } else {
++ if(send(data->fd, finishframe, len, 0) == -1) {
++ log_err("send failed: %s", sock_strerror(errno));
++ fd_set_nonblock(data->fd);
++ free(finishframe);
++ return 0;
++ }
+ }
+ if(verbosity) log_info("sent control frame(finish)");
+
+- fd_set_nonblock(fd);
++ fd_set_nonblock(data->fd);
+ free(finishframe);
+ return 1;
+ #else
+ log_err("no dnstap compiled, no reply");
+- (void)fd;
++ (void)data;
+ return 0;
+ #endif
+ }
+@@ -933,7 +950,7 @@
+ #endif /* HAVE_SSL */
+
+ /** callback for dnstap listener */
+-void dtio_tap_callback(int fd, short ATTR_UNUSED(bits), void* arg)
++void dtio_tap_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), void* arg)
+ {
+ struct tap_data* data = (struct tap_data*)arg;
+ if(verbosity>=3) log_info("tap callback");
+@@ -1016,7 +1033,7 @@
+ }
+ } else if(data->len >= 4 && sldns_read_uint32(data->frame) ==
+ FSTRM_CONTROL_FRAME_STOP && data->is_bidirectional) {
+- if(!reply_with_finish(fd)) {
++ if(!reply_with_finish(data)) {
+ tap_data_free(data);
+ return;
+ }
+--- contrib/unbound/doc/Changelog.orig
++++ contrib/unbound/doc/Changelog
+@@ -1,3 +1,476 @@
++2 November 2023: Wouter
++ - Set version number to 1.19.0.
++ - Tag for 1.19.0rc1 release.
++
++1 November 2023: George
++ - Mention flex and bison in README.md when building from repository
++ source.
++
++1 November 2023: Wouter
++ - Fix SSL compile failure for definition in log_crypto_err_io_code_arg.
++ - Fix SSL compile failure for other missing definitions in
++ log_crypto_err_io_code_arg.
++ - Fix compilation without openssl, remove unused function warning.
++
++31 October 2023: George
++ - Fix #941: dnscrypt doesn't work after upgrade to 1.18 with
++ suggestion by dukeartem to also fix the udp_ancil with dnscrypt.
++
++30 October 2023: George
++ - Merge #930 from Stuart Henderson: add void to
++ log_ident_revert_to_default declaration.
++
++30 October 2023: Wouter
++ - autoconf.
++
++24 October 2023: George
++ - Clearer configure text for missing protobuf-c development libraries.
++
++20 October 2023: Wouter
++ - Merge #951: Cachedb no store. The cachedb-no-store: yes option is
++ used to stop cachedb from writing messages to the backend storage.
++ It reads messages when data is available from the backend. The
++ default is no.
++
++19 October 2023: Wouter
++ - Fix to print detailed errors when an SSL IO routine fails via
++ SSL_get_error.
++
++18 October 2023: George
++ - Mailing list patches from Daniel Gröber for DNS64 fallback to plain
++ AAAA when no A record exists for synthesis, and minor DNS64 code
++ refactoring for better readability.
++ - Fixes for the DNS64 patches.
++ - Update the dns64_lookup.rpl test for the DNS64 fallback patch.
++ - Merge #955 from buevsan: fix ipset wrong behavior.
++ - Update testdata/ipset.tdir test for ipset fix.
++
++17 October 2023: Wouter
++ - Fix #954: Inconsistent RPZ handling for A record returned along with
++ CNAME.
++
++16 October 2023: George
++ - Expose the script filename in the Python module environment 'mod_env'
++ instead of the config_file structure which includes the linked list
++ of scripts in a multi Python module setup; fixes #79.
++ - Expose the configured listening and outgoing interfaces, if any, as
++ a list of strings in the Python 'config_file' class instead of the
++ current Swig object proxy; fixes #79.
++ - For multi Python module setups, clean previously parsed module
++ functions in __main__'s dictionary, if any, so that only current
++ module functions are registered.
++
++13 October 2023: George
++ - Better fix for infinite loop when reading multiple lines of input on
++ a broken remote control socket, by treating a zero byte line the
++ same as transmission end. Addesses #947 and #948.
++
++12 October 2023: Wouter
++ - Merge #944: Disable EDNS DO.
++ Disable the EDNS DO flag in upstream requests. This can be helpful
++ for devices that cannot handle DNSSEC information. But it should not
++ be enabled otherwise, because that would stop DNSSEC validation. The
++ DNSSEC validation would not work for Unbound itself, and also not
++ for downstream users. Default is no. The option
++ is disable-edns-do: no
++
++11 October 2023: George
++ - Fix #850: [FR] Ability to use specific database in Redis, with new
++ redis-logical-db configuration option.
++
++11 October 2023: Wouter
++ - Fix #949: "could not create control compt".
++ - Fix that cachedb does not warn when serve-expired is disabled about
++ use of serve-expired-reply-ttl and serve-expired-client-timeout.
++ - Fix for #949: Fix pythonmod/ubmodule-tst.py for Python 3.x.
++
++10 October 2023: George
++ - Fix infinite loop when reading multiple lines of input on a broken
++ remote control socket. Addesses #947 and #948.
++
++9 October 2023: Wouter
++ - Fix edns subnet so that queries with a source prefix of zero cause
++ the recursor send no edns subnet option to the upstream.
++ - Fix that printout of EDNS options shows the EDNS cookie option by
++ name.
++
++4 October 2023: Wouter
++ - Fix #946: Forwarder returns servfail on upstream response noerror no
++ data.
++
++3 October 2023: George
++ - Merge #881: Generalise the proxy protocol code.
++
++2 October 2023: George
++ - Fix misplaced comment.
++
++22 September 2023: Wouter
++ - Fix #942: 1.18.0 libunbound DNS regression when built without
++ OpenSSL.
++
++18 September 2023: Wouter
++ - Fix rpz tcp-only action with rpz triggers nsdname and nsip.
++
++15 September 2023: Wouter
++ - Merge #936: Check for c99 with autoconf versions prior to 2.70.
++ - Fix to remove two c99 notations.
++
++14 September 2023: Wouter
++ - Fix authority zone answers for obscured DNAMEs and delegations.
++
++8 September 2023: Wouter
++ - Fix send of udp retries when ENOBUFS is returned. It stops looping
++ and also waits for the condition to go away. Reported by Florian
++ Obser.
++
++7 September 2023: Wouter
++ - Fix to scrub resource records of type A and AAAA that have an
++ inappropriate size. They are removed from responses.
++ - Fix to move msgparse_rrset_remove_rr code to util/msgparse.c.
++ - Fix to add EDE text when RRs have been removed due to length.
++ - Fix to set ede match in unit test for rr length removal.
++ - Fix to print EDE text in readable form in output logs.
++
++6 September 2023: Wouter
++ - Merge #931: Prevent warnings from -Wmissing-prototypes.
++
++31 August 2023: Wouter
++ - Fix autoconf 2.69 warnings in configure.
++ - Fix #927: unbound 1.18.0 make test error. Fix make test without SHA1.
++
++30 August 2023: Wouter
++ - Fix for WKS call to getservbyname that creates allocation on exit
++ in unit test by testing numbers first and testing from the services
++ list later.
++
++28 August 2023: Wouter
++ - Fix for version generation race condition that ignored changes.
++
++25 August 2023: Wouter
++ - Fix compile error on NetBSD in util/netevent.h.
++
++23 August 2023: Wouter
++ - Tag for 1.18.0rc1 release. This became the 1.18.0 release on
++ 30 aug 2023, with the fix from 25 aug, fix compile on NetBSD
++ included. The repository continues with version 1.18.1.
++
++22 August 2023: Wouter
++ - Set version number to 1.18.0.
++
++21 August 2023: Wouter
++ - Debug Windows ci workflow.
++ - Fix windows ci workflow to install bison and flex.
++ - Fix for #925: unbound.service: Main process exited, code=killed,
++ status=11/SEGV. Fixes cachedb configuration handling.
++ - Fix #923: processQueryResponse() THROWAWAY should be mindful of
++ fail_reply.
++ - Fix unit test for unbound-control to work when threads are disabled,
++ and fix cache dump check.
++
++18 August 2023: Wouter
++ - Fix for iter_dec_attempts that could cause a hang, part of
++ capsforid and qname minimisation, depending on the settings.
++ - Fix uninitialized memory passed in padding bytes of cmsg to sendmsg.
++ - Fix stat_values test to work with dig that enables DNS cookies.
++
++17 August 2023: Wouter
++ - Merge PR #762: Downstream DNS Server Cookies a la RFC7873 and
++ RFC9018. Create server cookies for clients that send client cookies.
++ This needs to be explicitly turned on in the config file with:
++ `answer-cookie: yes`. A `cookie-secret:` can be configured for
++ anycast setups. Without one, a random cookie secret is generated.
++ The acl option `allow_cookie` allows queries with either a valid
++ cookie or over a stateful transport. The statistics output has
++ `queries_cookie_valid` and `queries_cookie_client` and
++ `queries_cookie_invalid` information. The `ip\-ratelimit\-cookie:`
++ value determines a rate limit for queries with cookies, if desired.
++ - Fix regional_alloc_init for potential unaligned source of the copy.
++ - Fix ip_ratelimit test to work with dig that enables DNS cookies.
++
++2 August 2023: George
++ - Move a cache reply callback in worker.c closer to the cache reply
++ generation.
++
++1 August 2023: George
++ - Merge #911 from natalie-reece: Exclude EDE before other EDNS options
++ when there isn't enough space.
++ - For #911: Try to trim EXTRA-TEXT (and LDNS_EDE_OTHER options
++ altogether) before giving up on attaching EDE options.
++ - More braces and formatting for Fix for EDNS EDE size calculation to
++ avoid future bugs.
++ - Fix to use the now cached EDE, if any, for CD_bit queries.
++
++1 August 2023: Wouter
++ - Fix for EDNS EDE size calculation.
++
++31 July 2023: George
++ - Merge #790 from Tom Carpay: Add support for EDE caching in cachedb
++ and subnetcache.
++
++31 July 2023: Wouter
++ - iana portlist update.
++
++30 July 2023: George
++ - Merge #759 from Tom Carpay: Add EDE (RFC8914) caching.
++
++28 July 2023: George
++ - Fix unused variable compile warning for kernel timestamps in
++ netevent.c
++
++21 July 2023: George
++ - Merge #857 from eaglegai: fix potential memory leaks when errors
++ happen.
++ - For #857: fix mixed declarations and code.
++ - Merge #118 from mibere: Changed verbosity level for Redis init &
++ deinit.
++ - Merge #390 from Frank Riley: Add missing callbacks to the python
++ module.
++ - Cleaner failure code for callback functions in interface.i.
++ - Merge #889 from borisVanhoof: Free memory in error case + remove
++ unused function.
++ - For #889: use netcat-openbsd instead of netcat-traditional.
++ - For #889: Account for num_detached_states before possible
++ mesh_state_delete when erroring out.
++
++20 July 2023: George
++ - Merge #909 from headshog: Numeric truncation when parsing TYPEXX and
++ CLASSXX representation.
++ - For #909: Fix return values.
++ - Merge #901 from Sergei Trofimovich: config: improve handling of
++ unknown modules.
++
++20 July 2023: Wouter
++ - For #909: Fix RR class comparison.
++
++14 July 2023: George
++ - More clear description of the different auth-zone behaviors on the
++ man page.
++
++13 July 2023: George
++ - Merge #880 from chipitsine: services/authzone.c: remove redundant
++ check.
++
++11 July 2023: George
++ - Merge #664 from tilan7763: Add prefetch support for subnet cache
++ entries.
++ - For #664: Easier code flow for subnetcache prefetching.
++ - For #664: Add testcase.
++ - For #664: Rename subnet_prefetch tests to subnet_global_prefetch to
++ differentiate from the new subnet prefetch support.
++
++3 July 2023: George
++ - Merge #739: Add SVCB dohpath support.
++ - Code cleanup for sldns_str2wire_svcparam_key_lookup.
++ - Merge #802: add validation EDEs to queries where the CD bit is set.
++ - For #802: Cleanup comments and add RCODE check for CD bit test case.
++ - Skip the 00-lint test. splint is not maintained; it either does not
++ work or produces false positives. Static analysis is handled in the
++ clang test.
++
++3 July 2023: Wouter
++ - Fix #906: warning: ‘Py_SetProgramName’ is deprecated.
++ - Fix dereference of NULL variable warning in mesh_do_callback.
++
++29 June 2023: George
++ - More fixes for reference counting for python module and clean up
++ failure code.
++ - Merge #827 from rcmcdonald91: Eliminate unnecessary Python reloading
++ which causes memory leaks.
++
++29 June 2023: Wouter
++ - Fix python modules with multiple scripts, by incrementing reference
++ counts.
++
++27 June 2023: George
++ - Merge #892: Add cachedb hit stat. Introduces 'num.query.cachedb' as
++ a new statistical counter.
++ - Remove warning about unknown cast-function-type warning pragma.
++
++22 June 2023: Wouter
++ - Merge #903: contrib: add yocto compatible init script.
++
++15 June 2023: Philip
++ - Fix for issue #887 (Timeouts to forward servers on BSD based
++ system with ASLR)
++ - Probably fixes #516 (Stream reuse does not work on Windows) as well
++
++14 June 2023: George
++ - Properly handle all return values of worker_check_request during
++ early EDE code.
++ - Do not check the incoming request more than once.
++
++12 June 2023: Wouter
++ - Merge #896: Fix: #895: pythonmodule: add all site-packages
++ directories to sys.path.
++ - Fix #895: python + sysconfig gives ANOTHER path comparing to
++ distutils.
++ - Fix for uncertain unit test for doh buffer size events.
++
++25 May 2023: Wouter
++ - Fix unbound-dnstap-socket printout when no query is present.
++ - Fix unbound-dnstap-socket time fraction conversion for printout.
++
++19 May 2023: Wouter
++ - Fix RPZ removal of client-ip, nsip, nsdname triggers from IXFR.
++ - Fix to remove unused variables from RPZ clientip data structure.
++
++16 May 2023: Wouter
++ - Fix #888: [FR] Use kernel timestamps for dnstap.
++ - Fix to print debug log for ancillary data with correct IP address.
++
++11 May 2023: Wouter
++ - Fix warning in windows compile, in set_recvtimestamp.
++
++4 May 2023: Wouter
++ - Fix #885: Error: util/configlexer.c: No such file or directory,
++ adds error messages explaining to install flex and bison.
++ - Fix to remove unused whitespace from acx_nlnetlabs.m4 and config.h.
++ - Fix doxygen in addr_to_nat64 header definition.
++
++1 May 2023: George
++ - Merge #722 from David 'eqvinox' Lamparter: NAT64 support.
++ - For #722: minor fixes, formatting, refactoring.
++
++1 May 2023: Wouter
++ - Fix RPZ IP responses with trigger rpz-drop on cache entries, that
++ they are dropped.
++
++26 April 2023: Philip
++ - Fix issue #860: Bad interaction with 0 TTL records and serve-expired
++
++26 April 2023: Wouter
++ - Merge #882 from vvfedorenko: Features/dropqueuedpackets, with
++ sock-queue-timeout option that drops packets that have been in the
++ socket queue for too long. Added statistics num.queries_timed_out
++ and query.queue_time_us.max that track the socket queue timeouts.
++ - Fix for #882: small changes, date updated in Copyright for
++ util/timeval_func.c and util/timeval_func.h. Man page entries and
++ example entry.
++ - Fix for #882: document variable to stop doxygen warning.
++
++19 April 2023: Wouter
++ - Fix for #878: Invalid IP address in unbound.conf causes Segmentation
++ Fault on OpenBSD.
++
++14 April 2023: Wouter
++ - Merge #875: change obsolete txt URL in unbound-anchor.c to point
++ to RFC 7958, and Fix #874.
++
++13 April 2023: Wouter
++ - Fix build badge, from failing travis link to github ci action link.
++
++6 April 2023: Wouter
++ - Fix for #870: Add test case for the qname minimisation and CNAME.
++
++4 April 2023: Wouter
++ - Fix #870: NXDOMAIN instead of NOERROR rcode when asked for existing
++ CNAME record.
++
++24 March 2023: Philip
++ - Fix issue #676: Unencrypted query is sent when
++ forward-tls-upstream: yes is used without tls-cert-bundle
++ - Extra consistency check to make sure that when TLS is requested,
++ either we set up a TLS connection or we return an error.
++
++21 March 2023: Philip
++ - Fix issue #851: reserved identifier violation
++
++20 March 2023: Wouter
++ - iana portlist update.
++
++17 March 2023: George
++ - Fix #812, fix #846, by using the SSL_OP_IGNORE_UNEXPECTED_EOF option
++ to ignore the unexpected eof while reading in openssl >= 3.
++
++16 March 2023: Wouter
++ - Fix ssl.h include brackets, instead of quotes.
++
++14 March 2023: Wouter
++ - Fix unbound-dnstap-socket test program to reply the finish frame
++ over a TLS connection correctly.
++
++23 February 2023: Wouter
++ - Fix for #852: Completion of error handling.
++
++21 February 2023: Philip
++ - Fix #825: Unexpected behavior with client-subnet-always-forward
++ and serve-expired
++
++10 February 2023: George
++ - Clean up iterator/iterator.c::error_response_cache() and allow for
++ better interaction with serve-expired, prefetch and cached error
++ responses.
++
++9 February 2023: George
++ - Allow TTL refresh of expired error responses.
++ - Add testcase for refreshing expired error responses.
++
++9 February 2023: Wouter
++ - Fix to ignore entirely empty responses, and try at another authority.
++ This turns completely empty responses, a type of noerror/nodata into
++ a servfail, but they do not conform to RFC2308, and the retry can
++ fetch improved content.
++ - Fix unit tests for spurious empty messages.
++ - Fix consistency of unit test without roundrobin answers for the
++ cnametooptout unit test.
++ - Fix to git ignore the library symbol file that configure can create.
++
++8 February 2023: Wouter
++ - Fix #841: Unbound won't build with aaaa-filter-iterator.patch.
++
++30 January 2023: George
++ - Add duration variable for speed_local.test.
++
++26 January 2023: Wouter
++ - Fix acx_nlnetlabs.m4 for -Wstrict-prototypes.
++
++23 January 2023: George
++ - Fix #833: [FR] Ability to set the Redis password.
++
++23 January 2023: Wouter
++ - Fix #835: [FR] Ability to use Redis unix sockets.
++
++20 January 2023: Wouter
++ - Merge #819: Added new static zone type block_a to suppress all A
++ queries for specific zones.
++
++19 January 2023: Wouter
++ - Set max-udp-size default to 1232. This is the same default value as
++ the default value for edns-buffer-size. It restricts client edns
++ buffer size choices, and makes unbound behave similar to other DNS
++ resolvers. The new choice, down from 4096 means it is harder to get
++ large responses from Unbound. Thanks to Xiang Li, from NISL Lab,
++ Tsinghua University.
++ - Add harden-unknown-additional option. It removes
++ unknown records from the authority section and additional section.
++ Thanks to Xiang Li, from NISL Lab, Tsinghua University.
++ - Set default for harden-unknown-additional to no. So that it does
++ not hamper future protocol developments.
++ - Fix test for new default.
++
++18 January 2023: Wouter
++ - Fix not following cleared RD flags potentially enables amplification
++ DDoS attacks, reported by Xiang Li and Wei Xu from NISL Lab,
++ Tsinghua University. The fix stops query loops, by refusing to send
++ RD=0 queries to a forwarder, they still get answered from cache.
++
++13 January 2023: Wouter
++ - Merge #826: Аdd a metric about the maximum number of collisions in
++ lrushah.
++ - Improve documentation for #826, describe the large collisions amount.
++
++9 January 2023: Wouter
++ - Fix python module install path detection.
++ - Fix python version detection in configure.
++
++6 January 2023: Wouter
++ - Fix #823: Response change to NODATA for some ANY queries since
++ 1.12, tested on 1.16.1.
++ - Fix wildcard in hyperlocal zone service degradation, reported
++ by Sergey Kacheev. This fix is included in 1.17.1rc2.
++ That became 1.17.1 on 12 Jan 2023, the code repo continues
++ with 1.17.2. 1.17.1 excludes fix #823, it is included forwards.
++
+ 5 January 2023: Wouter
+ - Tag for 1.17.1 release.
+
+@@ -4426,7 +4899,7 @@
+ - Fix that with openssl 1.1 control-use-cert: no uses less cpu, by
+ using no encryption over the unix socket.
+
+-22 Novenber 2016: Ralph
++22 November 2016: Ralph
+ - Make access-control-tag-data RDATA absolute. This makes the RDATA
+ origin consistent between local-data and access-control-tag-data.
+ - Fix NSEC ENT wildcard check. Matching wildcard does not have to be a
+--- contrib/unbound/doc/README.orig
++++ contrib/unbound/doc/README
+@@ -1,4 +1,4 @@
+-README for Unbound 1.17.1
++README for Unbound 1.19.1
+ Copyright 2007 NLnet Labs
+ http://unbound.net
+
+--- contrib/unbound/doc/README.DNS64.orig
++++ contrib/unbound/doc/README.DNS64
+@@ -28,3 +28,23 @@
+ ;; ANSWER SECTION:
+ jazz-v4.viagenie.ca. 86400 IN AAAA 64:ff9b::ce7b:1f02
+
++
++NAT64 support was added by David Lamparter in 2022; license(s) of the
++surrounding code apply. Note that NAT64 is closely related but functionally
++orthogonal to DNS64; it allows Unbound to send outgoing queries to IPv4-only
++servers over IPv6 through the configured NAT64 prefix. This allows running
++an Unbound instance on an IPv6-only host without breaking every single domain
++that only has IPv4 servers. Whether that Unbound instance also does DNS64 is
++an independent choice.
++
++To enable NAT64 in Unbound, add to unbound.conf's "server" section:
++
++ do-nat64: yes
++
++The NAT64 prefix defaults to the DNS64 prefix, which in turn defaults to the
++standard 64:FF9B::/96 prefix. You can reconfigure it with:
++
++ nat64-prefix: 64:FF9B::/96
++
++To test NAT64 operation, pick a domain that only has IPv4 reachability for its
++nameservers and try resolving any names in that domain.
+--- contrib/unbound/doc/example.conf.in.orig
++++ contrib/unbound/doc/example.conf.in
+@@ -1,7 +1,7 @@
+ #
+ # Example configuration file.
+ #
+-# See unbound.conf(5) man page, version 1.17.1.
++# See unbound.conf(5) man page, version 1.19.1.
+ #
+ # this is a comment.
+
+@@ -143,8 +143,8 @@
+ # edns-buffer-size: 1232
+
+ # Maximum UDP response size (not applied to TCP response).
+- # Suggested values are 512 to 4096. Default is 4096. 65536 disables it.
+- # max-udp-size: 4096
++ # Suggested values are 512 to 4096. Default is 1232. 65536 disables it.
++ # max-udp-size: 1232
+
+ # max memory to use for stream(tcp and tls) waiting result buffers.
+ # stream-wait-size: 4m
+@@ -243,6 +243,18 @@
+ # Enable IPv6, "yes" or "no".
+ # do-ip6: yes
+
++ # If running unbound on an IPv6-only host, domains that only have
++ # IPv4 servers would become unresolveable. If NAT64 is available in
++ # the network, unbound can use NAT64 to reach these servers with
++ # the following option. This is NOT needed for enabling DNS64 on a
++ # system that has IPv4 connectivity.
++ # Consider also enabling prefer-ip6 to prefer native IPv6 connections
++ # to nameservers.
++ # do-nat64: no
++
++ # NAT64 prefix. Defaults to using dns64-prefix value.
++ # nat64-prefix: 64:ff9b::0/96
++
+ # Enable UDP, "yes" or "no".
+ # do-udp: yes
+
+@@ -274,6 +286,10 @@
+ # Timeout for EDNS TCP keepalive, in msec.
+ # edns-tcp-keepalive-timeout: 120000
+
++ # UDP queries that have waited in the socket buffer for a long time
++ # can be dropped. Default is 0, disabled. In seconds, such as 3.
++ # sock-queue-timeout: 0
++
+ # Use systemd socket activation for UDP, TCP, and control sockets.
+ # use-systemd: no
+
+@@ -503,6 +519,10 @@
+ # to validate the zone.
+ # harden-algo-downgrade: no
+
++ # Harden against unknown records in the authority section and the
++ # additional section.
++ # harden-unknown-additional: no
++
+ # Sent minimum amount of information to upstream servers to enhance
+ # privacy. Only sent minimum required labels of the QNAME and set QTYPE
+ # to A when possible.
+@@ -663,6 +683,11 @@
+ # that set CD but cannot validate themselves.
+ # ignore-cd-flag: no
+
++ # Disable the DO flag in outgoing requests. It is helpful for upstream
++ # devices that cannot handle DNSSEC information. But do not enable it
++ # otherwise, because it would stop DNSSEC validation.
++ # disable-edns-do: no
++
+ # Serve expired responses from cache, with serve-expired-reply-ttl in
+ # the response, and then attempt to fetch the data afresh.
+ # serve-expired: no
+@@ -810,6 +835,8 @@
+ # o always_transparent, always_refuse, always_nxdomain, always_nodata,
+ # always_deny resolve in that way but ignore local data for
+ # that name
++ # o block_a resolves all records normally but returns
++ # NODATA for A queries and ignores local data for that name
+ # o always_null returns 0.0.0.0 or ::0 for any name in the zone.
+ # o noview breaks out of that view towards global local-zones.
+ #
+@@ -1199,6 +1226,8 @@
+ # backend: "testframe"
+ # # secret seed string to calculate hashed keys
+ # secret-seed: "default"
++# # if the backend should be read from, but not written to.
++# cachedb-no-store: no
+ #
+ # # For "redis" backend:
+ # # (to enable, use --with-libhiredis to configure before compiling)
+@@ -1206,10 +1235,16 @@
+ # redis-server-host: 127.0.0.1
+ # # redis server's TCP port
+ # redis-server-port: 6379
++# # if the server uses a unix socket, set its path, or "" when not used.
++# # redis-server-path: "/var/lib/redis/redis-server.sock"
++# # if the server uses an AUTH password, specify here, or "" when not used.
++# # redis-server-password: ""
+ # # timeout (in ms) for communication with the redis server
+ # redis-timeout: 100
+ # # set timeout on redis records based on DNS response TTL
+ # redis-expire-records: no
++# # redis logical database to use, 0 is the default database.
++# redis-logical-db: 0
+
+ # IPSet
+ # Add specify domain into set via ipset.
+--- contrib/unbound/doc/libunbound.3.in.orig
++++ contrib/unbound/doc/libunbound.3.in
+@@ -1,4 +1,4 @@
+-.TH "libunbound" "3" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "libunbound" "3" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" libunbound.3 -- unbound library functions manual
+ .\"
+@@ -44,7 +44,7 @@
+ .B ub_ctx_zone_remove,
+ .B ub_ctx_data_add,
+ .B ub_ctx_data_remove
+-\- Unbound DNS validating resolver 1.17.1 functions.
++\- Unbound DNS validating resolver 1.19.1 functions.
+ .SH "SYNOPSIS"
+ .B #include <unbound.h>
+ .LP
+--- contrib/unbound/doc/unbound-anchor.8.in.orig
++++ contrib/unbound/doc/unbound-anchor.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound-anchor" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound-anchor" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-anchor.8 -- unbound anchor maintenance utility manual
+ .\"
+--- contrib/unbound/doc/unbound-checkconf.8.in.orig
++++ contrib/unbound/doc/unbound-checkconf.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound-checkconf" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound-checkconf" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-checkconf.8 -- unbound configuration checker manual
+ .\"
+--- contrib/unbound/doc/unbound-control.8.in.orig
++++ contrib/unbound/doc/unbound-control.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound-control" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound-control" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-control.8 -- unbound remote control manual
+ .\"
+@@ -369,6 +369,15 @@
+ .I threadX.num.queries_ip_ratelimited
+ number of queries rate limited by thread
+ .TP
++.I threadX.num.queries_cookie_valid
++number of queries with a valid DNS Cookie by thread
++.TP
++.I threadX.num.queries_cookie_client
++number of queries with a client part only DNS Cookie by thread
++.TP
++.I threadX.num.queries_cookie_invalid
++number of queries with an invalid DNS Cookie by thread
++.TP
+ .I threadX.num.cachehits
+ number of queries that were successfully answered using a cache lookup
+ .TP
+@@ -398,6 +407,14 @@
+ .I threadX.num.expired
+ number of replies that served an expired cache entry.
+ .TP
++.I threadX.num.queries_timed_out
++number of queries that are dropped because they waited in the UDP socket buffer
++for too long.
++.TP
++.I threadX.query.queue_time_us.max
++The maximum wait time for packets in the socket buffer, in microseconds. This
++is only reported when sock-queue-timeout is enabled.
++.TP
+ .I threadX.num.recursivereplies
+ The number of replies sent to queries that needed recursive processing. Could be smaller than threadX.num.cachemiss if due to timeouts no replies were sent for some queries.
+ .TP
+@@ -438,6 +455,18 @@
+ .I total.num.queries
+ summed over threads.
+ .TP
++.I total.num.queries_ip_ratelimited
++summed over threads.
++.TP
++.I total.num.queries_cookie_valid
++summed over threads.
++.TP
++.I total.num.queries_cookie_client
++summed over threads.
++.TP
++.I total.num.queries_cookie_invalid
++summed over threads.
++.TP
+ .I total.num.cachehits
+ summed over threads.
+ .TP
+@@ -462,6 +491,12 @@
+ .I total.num.expired
+ summed over threads.
+ .TP
++.I total.num.queries_timed_out
++summed over threads.
++.TP
++.I total.query.queue_time_us.max
++the maximum of the thread values.
++.TP
+ .I total.num.recursivereplies
+ summed over threads.
+ .TP
+@@ -597,7 +632,7 @@
+ .TP
+ .I num.query.dnscrypt.shared_secret.cachemiss
+ The number of dnscrypt queries that did not find a shared secret in the cache.
+-The can be use to compute the shared secret hitrate.
++This can be used to compute the shared secret hitrate.
+ .TP
+ .I num.query.dnscrypt.replay
+ The number of dnscrypt queries that found a nonce hit in the nonce cache and
+@@ -653,6 +688,18 @@
+ The number of items in the key cache. These are DNSSEC keys, one item
+ per delegation point, and their validation status.
+ .TP
++.I msg.cache.max_collisions
++The maximum number of hash table collisions in the msg cache. This is the
++number of hashes that are identical when a new element is inserted in the
++hash table. If the value is very large, like hundreds, something is wrong
++with the performance of the hash table, hash values are incorrect or malicious.
++.TP
++.I rrset.cache.max_collisions
++The maximum number of hash table collisions in the rrset cache. This is the
++number of hashes that are identical when a new element is inserted in the
++hash table. If the value is very large, like hundreds, something is wrong
++with the performance of the hash table, hash values are incorrect or malicious.
++.TP
+ .I dnscrypt_shared_secret.cache.count
+ The number of items in the shared secret cache. These are precomputed shared
+ secrets for a given client public key/server secret key pair. Shared secrets
+@@ -692,7 +739,12 @@
+ .I num.query.subnet_cache
+ Number of queries answered from the edns client subnet cache. These are
+ counted as cachemiss by the main counters, but hit the client subnet
+-specific cache, after getting processed by the edns client subnet module.
++specific cache after getting processed by the edns client subnet module.
++.TP
++.I num.query.cachedb
++Number of queries answered from the external cache of cachedb.
++These are counted as cachemiss by the main counters, but hit the cachedb
++external cache after getting processed by the cachedb module.
+ .TP
+ .I num.rpz.action.<rpz_action>
+ Number of queries answered using configured RPZ policy, per RPZ action type.
+--- contrib/unbound/doc/unbound-host.1.in.orig
++++ contrib/unbound/doc/unbound-host.1.in
+@@ -1,4 +1,4 @@
+-.TH "unbound\-host" "1" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound\-host" "1" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound-host.1 -- unbound DNS lookup utility
+ .\"
+--- contrib/unbound/doc/unbound.8.in.orig
++++ contrib/unbound/doc/unbound.8.in
+@@ -1,4 +1,4 @@
+-.TH "unbound" "8" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound" "8" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound.8 -- unbound manual
+ .\"
+@@ -9,7 +9,7 @@
+ .\"
+ .SH "NAME"
+ .B unbound
+-\- Unbound DNS validating resolver 1.17.1.
++\- Unbound DNS validating resolver 1.19.1.
+ .SH "SYNOPSIS"
+ .B unbound
+ .RB [ \-h ]
+--- contrib/unbound/doc/unbound.conf.5.in.orig
++++ contrib/unbound/doc/unbound.conf.5.in
+@@ -1,4 +1,4 @@
+-.TH "unbound.conf" "5" "Jan 12, 2023" "NLnet Labs" "unbound 1.17.1"
++.TH "unbound.conf" "5" "Feb 13, 2024" "NLnet Labs" "unbound 1.19.1"
+ .\"
+ .\" unbound.conf.5 -- unbound.conf manual
+ .\"
+@@ -233,7 +233,8 @@
+ .B max\-udp\-size: \fI<number>
+ Maximum UDP response size (not applied to TCP response). 65536 disables the
+ udp response size maximum, and uses the choice from the client, always.
+-Suggested values are 512 to 4096. Default is 4096.
++Suggested values are 512 to 4096. Default is 1232. The default value is the
++same as the default for edns\-buffer\-size.
+ .TP
+ .B stream\-wait\-size: \fI<number>
+ Number of bytes size maximum to use for waiting stream buffers. Default is
+@@ -504,6 +505,14 @@
+ A minimum actual timeout of 200 milliseconds is observed regardless of the
+ advertised timeout.
+ .TP
++.B sock\-queue\-timeout: \fI<sec>\fR
++UDP queries that have waited in the socket buffer for a long time can be
++dropped. Default is 0, disabled. The time is set in seconds, 3 could be a
++good value to ignore old queries that likely the client does not need a reply
++for any more. This could happen if the host has not been able to service
++the queries for a while, i.e. Unbound is not running, and then is enabled
++again. It uses timestamp socket options.
++.TP
+ .B tcp\-upstream: \fI<yes or no>
+ Enable or disable whether the upstream queries use TCP only for transport.
+ Default is no. Useful in tunneling scenarios. If set to no you can specify
+@@ -692,17 +701,17 @@
+ .B access\-control: \fI<IP netblock> <action>
+ The netblock is given as an IP4 or IP6 address with /size appended for a
+ classless network block. The action can be \fIdeny\fR, \fIrefuse\fR,
+-\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIdeny_non_local\fR or
+-\fIrefuse_non_local\fR.
++\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIallow_cookie\fR,
++\fIdeny_non_local\fR or \fIrefuse_non_local\fR.
+ The most specific netblock match is used, if none match \fIrefuse\fR is used.
+ The order of the access\-control statements therefore does not matter.
+ .IP
+-The action \fIdeny\fR stops queries from hosts from that netblock.
++The \fIdeny\fR action stops queries from hosts from that netblock.
+ .IP
+-The action \fIrefuse\fR stops queries too, but sends a DNS rcode REFUSED
++The \fIrefuse\fR action stops queries too, but sends a DNS rcode REFUSED
+ error message back.
+ .IP
+-The action \fIallow\fR gives access to clients from that netblock.
++The \fIallow\fR action gives access to clients from that netblock.
+ It gives only access for recursion clients (which is
+ what almost all clients need). Nonrecursive queries are refused.
+ .IP
+@@ -722,13 +731,27 @@
+ zones to a resolver DNS server, but only supports stub domains and
+ sends queries to the resolver DNS server with the RD bit cleared.
+ .IP
+-The action \fIallow_snoop\fR gives nonrecursive access too. This give
++The \fIallow_snoop\fR action gives nonrecursive access too. This give
+ both recursive and non recursive access. The name \fIallow_snoop\fR refers
+ to cache snooping, a technique to use nonrecursive queries to examine
+ the cache contents (for malicious acts). However, nonrecursive queries can
+ also be a valuable debugging tool (when you want to examine the cache
+ contents). In that case use \fIallow_snoop\fR for your administration host.
+ .IP
++The \fIallow_cookie\fR action allows access to UDP queries that contain a
++valid DNS Cookie as specified in RFC 7873 and RFC 9018, when the
++\fBanswer\-cookie\fR option is enabled.
++UDP queries containing only a DNS Client Cookie and no Server Cookie, or an
++invalid DNS Cookie, will receive a BADCOOKIE response including a newly
++generated DNS Cookie, allowing clients to retry with that DNS Cookie.
++The \fIallow_cookie\fR action will also accept requests over stateful
++transports, regardless of the presence of an DNS Cookie and regardless of the
++\fBanswer\-cookie\fR setting.
++If \fBip\-ratelimit\fR is used, clients with a valid DNS Cookie will bypass the
++ratelimit.
++If a ratelimit for such clients is still needed, \fBip\-ratelimit\-cookie\fR
++can be used instead.
++.IP
+ By default only localhost is \fIallow\fRed, the rest is \fIrefuse\fRd.
+ The default is \fIrefuse\fRd, because that is protocol\-friendly. The DNS
+ protocol is not designed to handle dropped packets due to policy, and
+@@ -1019,6 +1042,12 @@
+ that allow this feature to work, but sometimes they do not, and turning
+ this option off avoids that validation failure.
+ .TP
++.B harden\-unknown\-additional: \fI<yes or no>
++Harden against unknown records in the authority section and additional
++section. Default is no. If no, such records are copied from the upstream
++and presented to the client together with the answer. If yes, it could
++hamper future protocol developments that want to add records.
++.TP
+ .B use\-caps\-for\-id: \fI<yes or no>
+ Use 0x20\-encoded random bits in the query to foil spoof attempts.
+ This perturbs the lowercase and uppercase of query names sent to
+@@ -1273,6 +1302,20 @@
+ the clients, and then Unbound provides them with DNSSEC protection.
+ The default value is "no".
+ .TP
++.B disable\-edns\-do: \fI<yes or no>
++Disable the EDNS DO flag in upstream requests.
++It breaks DNSSEC validation for Unbound's clients.
++This results in the upstream name servers to not include DNSSEC records in
++their replies and could be helpful for devices that cannot handle DNSSEC
++information.
++When the option is enabled, clients that set the DO flag receive no EDNS
++record in the response to indicate the lack of support to them.
++If this option is enabled but Unbound is already configured for DNSSEC
++validation (i.e., the validator module is enabled; default) this option is
++implicitly turned off with a warning as to not break DNSSEC validation in
++Unbound.
++Default is no.
++.TP
+ .B serve\-expired: \fI<yes or no>
+ If enabled, Unbound attempts to serve old responses from cache with a
+ TTL of \fBserve\-expired\-reply\-ttl\fR in the response without waiting for the
+@@ -1391,10 +1434,10 @@
+ Configure a local zone. The type determines the answer to give if
+ there is no match from local\-data. The types are deny, refuse, static,
+ transparent, redirect, nodefault, typetransparent, inform, inform_deny,
+-inform_redirect, always_transparent, always_refuse, always_nxdomain, always_null, noview,
+-and are explained below. After that the default settings are listed. Use
+-local\-data: to enter data into the local zone. Answers for local zones
+-are authoritative DNS answers. By default the zones are class IN.
++inform_redirect, always_transparent, block_a, always_refuse, always_nxdomain,
++always_null, noview, and are explained below. After that the default settings
++are listed. Use local\-data: to enter data into the local zone. Answers for
++local zones are authoritative DNS answers. By default the zones are class IN.
+ .IP
+ If you need more complicated authoritative data, with referrals, wildcards,
+ CNAME/DNAME support, or DNSSEC authoritative service, setup a stub\-zone for
+@@ -1469,6 +1512,12 @@
+ \h'5'\fIalways_transparent\fR
+ Like transparent, but ignores local data and resolves normally.
+ .TP 10
++\h'5'\fIblock_a\fR
++Like transparent, but ignores local data and resolves normally all query
++types excluding A. For A queries it unconditionally returns NODATA.
++Useful in cases when there is a need to explicitly force all apps to use
++IPv6 protocol and avoid any queries to IPv4.
++.TP 10
+ \h'5'\fIalways_refuse\fR
+ Like refuse, but ignores local data and refuses the query.
+ .TP 10
+@@ -1785,11 +1834,27 @@
+ .TP 5
+ .B ip\-ratelimit: \fI<number or 0>
+ Enable global ratelimiting of queries accepted per IP address.
+-If 0, the default, it is disabled. This option is experimental at this time.
++This option is experimental at this time.
+ The ratelimit is in queries per second that are allowed. More queries are
+ completely dropped and will not receive a reply, SERVFAIL or otherwise.
+ IP ratelimiting happens before looking in the cache. This may be useful for
+ mitigating amplification attacks.
++Default is 0 (disabled).
++.TP 5
++.B ip\-ratelimit\-cookie: \fI<number or 0>
++Enable global ratelimiting of queries accepted per IP address with a valid DNS
++Cookie.
++This option is experimental at this time.
++The ratelimit is in queries per second that are allowed.
++More queries are completely dropped and will not receive a reply, SERVFAIL or
++otherwise.
++IP ratelimiting happens before looking in the cache.
++This option could be useful in combination with \fIallow_cookie\fR in an
++attempt to mitigate other amplification attacks than UDP reflections (e.g.,
++attacks targeting Unbound itself) which are already handled with DNS Cookies.
++If used, the value is suggested to be higher than \fBip\-ratelimit\fR e.g.,
++tenfold.
++Default is 0 (disabled).
+ .TP 5
+ .B ip\-ratelimit\-size: \fI<memory size>
+ Give the size of the data structure in which the current ongoing rates are
+@@ -1858,6 +1923,18 @@
+ use the fastest specified number of servers with the fast\-server\-permil
+ option, that turns this on or off. The default is to use the fastest 3 servers.
+ .TP 5
++.B answer\-cookie: \fI<yes or no>
++If enabled, Unbound will answer to requests containing DNS Cookies as
++specified in RFC 7873 and RFC 9018.
++Default is no.
++.TP 5
++.B cookie\-secret: \fI<128 bit hex string>
++Server's secret for DNS Cookie generation.
++Useful to explicitly set for servers in an anycast deployment that need to
++share the secret in order to verify each other's Server Cookies.
++An example hex string would be "000102030405060708090a0b0c0d0e0f".
++Default is a 128 bits random secret generated at startup time.
++.TP 5
+ .B edns\-client\-string: \fI<IP netblock> <string>
+ Include an EDNS0 option containing configured ascii string in queries with
+ destination address matching the configured IP netblock. This configuration
+@@ -2091,13 +2168,32 @@
+ Authority zones are configured with \fBauth\-zone:\fR, and each one must
+ have a \fBname:\fR. There can be multiple ones, by listing multiple auth\-zone clauses, each with a different name, pertaining to that part of the namespace.
+ The authority zone with the name closest to the name looked up is used.
+-Authority zones are processed after \fBlocal\-zones\fR and before
+-cache (\fBfor\-downstream:\fR \fIyes\fR), and when used in this manner
+-make Unbound respond like an authority server. Authority zones are also
+-processed after cache, just before going to the network to fetch
+-information for recursion (\fBfor\-upstream:\fR \fIyes\fR), and when used
+-in this manner provide a local copy of an authority server that speeds up
+-lookups of that data.
++Authority zones can be processed on two distinct, non-exclusive, configurable
++stages.
++.LP
++With \fBfor\-downstream:\fR \fIyes\fR (default), authority zones are processed
++after \fBlocal\-zones\fR and before cache.
++When used in this manner, Unbound responds like an authority server with no
++further processing other than returning an answer from the zone contents.
++A notable example, in this case, is CNAME records which are returned verbatim
++to downstream clients without further resolution.
++.LP
++With \fBfor\-upstream:\fR \fIyes\fR (default), authority zones are processed
++after the cache lookup, just before going to the network to fetch
++information for recursion.
++When used in this manner they provide a local copy of an authority server
++that speeds up lookups for that data during resolving.
++.LP
++If both options are enabled (default), client queries for an authority zone are
++answered authoritatively from Unbound, while internal queries that require data
++from the authority zone consult the local zone data instead of going to the
++network.
++.LP
++An interesting configuration is \fBfor\-downstream:\fR \fIno\fR,
++\fBfor\-upstream:\fR \fIyes\fR that allows for hyperlocal behavior where both
++client and internal queries consult the local zone data while resolving.
++In this case, the aforementioned CNAME example will result in a thoroughly
++resolved answer.
+ .LP
+ Authority zones can be read from zonefile. And can be kept updated via
+ AXFR and IXFR. After update the zonefile is rewritten. The update mechanism
+@@ -2291,6 +2387,21 @@
+ used by dns64 processing instead. Can be entered multiple times, list a
+ new domain for which it applies, one per line. Applies also to names
+ underneath the name given.
++.SS "NAT64 Operation"
++.LP
++NAT64 operation allows using a NAT64 prefix for outbound requests to IPv4-only
++servers. It is controlled by two options in the \fBserver:\fR section:
++.TP
++.B do\-nat64: \fI<yes or no>\fR
++Use NAT64 to reach IPv4-only servers.
++Consider also enabling \fBprefer\-ip6\fR to prefer native IPv6 connections to
++nameservers.
++Default no.
++.TP
++.B nat64\-prefix: \fI<IPv6 prefix>\fR
++Use a specific NAT64 prefix to reach IPv4-only servers. Defaults to using
++the prefix configured in \fBdns64\-prefix\fR, which in turn defaults to
++64:ff9b::/96. The prefix length must be one of /32, /40, /48, /56, /64 or /96.
+ .SS "DNSCrypt Options"
+ .LP
+ The
+@@ -2570,6 +2681,11 @@
+ If the backend database is shared by multiple Unbound instances,
+ all instances must use the same secret seed.
+ This option defaults to "default".
++.TP
++.B cachedb-no-store: \fI<yes or no>\fR
++If the backend should be read from, but not written to. This makes this
++instance not store dns messages in the backend. But if data is available it
++is retrieved. The default is no.
+ .P
+ The following
+ .B cachedb
+@@ -2586,6 +2702,16 @@
+ The TCP port number of the Redis server.
+ This option defaults to 6379.
+ .TP
++.B redis-server-path: \fI<unix socket path>\fR
++The unix socket path to connect to the redis server. Off by default, and it
++can be set to "" to turn this off. Unix sockets may have better throughput
++than the IP address option.
++.TP
++.B redis-server-password: \fI"<password>"\fR
++The Redis AUTH password to use for the redis server.
++Only relevant if Redis is configured for client password authorisation.
++Off by default, and it can be set to "" to turn this off.
++.TP
+ .B redis-timeout: \fI<msec>\fR
+ The period until when Unbound waits for a response from the Redis sever.
+ If this timeout expires Unbound closes the connection, treats it as
+@@ -2600,6 +2726,17 @@
+ this option is internally reverted to "no". Redis SETEX support is required
+ for this option (Redis >= 2.0.0).
+ This option defaults to no.
++.TP
++.B redis-logical-db: \fI<logical database index>
++The logical database in Redis to use.
++These are databases in the same Redis instance sharing the same configuration
++and persisted in the same RDB/AOF file.
++If unsure about using this option, Redis documentation
++(https://redis.io/commands/select/) suggests not to use a single Redis instance
++for multiple unrelated applications.
++The default database in Redis is 0 while other logical databases need to be
++explicitly SELECT'ed upon connecting.
++This option defaults to 0.
+ .SS DNSTAP Logging Options
+ DNSTAP support, when compiled in by using \fB\-\-enable\-dnstap\fR, is enabled
+ in the \fBdnstap:\fR section.
+--- contrib/unbound/dynlibmod/dynlibmod.c.orig
++++ contrib/unbound/dynlibmod/dynlibmod.c
+@@ -75,6 +75,7 @@
+ struct config_strlist* cfg_item = env->cfg->dynlib_file;
+ struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env));
+ __DYNMOD dynamic_library;
++ int i;
+ if (!de)
+ {
+ log_err("dynlibmod[%d]: malloc failure", dynlib_mod_idx);
+@@ -84,7 +85,7 @@
+ env->modinfo[id] = (void*) de;
+
+ de->fname = NULL;
+- for(int i = dynlib_mod_idx;
++ for(i = dynlib_mod_idx;
+ i != 0 && cfg_item != NULL;
+ i--, cfg_item = cfg_item->next) {}
+
+--- contrib/unbound/edns-subnet/subnetmod.c.orig
++++ contrib/unbound/edns-subnet/subnetmod.c
+@@ -156,6 +156,7 @@
+ qstate->no_cache_store = 0;
+ }
+
++ sq->subnet_sent_no_subnet = 0;
+ if(sq->ecs_server_out.subnet_validdata && ((sq->subnet_downstream &&
+ qstate->env->cfg->client_subnet_always_forward) ||
+ ecs_is_whitelisted(sn_env->whitelist,
+@@ -166,6 +167,14 @@
+ * set. */
+ if(!edns_opt_list_find(qstate->edns_opts_back_out,
+ qstate->env->cfg->client_subnet_opcode)) {
++ /* if the client is not wanting an EDNS subnet option,
++ * omit it and store that we omitted it but actually
++ * are doing EDNS subnet to the server. */
++ if(sq->ecs_server_out.subnet_source_mask == 0) {
++ sq->subnet_sent_no_subnet = 1;
++ sq->subnet_sent = 0;
++ return 1;
++ }
+ subnet_ecs_opt_list_append(&sq->ecs_server_out,
+ &qstate->edns_opts_back_out, qstate, region);
+ }
+@@ -352,7 +361,7 @@
+ ((struct subnet_qstate*)qstate->minfo[id])->qinfo_hash :
+ query_info_hash(&qstate->qinfo, qstate->query_flags);
+ /* Step 1, general qinfo lookup */
+- struct lruhash_entry *lru_entry = slabhash_lookup(subnet_msg_cache, h,
++ struct lruhash_entry* lru_entry = slabhash_lookup(subnet_msg_cache, h,
+ &qstate->qinfo, 1);
+ int need_to_insert = (lru_entry == NULL);
+ if (!lru_entry) {
+@@ -396,7 +405,7 @@
+ log_err("subnetcache: cache insertion failed");
+ return;
+ }
+-
++
+ /* store RRsets */
+ for(i=0; i<rep->rrset_count; i++) {
+ rep->ref[i].key = rep->rrsets[i];
+@@ -421,7 +430,7 @@
+
+ /** Lookup in cache and reply true iff reply is sent. */
+ static int
+-lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
++lookup_and_reply(struct module_qstate *qstate, int id, struct subnet_qstate *sq, int prefetch)
+ {
+ struct lruhash_entry *e;
+ struct module_env *env = qstate->env;
+@@ -473,6 +482,10 @@
+ INET6_SIZE);
+ sq->ecs_client_out.subnet_validdata = 1;
+ }
++
++ if (prefetch && *qstate->env->now >= ((struct reply_info *)node->elem)->prefetch_ttl) {
++ qstate->need_refetch = 1;
++ }
+ return 1;
+ }
+
+@@ -509,18 +522,18 @@
+ * module_finished */
+ return module_finished;
+ }
+-
++
+ /* We have not asked for subnet data */
+- if (!sq->subnet_sent) {
++ if (!sq->subnet_sent && !sq->subnet_sent_no_subnet) {
+ if (s_in->subnet_validdata)
+ verbose(VERB_QUERY, "subnetcache: received spurious data");
+ if (sq->subnet_downstream) /* Copy back to client */
+ cp_edns_bad_response(c_out, c_in);
+ return module_finished;
+ }
+-
++
+ /* subnet sent but nothing came back */
+- if (!s_in->subnet_validdata) {
++ if (!s_in->subnet_validdata && !sq->subnet_sent_no_subnet) {
+ /* The authority indicated no support for edns subnet. As a
+ * consequence the answer ended up in the regular cache. It
+ * is still useful to put it in the edns subnet cache for
+@@ -535,11 +548,23 @@
+ cp_edns_bad_response(c_out, c_in);
+ return module_finished;
+ }
+-
++
++ /* Purposefully there was no sent subnet, and there is consequently
++ * no subnet in the answer. If there was, use the subnet in the answer
++ * anyway. But if there is not, treat it as a prefix 0 answer. */
++ if(sq->subnet_sent_no_subnet && !s_in->subnet_validdata) {
++ /* Fill in 0.0.0.0/0 scope 0, or ::0/0 scope 0, for caching. */
++ s_in->subnet_addr_fam = s_out->subnet_addr_fam;
++ s_in->subnet_source_mask = 0;
++ s_in->subnet_scope_mask = 0;
++ memset(s_in->subnet_addr, 0, INET6_SIZE);
++ s_in->subnet_validdata = 1;
++ }
++
+ /* Being here means we have asked for and got a subnet specific
+ * answer. Also, the answer from the authority is not yet cached
+ * anywhere. */
+-
++
+ /* can we accept response? */
+ if(s_out->subnet_addr_fam != s_in->subnet_addr_fam ||
+ s_out->subnet_source_mask != s_in->subnet_source_mask ||
+@@ -552,6 +577,7 @@
+ (void)edns_opt_list_remove(&qstate->edns_opts_back_out,
+ qstate->env->cfg->client_subnet_opcode);
+ sq->subnet_sent = 0;
++ sq->subnet_sent_no_subnet = 0;
+ return module_restart_next;
+ }
+
+@@ -672,6 +698,7 @@
+ edns_opt_list_remove(&qstate->edns_opts_back_out,
+ qstate->env->cfg->client_subnet_opcode);
+ sq->subnet_sent = 0;
++ sq->subnet_sent_no_subnet = 0;
+ memset(&sq->ecs_server_out, 0, sizeof(sq->ecs_server_out));
+ } else if (!sq->track_max_scope &&
+ FLAGS_GET_RCODE(response->rep->flags) == LDNS_RCODE_NOERROR &&
+@@ -733,6 +760,9 @@
+ sq->ecs_server_in.subnet_scope_mask >
+ sq->max_scope))
+ sq->max_scope = sq->ecs_server_in.subnet_scope_mask;
++ } else if(sq->subnet_sent_no_subnet) {
++ /* The answer can be stored as scope 0, not in global cache. */
++ qstate->no_cache_store = 1;
+ }
+
+ return 1;
+@@ -779,6 +809,11 @@
+ &qstate->mesh_info->reply_list->query_reply.client_addr,
+ &sq->ecs_client_in, qstate->env->cfg);
+ }
++ else if(qstate->client_addr.ss_family != AF_UNSPEC) {
++ subnet_option_from_ss(
++ &qstate->client_addr,
++ &sq->ecs_client_in, qstate->env->cfg);
++ }
+
+ if(sq->ecs_client_in.subnet_validdata == 0) {
+ /* No clients are interested in result or we could not
+@@ -802,7 +837,9 @@
+
+ if(!sq->started_no_cache_lookup && !qstate->blacklist) {
+ lock_rw_wrlock(&sne->biglock);
+- if(lookup_and_reply(qstate, id, sq)) {
++ if(qstate->mesh_info->reply_list &&
++ lookup_and_reply(qstate, id, sq,
++ qstate->env->cfg->prefetch)) {
+ sne->num_msg_cache++;
+ lock_rw_unlock(&sne->biglock);
+ verbose(VERB_QUERY, "subnetcache: answered from cache");
+--- contrib/unbound/edns-subnet/subnetmod.h.orig
++++ contrib/unbound/edns-subnet/subnetmod.h
+@@ -85,6 +85,13 @@
+ struct ecs_data ecs_server_out;
+ int subnet_downstream;
+ int subnet_sent;
++ /**
++ * If there was no subnet sent because the client used source prefix
++ * length 0 for omitting the information. Then the answer is cached
++ * like subnet was a /0 scope. Like the subnet_sent flag, but when
++ * the EDNS subnet option is omitted because the client asked.
++ */
++ int subnet_sent_no_subnet;
+ /** keep track of longest received scope, set after receiving CNAME for
+ * incoming QNAME. */
+ int track_max_scope;
+--- contrib/unbound/ipset/ipset.c.orig
++++ contrib/unbound/ipset/ipset.c
+@@ -158,10 +158,10 @@
+ qs = NULL;
+ plen = strlen(p->str);
+
+- if (dlen >= plen) {
++ if (dlen == plen || (dlen > plen && dname[dlen - plen - 1] == '.' )) {
+ ds = dname + (dlen - plen);
+ }
+- if (qlen >= plen) {
++ if (qlen == plen || (qlen > plen && qname[qlen - plen - 1] == '.' )) {
+ qs = qname + (qlen - plen);
+ }
+ if ((ds && strncasecmp(p->str, ds, plen) == 0)
+--- contrib/unbound/iterator/iter_delegpt.c.orig
++++ contrib/unbound/iterator/iter_delegpt.c
+@@ -321,6 +321,45 @@
+ }
+ }
+
++int
++delegpt_addr_on_result_list(struct delegpt* dp, struct delegpt_addr* find)
++{
++ struct delegpt_addr* a = dp->result_list;
++ while(a) {
++ if(a == find)
++ return 1;
++ a = a->next_result;
++ }
++ return 0;
++}
++
++void
++delegpt_usable_list_remove_addr(struct delegpt* dp, struct delegpt_addr* del)
++{
++ struct delegpt_addr* usa = dp->usable_list, *prev = NULL;
++ while(usa) {
++ if(usa == del) {
++ /* snip off the usable list */
++ if(prev)
++ prev->next_usable = usa->next_usable;
++ else dp->usable_list = usa->next_usable;
++ return;
++ }
++ prev = usa;
++ usa = usa->next_usable;
++ }
++}
++
++void
++delegpt_add_to_result_list(struct delegpt* dp, struct delegpt_addr* a)
++{
++ if(delegpt_addr_on_result_list(dp, a))
++ return;
++ delegpt_usable_list_remove_addr(dp, a);
++ a->next_result = dp->result_list;
++ dp->result_list = a;
++}
++
+ void
+ delegpt_add_unused_targets(struct delegpt* dp)
+ {
+--- contrib/unbound/iterator/iter_delegpt.h.orig
++++ contrib/unbound/iterator/iter_delegpt.h
+@@ -457,4 +457,29 @@
+ /** get memory in use by dp */
+ size_t delegpt_get_mem(struct delegpt* dp);
+
++/**
++ * See if the addr is on the result list.
++ * @param dp: delegation point.
++ * @param find: the pointer is searched for on the result list.
++ * @return 1 if found, 0 if not found.
++ */
++int delegpt_addr_on_result_list(struct delegpt* dp, struct delegpt_addr* find);
++
++/**
++ * Remove the addr from the usable list.
++ * @param dp: the delegation point.
++ * @param del: the addr to remove from the list, the pointer is searched for.
++ */
++void delegpt_usable_list_remove_addr(struct delegpt* dp,
++ struct delegpt_addr* del);
++
++/**
++ * Add the delegpt_addr back to the result list, if it is not already on
++ * the result list. Also removes it from the usable list.
++ * @param dp: delegation point.
++ * @param a: addr to add, nothing happens if it is already on the result list.
++ * It is removed from the usable list.
++ */
++void delegpt_add_to_result_list(struct delegpt* dp, struct delegpt_addr* a);
++
+ #endif /* ITERATOR_ITER_DELEGPT_H */
+--- contrib/unbound/iterator/iter_priv.c.orig
++++ contrib/unbound/iterator/iter_priv.c
+@@ -207,28 +207,6 @@
+ return sizeof(*priv) + regional_get_mem(priv->region);
+ }
+
+-/** remove RR from msgparse RRset, return true if rrset is entirely bad */
+-static int
+-remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* rrset,
+- struct rr_parse* prev, struct rr_parse** rr, struct sockaddr_storage* addr, socklen_t addrlen)
+-{
+- if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN && str) {
+- uint8_t buf[LDNS_MAX_DOMAINLEN+1];
+- dname_pkt_copy(pkt, buf, rrset->dname);
+- log_name_addr(VERB_QUERY, str, buf, addr, addrlen);
+- }
+- if(prev)
+- prev->next = (*rr)->next;
+- else rrset->rr_first = (*rr)->next;
+- if(rrset->rr_last == *rr)
+- rrset->rr_last = prev;
+- rrset->rr_count --;
+- rrset->size -= (*rr)->size;
+- /* rr struct still exists, but is unlinked, so that in the for loop
+- * the rr->next works fine to continue. */
+- return rrset->rr_count == 0;
+-}
+-
+ int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt,
+ struct rrset_parse* rrset)
+ {
+@@ -261,7 +239,7 @@
+ INET_SIZE);
+ memmove(&addr, &sa, len);
+ if(priv_lookup_addr(priv, &addr, len)) {
+- if(remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, &rr, &addr, len))
++ if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len))
+ return 1;
+ continue;
+ }
+@@ -284,7 +262,7 @@
+ INET6_SIZE);
+ memmove(&addr, &sa, len);
+ if(priv_lookup_addr(priv, &addr, len)) {
+- if(remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, &rr, &addr, len))
++ if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len))
+ return 1;
+ continue;
+ }
+--- contrib/unbound/iterator/iter_resptype.c.orig
++++ contrib/unbound/iterator/iter_resptype.c
+@@ -42,6 +42,7 @@
+ #include "config.h"
+ #include "iterator/iter_resptype.h"
+ #include "iterator/iter_delegpt.h"
++#include "iterator/iterator.h"
+ #include "services/cache/dns.h"
+ #include "util/net_help.h"
+ #include "util/data/dname.h"
+@@ -105,7 +106,8 @@
+
+ enum response_type
+ response_type_from_server(int rdset,
+- struct dns_msg* msg, struct query_info* request, struct delegpt* dp)
++ struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
++ int* empty_nodata_found)
+ {
+ uint8_t* origzone = (uint8_t*)"\000"; /* the default */
+ struct ub_packed_rrset_key* s;
+@@ -284,6 +286,22 @@
+
+ /* If we've gotten this far, this is NOERROR/NODATA (which could
+ * be an entirely empty message) */
++ /* For entirely empty messages, try again, at first, then accept
++ * it it happens more. A regular noerror/nodata response has a soa
++ * negative ttl value in the authority section. This makes it try
++ * again at another authority. And decides between storing a 5 second
++ * empty message or a 5 second servfail response. */
++ if(msg->rep->an_numrrsets == 0 && msg->rep->ns_numrrsets == 0 &&
++ msg->rep->ar_numrrsets == 0) {
++ if(empty_nodata_found) {
++ /* detect as throwaway at first, but accept later. */
++ (*empty_nodata_found)++;
++ if(*empty_nodata_found < EMPTY_NODATA_RETRY_COUNT)
++ return RESPONSE_TYPE_THROWAWAY;
++ return RESPONSE_TYPE_ANSWER;
++ }
++ return RESPONSE_TYPE_ANSWER;
++ }
+ /* check if recursive answer; saying it has empty cache */
+ if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset)
+ return RESPONSE_TYPE_REC_LAME;
+--- contrib/unbound/iterator/iter_resptype.h.orig
++++ contrib/unbound/iterator/iter_resptype.h
+@@ -119,9 +119,11 @@
+ * @param request: the request that generated the response.
+ * @param dp: The delegation point that was being queried
+ * when the response was returned.
++ * @param empty_nodata_found: flag to keep track of empty nodata detection.
+ * @return the response type (CNAME or ANSWER).
+ */
+ enum response_type response_type_from_server(int rdset,
+- struct dns_msg* msg, struct query_info* request, struct delegpt* dp);
++ struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
++ int* empty_nodata_found);
+
+ #endif /* ITERATOR_ITER_RESPTYPE_H */
+--- contrib/unbound/iterator/iter_scrub.c.orig
++++ contrib/unbound/iterator/iter_scrub.c
+@@ -346,6 +346,26 @@
+ return 0;
+ }
+
++/** Check if type is allowed in the authority section */
++static int
++type_allowed_in_authority_section(uint16_t tp)
++{
++ if(tp == LDNS_RR_TYPE_SOA || tp == LDNS_RR_TYPE_NS ||
++ tp == LDNS_RR_TYPE_DS || tp == LDNS_RR_TYPE_NSEC ||
++ tp == LDNS_RR_TYPE_NSEC3)
++ return 1;
++ return 0;
++}
++
++/** Check if type is allowed in the additional section */
++static int
++type_allowed_in_additional_section(uint16_t tp)
++{
++ if(tp == LDNS_RR_TYPE_A || tp == LDNS_RR_TYPE_AAAA)
++ return 1;
++ return 0;
++}
++
+ /**
+ * This routine normalizes a response. This includes removing "irrelevant"
+ * records from the answer and additional sections and (re)synthesizing
+@@ -355,11 +375,13 @@
+ * @param msg: msg to normalize.
+ * @param qinfo: original query.
+ * @param region: where to allocate synthesized CNAMEs.
++ * @param env: module env with config options.
+ * @return 0 on error.
+ */
+ static int
+ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
+- struct query_info* qinfo, struct regional* region)
++ struct query_info* qinfo, struct regional* region,
++ struct module_env* env)
+ {
+ uint8_t* sname = qinfo->qname;
+ size_t snamelen = qinfo->qname_len;
+@@ -511,6 +533,7 @@
+
+ /* Mark additional names from AUTHORITY */
+ while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) {
++ /* protect internals of recursor by making sure to del these */
+ if(rrset->type==LDNS_RR_TYPE_DNAME ||
+ rrset->type==LDNS_RR_TYPE_CNAME ||
+ rrset->type==LDNS_RR_TYPE_A ||
+@@ -519,6 +542,13 @@
+ "RRset:", pkt, msg, prev, &rrset);
+ continue;
+ }
++ /* Allowed list of types in the authority section */
++ if(env->cfg->harden_unknown_additional &&
++ !type_allowed_in_authority_section(rrset->type)) {
++ remove_rrset("normalize: removing irrelevant "
++ "RRset:", pkt, msg, prev, &rrset);
++ continue;
++ }
+ /* only one NS set allowed in authority section */
+ if(rrset->type==LDNS_RR_TYPE_NS) {
+ /* NS set must be pertinent to the query */
+@@ -576,7 +606,6 @@
+ * found in ANSWER and AUTHORITY. */
+ /* These records have not been marked OK previously */
+ while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) {
+- /* FIXME: what about other types? */
+ if(rrset->type==LDNS_RR_TYPE_A ||
+ rrset->type==LDNS_RR_TYPE_AAAA)
+ {
+@@ -589,6 +618,7 @@
+ continue;
+ }
+ }
++ /* protect internals of recursor by making sure to del these */
+ if(rrset->type==LDNS_RR_TYPE_DNAME ||
+ rrset->type==LDNS_RR_TYPE_CNAME ||
+ rrset->type==LDNS_RR_TYPE_NS) {
+@@ -596,6 +626,13 @@
+ "RRset:", pkt, msg, prev, &rrset);
+ continue;
+ }
++ /* Allowed list of types in the additional section */
++ if(env->cfg->harden_unknown_additional &&
++ !type_allowed_in_additional_section(rrset->type)) {
++ remove_rrset("normalize: removing irrelevant "
++ "RRset:", pkt, msg, prev, &rrset);
++ continue;
++ }
+ prev = rrset;
+ rrset = rrset->rrset_all_next;
+ }
+@@ -679,6 +716,56 @@
+ return 0;
+ }
+
++/** Remove individual RRs, if the length is wrong. Returns true if the RRset
++ * has been removed. */
++static int
++scrub_sanitize_rr_length(sldns_buffer* pkt, struct msg_parse* msg,
++ struct rrset_parse* prev, struct rrset_parse** rrset, int* added_ede,
++ struct module_qstate* qstate)
++{
++ struct rr_parse* rr, *rr_prev = NULL;
++ for(rr = (*rrset)->rr_first; rr; rr = rr->next) {
++
++ /* Sanity check for length of records
++ * An A record should be 6 bytes only
++ * (2 bytes for length and 4 for IPv4 addr)*/
++ if((*rrset)->type == LDNS_RR_TYPE_A && rr->size != 6 ) {
++ if(!*added_ede) {
++ *added_ede = 1;
++ errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
++ LDNS_EDE_OTHER);
++ }
++ if(msgparse_rrset_remove_rr("sanitize: removing type A RR of inappropriate length:",
++ pkt, *rrset, rr_prev, rr, NULL, 0)) {
++ remove_rrset("sanitize: removing type A RRset of inappropriate length:",
++ pkt, msg, prev, rrset);
++ return 1;
++ }
++ continue;
++ }
++
++ /* Sanity check for length of records
++ * An AAAA record should be 18 bytes only
++ * (2 bytes for length and 16 for IPv6 addr)*/
++ if((*rrset)->type == LDNS_RR_TYPE_AAAA && rr->size != 18 ) {
++ if(!*added_ede) {
++ *added_ede = 1;
++ errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
++ LDNS_EDE_OTHER);
++ }
++ if(msgparse_rrset_remove_rr("sanitize: removing type AAAA RR of inappropriate length:",
++ pkt, *rrset, rr_prev, rr, NULL, 0)) {
++ remove_rrset("sanitize: removing type AAAA RRset of inappropriate length:",
++ pkt, msg, prev, rrset);
++ return 1;
++ }
++ continue;
++ }
++ rr_prev = rr;
++ }
++ return 0;
++}
++
+ /**
+ * Given a response event, remove suspect RRsets from the response.
+ * "Suspect" rrsets are potentially poison. Note that this routine expects
+@@ -691,15 +778,17 @@
+ * @param zonename: name of server zone.
+ * @param env: module environment with config and cache.
+ * @param ie: iterator environment with private address data.
++ * @param qstate: for setting errinf for EDE error messages.
+ * @return 0 on error.
+ */
+ static int
+ scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
+ struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
+- struct iter_env* ie)
++ struct iter_env* ie, struct module_qstate* qstate)
+ {
+ int del_addi = 0; /* if additional-holding rrsets are deleted, we
+ do not trust the normalized additional-A-AAAA any more */
++ int added_rrlen_ede = 0;
+ struct rrset_parse* rrset, *prev;
+ prev = NULL;
+ rrset = msg->rrset_first;
+@@ -744,6 +833,14 @@
+ rrset = msg->rrset_first;
+ while(rrset) {
+
++ /* Sanity check for length of records */
++ if(rrset->type == LDNS_RR_TYPE_A ||
++ rrset->type == LDNS_RR_TYPE_AAAA) {
++ if(scrub_sanitize_rr_length(pkt, msg, prev, &rrset,
++ &added_rrlen_ede, qstate))
++ continue;
++ }
++
+ /* remove private addresses */
+ if( (rrset->type == LDNS_RR_TYPE_A ||
+ rrset->type == LDNS_RR_TYPE_AAAA)) {
+@@ -817,7 +914,8 @@
+ int
+ scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
+ struct query_info* qinfo, uint8_t* zonename, struct regional* region,
+- struct module_env* env, struct iter_env* ie)
++ struct module_env* env, struct module_qstate* qstate,
++ struct iter_env* ie)
+ {
+ /* basic sanity checks */
+ log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS,
+@@ -846,10 +944,10 @@
+ }
+
+ /* normalize the response, this cleans up the additional. */
+- if(!scrub_normalize(pkt, msg, qinfo, region))
++ if(!scrub_normalize(pkt, msg, qinfo, region, env))
+ return 0;
+ /* delete all out-of-zone information */
+- if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
++ if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
+ return 0;
+ return 1;
+ }
+--- contrib/unbound/iterator/iter_scrub.h.orig
++++ contrib/unbound/iterator/iter_scrub.h
+@@ -48,6 +48,7 @@
+ struct regional;
+ struct module_env;
+ struct iter_env;
++struct module_qstate;
+
+ /**
+ * Cleanup the passed dns message.
+@@ -59,11 +60,13 @@
+ * Used to determine out of bailiwick information.
+ * @param regional: where to allocate (new) parts of the message.
+ * @param env: module environment with config settings and cache.
++ * @param qstate: for setting errinf for EDE error messages.
+ * @param ie: iterator module environment data.
+ * @return: false if the message is total waste. true if scrubbed with success.
+ */
+ int scrub_message(struct sldns_buffer* pkt, struct msg_parse* msg,
+ struct query_info* qinfo, uint8_t* zonename, struct regional* regional,
+- struct module_env* env, struct iter_env* ie);
++ struct module_env* env, struct module_qstate* qstate,
++ struct iter_env* ie);
+
+ #endif /* ITERATOR_ITER_SCRUB_H */
+--- contrib/unbound/iterator/iter_utils.c.orig
++++ contrib/unbound/iterator/iter_utils.c
+@@ -71,6 +71,11 @@
+ /** time when nameserver glue is said to be 'recent' */
+ #define SUSPICION_RECENT_EXPIRY 86400
+
++/** if NAT64 is enabled and no NAT64 prefix is configured, first fall back to
++ * DNS64 prefix. If that is not configured, fall back to this default value.
++ */
++static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96";
++
+ /** fillup fetch policy array */
+ static void
+ fetch_fill(struct iter_env* ie, const char* str)
+@@ -142,6 +147,7 @@
+ int
+ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg)
+ {
++ const char *nat64_prefix;
+ int i;
+ /* target fetch policy */
+ if(!read_fetch_policy(iter_env, cfg->target_fetch_policy))
+@@ -172,8 +178,32 @@
+ }
+
+ }
++
++ nat64_prefix = cfg->nat64_prefix;
++ if(!nat64_prefix)
++ nat64_prefix = cfg->dns64_prefix;
++ if(!nat64_prefix)
++ nat64_prefix = DEFAULT_NAT64_PREFIX;
++ if(!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr,
++ &iter_env->nat64_prefix_addrlen,
++ &iter_env->nat64_prefix_net)) {
++ log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix);
++ return 0;
++ }
++ if(!addr_is_ip6(&iter_env->nat64_prefix_addr,
++ iter_env->nat64_prefix_addrlen)) {
++ log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix);
++ return 0;
++ }
++ if(!prefixnet_is_nat64(iter_env->nat64_prefix_net)) {
++ log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s",
++ nat64_prefix);
++ return 0;
++ }
++
+ iter_env->supports_ipv6 = cfg->do_ip6;
+ iter_env->supports_ipv4 = cfg->do_ip4;
++ iter_env->use_nat64 = cfg->do_nat64;
+ iter_env->outbound_msg_retry = cfg->outbound_msg_retry;
+ iter_env->max_sent_count = cfg->max_sent_count;
+ iter_env->max_query_restarts = cfg->max_query_restarts;
+@@ -240,7 +270,8 @@
+ if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) {
+ return -1; /* there is no ip6 available */
+ }
+- if(!iter_env->supports_ipv4 && !addr_is_ip6(&a->addr, a->addrlen)) {
++ if(!iter_env->supports_ipv4 && !iter_env->use_nat64 &&
++ !addr_is_ip6(&a->addr, a->addrlen)) {
+ return -1; /* there is no ip4 available */
+ }
+ /* check lameness - need zone , class info */
+@@ -747,10 +778,15 @@
+
+ int
+ iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
+- struct delegpt* dp, int supports_ipv4, int supports_ipv6)
++ struct delegpt* dp, int supports_ipv4, int supports_ipv6,
++ int use_nat64)
+ {
+ struct delegpt_ns* ns;
+ struct delegpt_addr* a;
++
++ if(supports_ipv6 && use_nat64)
++ supports_ipv4 = 1;
++
+ /* check:
+ * o RD qflag is on.
+ * o no addresses are provided.
+@@ -1310,8 +1346,7 @@
+ for(a=dp->target_list; a; a = a->next_target) {
+ if(a->attempts >= outbound_msg_retry) {
+ /* add back to result list */
+- a->next_result = dp->result_list;
+- dp->result_list = a;
++ delegpt_add_to_result_list(dp, a);
+ }
+ if(a->attempts > d)
+ a->attempts -= d;
+--- contrib/unbound/iterator/iter_utils.h.orig
++++ contrib/unbound/iterator/iter_utils.h
+@@ -189,10 +189,13 @@
+ * if not, then the IPv4 addresses are useless.
+ * @param supports_ipv6: if we support ipv6 for lookups to the target.
+ * if not, then the IPv6 addresses are useless.
++ * @param use_nat64: if we support NAT64 for lookups to the target.
++ * if yes, IPv4 addresses are useful even if we don't support IPv4.
+ * @return true if dp is useless.
+ */
+-int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
+- struct delegpt* dp, int supports_ipv4, int supports_ipv6);
++int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
++ struct delegpt* dp, int supports_ipv4, int supports_ipv6,
++ int use_nat64);
+
+ /**
+ * See if qname has DNSSEC needs. This is true if there is a trust anchor above
+--- contrib/unbound/iterator/iterator.c.orig
++++ contrib/unbound/iterator/iterator.c
+@@ -255,7 +255,7 @@
+ log_err("out of memory adding missing");
+ }
+ delegpt_mark_neg(dpns, qstate->qinfo.qtype);
+- if((dpns->got4 == 2 || !ie->supports_ipv4) &&
++ if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+ (dpns->got6 == 2 || !ie->supports_ipv6)) {
+ dpns->resolved = 1; /* mark as failed */
+ target_count_increase_nx(super_iq, 1);
+@@ -302,81 +302,65 @@
+ static int
+ error_response_cache(struct module_qstate* qstate, int id, int rcode)
+ {
+- if(!qstate->no_cache_store) {
+- /* store in cache */
+- struct reply_info err;
+- if(qstate->prefetch_leeway > NORR_TTL) {
+- verbose(VERB_ALGO, "error response for prefetch in cache");
+- /* attempt to adjust the cache entry prefetch */
+- if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo,
+- NORR_TTL, qstate->query_flags))
+- return error_response(qstate, id, rcode);
+- /* if that fails (not in cache), fall through to store err */
++ struct reply_info err;
++ struct msgreply_entry* msg;
++ if(qstate->no_cache_store) {
++ return error_response(qstate, id, rcode);
++ }
++ if(qstate->prefetch_leeway > NORR_TTL) {
++ verbose(VERB_ALGO, "error response for prefetch in cache");
++ /* attempt to adjust the cache entry prefetch */
++ if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo,
++ NORR_TTL, qstate->query_flags))
++ return error_response(qstate, id, rcode);
++ /* if that fails (not in cache), fall through to store err */
++ }
++ if((msg=msg_cache_lookup(qstate->env,
++ qstate->qinfo.qname, qstate->qinfo.qname_len,
++ qstate->qinfo.qtype, qstate->qinfo.qclass,
++ qstate->query_flags, 0,
++ qstate->env->cfg->serve_expired_ttl_reset)) != NULL) {
++ struct reply_info* rep = (struct reply_info*)msg->entry.data;
++ if(qstate->env->cfg->serve_expired &&
++ qstate->env->cfg->serve_expired_ttl_reset && rep &&
++ *qstate->env->now + qstate->env->cfg->serve_expired_ttl
++ > rep->serve_expired_ttl) {
++ verbose(VERB_ALGO, "reset serve-expired-ttl for "
++ "response in cache");
++ rep->serve_expired_ttl = *qstate->env->now +
++ qstate->env->cfg->serve_expired_ttl;
+ }
+- if(qstate->env->cfg->serve_expired) {
+- /* if serving expired contents, and such content is
+- * already available, don't overwrite this servfail */
+- struct msgreply_entry* msg;
+- if((msg=msg_cache_lookup(qstate->env,
+- qstate->qinfo.qname, qstate->qinfo.qname_len,
+- qstate->qinfo.qtype, qstate->qinfo.qclass,
+- qstate->query_flags, 0,
+- qstate->env->cfg->serve_expired_ttl_reset))
+- != NULL) {
+- if(qstate->env->cfg->serve_expired_ttl_reset) {
+- struct reply_info* rep =
+- (struct reply_info*)msg->entry.data;
+- if(rep && *qstate->env->now +
+- qstate->env->cfg->serve_expired_ttl >
+- rep->serve_expired_ttl) {
+- rep->serve_expired_ttl =
+- *qstate->env->now +
+- qstate->env->cfg->serve_expired_ttl;
+- }
+- }
+- lock_rw_unlock(&msg->entry.lock);
+- return error_response(qstate, id, rcode);
+- }
+- /* serving expired contents, but nothing is cached
+- * at all, so the servfail cache entry is useful
+- * (stops waste of time on this servfail NORR_TTL) */
+- } else {
+- /* don't overwrite existing (non-expired) data in
+- * cache with a servfail */
+- struct msgreply_entry* msg;
+- if((msg=msg_cache_lookup(qstate->env,
+- qstate->qinfo.qname, qstate->qinfo.qname_len,
+- qstate->qinfo.qtype, qstate->qinfo.qclass,
+- qstate->query_flags, *qstate->env->now, 0))
+- != NULL) {
+- struct reply_info* rep = (struct reply_info*)
+- msg->entry.data;
+- if(FLAGS_GET_RCODE(rep->flags) ==
+- LDNS_RCODE_NOERROR ||
+- FLAGS_GET_RCODE(rep->flags) ==
+- LDNS_RCODE_NXDOMAIN) {
+- /* we have a good entry,
+- * don't overwrite */
+- lock_rw_unlock(&msg->entry.lock);
+- return error_response(qstate, id, rcode);
+- }
+- lock_rw_unlock(&msg->entry.lock);
+- }
+-
++ if(rep && (FLAGS_GET_RCODE(rep->flags) ==
++ LDNS_RCODE_NOERROR ||
++ FLAGS_GET_RCODE(rep->flags) ==
++ LDNS_RCODE_NXDOMAIN ||
++ FLAGS_GET_RCODE(rep->flags) ==
++ LDNS_RCODE_YXDOMAIN) &&
++ (qstate->env->cfg->serve_expired ||
++ *qstate->env->now <= rep->ttl)) {
++ /* we have a good entry, don't overwrite */
++ lock_rw_unlock(&msg->entry.lock);
++ return error_response(qstate, id, rcode);
+ }
+- memset(&err, 0, sizeof(err));
+- err.flags = (uint16_t)(BIT_QR | BIT_RA);
+- FLAGS_SET_RCODE(err.flags, rcode);
+- err.qdcount = 1;
+- err.ttl = NORR_TTL;
+- err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl);
+- err.serve_expired_ttl = NORR_TTL;
+- /* do not waste time trying to validate this servfail */
+- err.security = sec_status_indeterminate;
+- verbose(VERB_ALGO, "store error response in message cache");
+- iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL,
+- qstate->query_flags, qstate->qstarttime);
+- }
++ lock_rw_unlock(&msg->entry.lock);
++ /* nothing interesting is cached (already error response or
++ * expired good record when we don't serve expired), so this
++ * servfail cache entry is useful (stops waste of time on this
++ * servfail NORR_TTL) */
++ }
++ /* store in cache */
++ memset(&err, 0, sizeof(err));
++ err.flags = (uint16_t)(BIT_QR | BIT_RA);
++ FLAGS_SET_RCODE(err.flags, rcode);
++ err.qdcount = 1;
++ err.ttl = NORR_TTL;
++ err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl);
++ err.serve_expired_ttl = NORR_TTL;
++ /* do not waste time trying to validate this servfail */
++ err.security = sec_status_indeterminate;
++ verbose(VERB_ALGO, "store error response in message cache");
++ iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL,
++ qstate->query_flags, qstate->qstarttime);
+ return error_response(qstate, id, rcode);
+ }
+
+@@ -590,6 +574,54 @@
+ return 1;
+ }
+
++/** fill fail address for later recovery */
++static void
++fill_fail_addr(struct iter_qstate* iq, struct sockaddr_storage* addr,
++ socklen_t addrlen)
++{
++ if(addrlen == 0) {
++ iq->fail_addr_type = 0;
++ return;
++ }
++ if(((struct sockaddr_in*)addr)->sin_family == AF_INET) {
++ iq->fail_addr_type = 4;
++ memcpy(&iq->fail_addr.in,
++ &((struct sockaddr_in*)addr)->sin_addr,
++ sizeof(iq->fail_addr.in));
++ }
++#ifdef AF_INET6
++ else if(((struct sockaddr_in*)addr)->sin_family == AF_INET6) {
++ iq->fail_addr_type = 6;
++ memcpy(&iq->fail_addr.in6,
++ &((struct sockaddr_in6*)addr)->sin6_addr,
++ sizeof(iq->fail_addr.in6));
++ }
++#endif
++ else {
++ iq->fail_addr_type = 0;
++ }
++}
++
++/** print fail addr to string */
++static void
++print_fail_addr(struct iter_qstate* iq, char* buf, size_t len)
++{
++ if(iq->fail_addr_type == 4) {
++ if(inet_ntop(AF_INET, &iq->fail_addr.in, buf,
++ (socklen_t)len) == 0)
++ (void)strlcpy(buf, "(inet_ntop error)", len);
++ }
++#ifdef AF_INET6
++ else if(iq->fail_addr_type == 6) {
++ if(inet_ntop(AF_INET6, &iq->fail_addr.in6, buf,
++ (socklen_t)len) == 0)
++ (void)strlcpy(buf, "(inet_ntop error)", len);
++ }
++#endif
++ else
++ (void)strlcpy(buf, "", len);
++}
++
+ /** add response specific error information for log servfail */
+ static void
+ errinf_reply(struct module_qstate* qstate, struct iter_qstate* iq)
+@@ -597,16 +629,14 @@
+ if(qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail)
+ return;
+ if((qstate->reply && qstate->reply->remote_addrlen != 0) ||
+- (iq->fail_reply && iq->fail_reply->remote_addrlen != 0)) {
++ (iq->fail_addr_type != 0)) {
+ char from[256], frm[512];
+ if(qstate->reply && qstate->reply->remote_addrlen != 0)
+ addr_to_str(&qstate->reply->remote_addr,
+ qstate->reply->remote_addrlen, from,
+ sizeof(from));
+ else
+- addr_to_str(&iq->fail_reply->remote_addr,
+- iq->fail_reply->remote_addrlen, from,
+- sizeof(from));
++ print_fail_addr(iq, from, sizeof(from));
+ snprintf(frm, sizeof(frm), "from %s", from);
+ errinf(qstate, frm);
+ }
+@@ -1137,7 +1167,7 @@
+ * Generate a NS check request to obtain authoritative information
+ * on an NS rrset.
+ *
+- * @param qstate: the qtstate that triggered the need to prime.
++ * @param qstate: the qstate that triggered the need to prime.
+ * @param iq: iterator query state.
+ * @param id: module id.
+ */
+@@ -1419,6 +1449,39 @@
+ }
+ iq->qchase.qname = sname;
+ iq->qchase.qname_len = slen;
++ if(qstate->env->auth_zones) {
++ /* apply rpz qname triggers after cname */
++ struct dns_msg* forged_response =
++ rpz_callback_from_iterator_cname(qstate, iq);
++ while(forged_response && reply_find_rrset_section_an(
++ forged_response->rep, iq->qchase.qname,
++ iq->qchase.qname_len, LDNS_RR_TYPE_CNAME,
++ iq->qchase.qclass)) {
++ /* another cname to follow */
++ if(!handle_cname_response(qstate, iq, forged_response,
++ &sname, &slen)) {
++ errinf(qstate, "malloc failure, CNAME info");
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
++ }
++ iq->qchase.qname = sname;
++ iq->qchase.qname_len = slen;
++ forged_response =
++ rpz_callback_from_iterator_cname(qstate, iq);
++ }
++ if(forged_response != NULL) {
++ qstate->ext_state[id] = module_finished;
++ qstate->return_rcode = LDNS_RCODE_NOERROR;
++ qstate->return_msg = forged_response;
++ iq->response = forged_response;
++ next_state(iq, FINISHED_STATE);
++ if(!iter_prepend(iq, qstate->return_msg, qstate->region)) {
++ log_err("rpz: after cached cname, prepend rrsets: out of memory");
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
++ }
++ qstate->return_msg->qinfo = qstate->qinfo;
++ return 0;
++ }
++ }
+ /* This *is* a query restart, even if it is a cheap
+ * one. */
+ iq->dp = NULL;
+@@ -1451,6 +1514,19 @@
+ errinf(qstate, "malloc failure for forward zone");
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
++ if((qstate->query_flags&BIT_RD)==0) {
++ /* If the server accepts RD=0 queries and forwards
++ * with RD=1, then if the server is listed as an NS
++ * entry, it starts query loops. Stop that loop by
++ * disallowing the query. The RD=0 was previously used
++ * to check the cache with allow_snoop. For stubs,
++ * the iterator pass would have primed the stub and
++ * then cached information can be used for further
++ * queries. */
++ verbose(VERB_ALGO, "cannot forward RD=0 query, to stop query loops");
++ errinf(qstate, "cannot forward RD=0 query");
++ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
++ }
+ iq->refetch_glue = 0;
+ iq->minimisation_state = DONOT_MINIMISE_STATE;
+ /* the request has been forwarded.
+@@ -1560,18 +1636,19 @@
+
+ /* see if this dp not useless.
+ * It is useless if:
+- * o all NS items are required glue.
++ * o all NS items are required glue.
+ * or the query is for NS item that is required glue.
+ * o no addresses are provided.
+ * o RD qflag is on.
+ * Instead, go up one level, and try to get even further
+- * If the root was useless, use safety belt information.
++ * If the root was useless, use safety belt information.
+ * Only check cache returns, because replies for servers
+ * could be useless but lead to loops (bumping into the
+ * same server reply) if useless-checked.
+ */
+- if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
+- iq->dp, ie->supports_ipv4, ie->supports_ipv6)) {
++ if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags,
++ iq->dp, ie->supports_ipv4, ie->supports_ipv6,
++ ie->use_nat64)) {
+ struct delegpt* retdp = NULL;
+ if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &retdp)) {
+ if(retdp) {
+@@ -1932,7 +2009,7 @@
+ break;
+ }
+ /* Send the A request. */
+- if(ie->supports_ipv4 &&
++ if((ie->supports_ipv4 || ie->use_nat64) &&
+ ((ns->lame && !ns->done_pside4) ||
+ (!ns->lame && !ns->got4))) {
+ if(!generate_target_query(qstate, iq, id,
+@@ -2085,14 +2162,14 @@
+ /* if this nameserver is at a delegation point, but that
+ * delegation point is a stub and we cannot go higher, skip*/
+ if( ((ie->supports_ipv6 && !ns->done_pside6) ||
+- (ie->supports_ipv4 && !ns->done_pside4)) &&
++ ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) &&
+ !can_have_last_resort(qstate->env, ns->name, ns->namelen,
+ iq->qchase.qclass, NULL)) {
+ log_nametypeclass(VERB_ALGO, "cannot pside lookup ns "
+ "because it is also a stub/forward,",
+ ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
+ if(ie->supports_ipv6) ns->done_pside6 = 1;
+- if(ie->supports_ipv4) ns->done_pside4 = 1;
++ if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1;
+ continue;
+ }
+ /* query for parent-side A and AAAA for nameservers */
+@@ -2117,7 +2194,7 @@
+ return 0;
+ }
+ }
+- if(ie->supports_ipv4 && !ns->done_pside4) {
++ if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) {
+ /* Send the A request. */
+ if(!generate_parentside_target_query(qstate, iq, id,
+ ns->name, ns->namelen,
+@@ -2259,6 +2336,8 @@
+ int tf_policy;
+ struct delegpt_addr* target;
+ struct outbound_entry* outq;
++ struct sockaddr_storage real_addr;
++ socklen_t real_addrlen;
+ int auth_fallback = 0;
+ uint8_t* qout_orig = NULL;
+ size_t qout_orig_len = 0;
+@@ -2384,7 +2463,7 @@
+ }
+ if(!ie->supports_ipv6)
+ delegpt_no_ipv6(iq->dp);
+- if(!ie->supports_ipv4)
++ if(!ie->supports_ipv4 && !ie->use_nat64)
+ delegpt_no_ipv4(iq->dp);
+ delegpt_log(VERB_ALGO, iq->dp);
+
+@@ -2805,23 +2884,36 @@
+ /* We have a valid target. */
+ if(verbosity >= VERB_QUERY) {
+ log_query_info(VERB_QUERY, "sending query:", &iq->qinfo_out);
+- log_name_addr(VERB_QUERY, "sending to target:", iq->dp->name,
++ log_name_addr(VERB_QUERY, "sending to target:", iq->dp->name,
+ &target->addr, target->addrlen);
+ verbose(VERB_ALGO, "dnssec status: %s%s",
+ iq->dnssec_expected?"expected": "not expected",
+ iq->dnssec_lame_query?" but lame_query anyway": "");
+ }
++
++ real_addr = target->addr;
++ real_addrlen = target->addrlen;
++
++ if(ie->use_nat64 && target->addr.ss_family == AF_INET) {
++ addr_to_nat64(&target->addr, &ie->nat64_prefix_addr,
++ ie->nat64_prefix_addrlen, ie->nat64_prefix_net,
++ &real_addr, &real_addrlen);
++ log_name_addr(VERB_QUERY, "applied NAT64:",
++ iq->dp->name, &real_addr, real_addrlen);
++ }
++
+ fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query));
+ outq = (*qstate->env->send_query)(&iq->qinfo_out,
+ iq->chase_flags | (iq->chase_to_rd?BIT_RD:0),
+ /* unset CD if to forwarder(RD set) and not dnssec retry
+ * (blacklist nonempty) and no trust-anchors are configured
+ * above the qname or on the first attempt when dnssec is on */
+- EDNS_DO| ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&&
++ (qstate->env->cfg->disable_edns_do?0:EDNS_DO)|
++ ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&&
+ !qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env,
+ &iq->qinfo_out)||target->attempts==1)?0:BIT_CD),
+ iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted(
+- ie, iq), sq_check_ratelimit, &target->addr, target->addrlen,
++ ie, iq), sq_check_ratelimit, &real_addr, real_addrlen,
+ iq->dp->name, iq->dp->namelen,
+ (iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream),
+ (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream),
+@@ -2838,7 +2930,7 @@
+ return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ }
+ log_addr(VERB_QUERY, "error sending query to auth server",
+- &target->addr, target->addrlen);
++ &real_addr, real_addrlen);
+ if(qstate->env->cfg->qname_minimisation)
+ iq->minimisation_state = SKIP_MINIMISE_STATE;
+ return next_state(iq, QUERYTARGETS_STATE);
+@@ -2882,7 +2974,7 @@
+ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
+ struct iter_env* ie, int id)
+ {
+- int dnsseclame = 0;
++ int dnsseclame = 0, origtypecname = 0, orig_empty_nodata_found;
+ enum response_type type;
+
+ iq->num_current_queries--;
+@@ -2902,12 +2994,25 @@
+ return next_state(iq, QUERYTARGETS_STATE);
+ }
+ iq->timeout_count = 0;
++ orig_empty_nodata_found = iq->empty_nodata_found;
+ type = response_type_from_server(
+ (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
+- iq->response, &iq->qinfo_out, iq->dp);
++ iq->response, &iq->qinfo_out, iq->dp, &iq->empty_nodata_found);
+ iq->chase_to_rd = 0;
+ /* remove TC flag, if this is erroneously set by TCP upstream */
+ iq->response->rep->flags &= ~BIT_TC;
++ if(orig_empty_nodata_found != iq->empty_nodata_found &&
++ iq->empty_nodata_found < EMPTY_NODATA_RETRY_COUNT) {
++ /* try to search at another server */
++ if(qstate->reply) {
++ struct delegpt_addr* a = delegpt_find_addr(
++ iq->dp, &qstate->reply->remote_addr,
++ qstate->reply->remote_addrlen);
++ /* make selection disprefer it */
++ if(a) a->lame = 1;
++ }
++ return next_state(iq, QUERYTARGETS_STATE);
++ }
+ if(type == RESPONSE_TYPE_REFERRAL && (iq->chase_flags&BIT_RD) &&
+ !iq->auth_zone_response) {
+ /* When forwarding (RD bit is set), we handle referrals
+@@ -2965,6 +3070,8 @@
+ /* YXDOMAIN is a permanent error, no need to retry */
+ type = RESPONSE_TYPE_ANSWER;
+ }
++ if(type == RESPONSE_TYPE_CNAME)
++ origtypecname = 1;
+ if(type == RESPONSE_TYPE_CNAME && iq->response->rep->an_numrrsets >= 1
+ && ntohs(iq->response->rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_DNAME) {
+ uint8_t* sname = NULL;
+@@ -3050,11 +3157,14 @@
+ iq->minimisation_state = DONOT_MINIMISE_STATE;
+ }
+ if(FLAGS_GET_RCODE(iq->response->rep->flags) ==
+- LDNS_RCODE_NXDOMAIN) {
++ LDNS_RCODE_NXDOMAIN && !origtypecname) {
+ /* Stop resolving when NXDOMAIN is DNSSEC
+ * signed. Based on assumption that nameservers
+ * serving signed zones do not return NXDOMAIN
+ * for empty-non-terminals. */
++ /* If this response is actually a CNAME type,
++ * the nxdomain rcode may not be for the qname,
++ * and so it is not the final response. */
+ if(iq->dnssec_expected)
+ return final_state(iq);
+ /* Make subrequest to validate intermediate
+@@ -3182,7 +3292,7 @@
+ (*qstate->env->detach_subs)(qstate);
+ iq->num_target_queries = 0;
+ iq->response = NULL;
+- iq->fail_reply = NULL;
++ iq->fail_addr_type = 0;
+ verbose(VERB_ALGO, "cleared outbound list for next round");
+ return next_state(iq, QUERYTARGETS_STATE);
+ } else if(type == RESPONSE_TYPE_CNAME) {
+@@ -3438,7 +3548,7 @@
+ iq->response->rep->flags &= ~(BIT_RD|BIT_RA); /* ignore rec-lame */
+ type = response_type_from_server(
+ (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
+- iq->response, &iq->qchase, iq->dp);
++ iq->response, &iq->qchase, iq->dp, NULL);
+ if(type == RESPONSE_TYPE_ANSWER) {
+ qstate->return_rcode = LDNS_RCODE_NOERROR;
+ qstate->return_msg = iq->response;
+@@ -3564,7 +3674,7 @@
+ } else {
+ verbose(VERB_ALGO, "iterator TargetResponse failed");
+ delegpt_mark_neg(dpns, qstate->qinfo.qtype);
+- if((dpns->got4 == 2 || !ie->supports_ipv4) &&
++ if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) &&
+ (dpns->got6 == 2 || !ie->supports_ipv6)) {
+ dpns->resolved = 1; /* fail the target */
+ /* do not count cached answers */
+@@ -3809,6 +3919,26 @@
+ /* make sure QR flag is on */
+ iq->response->rep->flags |= BIT_QR;
+
++ /* explicitly set the EDE string to NULL */
++ iq->response->rep->reason_bogus_str = NULL;
++ if((qstate->env->cfg->val_log_level >= 2 ||
++ qstate->env->cfg->log_servfail) && qstate->errinf &&
++ !qstate->env->cfg->val_log_squelch) {
++ char* err_str = errinf_to_str_misc(qstate);
++ if(err_str) {
++ size_t err_str_len = strlen(err_str);
++ verbose(VERB_ALGO, "iterator EDE: %s", err_str);
++ /* allocate space and store the error
++ * string */
++ iq->response->rep->reason_bogus_str = regional_alloc(
++ qstate->region,
++ sizeof(char) * (err_str_len+1));
++ memcpy(iq->response->rep->reason_bogus_str,
++ err_str, err_str_len+1);
++ }
++ free(err_str);
++ }
++
+ /* we have finished processing this query */
+ qstate->ext_state[id] = module_finished;
+
+@@ -3987,7 +4117,8 @@
+ }
+
+ /* parse message */
+- iq->fail_reply = qstate->reply;
++ fill_fail_addr(iq, &qstate->reply->remote_addr,
++ qstate->reply->remote_addrlen);
+ prs = (struct msg_parse*)regional_alloc(qstate->env->scratch,
+ sizeof(struct msg_parse));
+ if(!prs) {
+@@ -4031,7 +4162,7 @@
+
+ /* normalize and sanitize: easy to delete items from linked lists */
+ if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name,
+- qstate->env->scratch, qstate->env, ie)) {
++ qstate->env->scratch, qstate->env, qstate, ie)) {
+ /* if 0x20 enabled, start fallback, but we have no message */
+ if(event == module_event_capsfail && !iq->caps_fallback) {
+ iq->caps_fallback = 1;
+--- contrib/unbound/iterator/iterator.h.orig
++++ contrib/unbound/iterator/iterator.h
+@@ -101,9 +101,11 @@
+ * Chosen so that the UNKNOWN_SERVER_NICENESS falls within the band of a
+ * fast server, this causes server exploration as a side benefit. msec. */
+ #define RTT_BAND 400
++/** Number of retries for empty nodata packets before it is accepted. */
++#define EMPTY_NODATA_RETRY_COUNT 2
+
+ /**
+- * Global state for the iterator.
++ * Global state for the iterator.
+ */
+ struct iter_env {
+ /** A flag to indicate whether or not we have an IPv6 route */
+@@ -112,6 +114,18 @@
+ /** A flag to indicate whether or not we have an IPv4 route */
+ int supports_ipv4;
+
++ /** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */
++ int use_nat64;
++
++ /** NAT64 prefix address, cf. dns64_env->prefix_addr */
++ struct sockaddr_storage nat64_prefix_addr;
++
++ /** sizeof(sockaddr_in6) */
++ socklen_t nat64_prefix_addrlen;
++
++ /** CIDR mask length of NAT64 prefix */
++ int nat64_prefix_net;
++
+ /** A set of inetaddrs that should never be queried. */
+ struct iter_donotq* donotq;
+
+@@ -403,6 +417,11 @@
+ */
+ int refetch_glue;
+
++ /**
++ * This flag detects that a completely empty nodata was received,
++ * already so that it is accepted later. */
++ int empty_nodata_found;
++
+ /** list of pending queries to authoritative servers. */
+ struct outbound_list outlist;
+
+@@ -439,7 +458,14 @@
+ /** true if there have been parse failures of reply packets */
+ int parse_failures;
+ /** a failure printout address for last received answer */
+- struct comm_reply* fail_reply;
++ union {
++ struct in_addr in;
++#ifdef AF_INET6
++ struct in6_addr in6;
++#endif
++ } fail_addr;
++ /** which fail_addr, 0 is nothing, 4 or 6 */
++ int fail_addr_type;
+ };
+
+ /**
+--- contrib/unbound/libunbound/libworker.c.orig
++++ contrib/unbound/libunbound/libworker.c
+@@ -62,6 +62,7 @@
+ #include "util/random.h"
+ #include "util/config_file.h"
+ #include "util/netevent.h"
++#include "util/proxy_protocol.h"
+ #include "util/storage/lookup3.h"
+ #include "util/storage/slabhash.h"
+ #include "util/net_help.h"
+@@ -168,15 +169,15 @@
+ hints_delete(w->env->hints);
+ w->env->hints = NULL;
+ }
+- if(cfg->ssl_upstream || (cfg->tls_cert_bundle && cfg->tls_cert_bundle[0]) || cfg->tls_win_cert) {
+- w->sslctx = connect_sslctx_create(NULL, NULL,
+- cfg->tls_cert_bundle, cfg->tls_win_cert);
+- if(!w->sslctx) {
+- /* to make the setup fail after unlock */
+- hints_delete(w->env->hints);
+- w->env->hints = NULL;
+- }
++#ifdef HAVE_SSL
++ w->sslctx = connect_sslctx_create(NULL, NULL,
++ cfg->tls_cert_bundle, cfg->tls_win_cert);
++ if(!w->sslctx) {
++ /* to make the setup fail after unlock */
++ hints_delete(w->env->hints);
++ w->env->hints = NULL;
+ }
++#endif
+ if(!w->is_bg || w->is_bg_thread) {
+ lock_basic_unlock(&ctx->cfglock);
+ }
+@@ -265,6 +266,7 @@
+ w->env->kill_sub = &mesh_state_delete;
+ w->env->detect_cycle = &mesh_detect_cycle;
+ comm_base_timept(w->base, &w->env->now, &w->env->now_tv);
++ pp_init(&sldns_write_uint16, &sldns_write_uint32);
+ return w;
+ }
+
+@@ -605,6 +607,8 @@
+ edns->opt_list_out = NULL;
+ edns->opt_list_inplace_cb_out = NULL;
+ edns->padding_block_size = 0;
++ edns->cookie_present = 0;
++ edns->cookie_valid = 0;
+ if(sldns_buffer_capacity(w->back->udp_buff) < 65535)
+ edns->udp_size = (uint16_t)sldns_buffer_capacity(
+ w->back->udp_buff);
+--- contrib/unbound/libunbound/unbound-event.h.orig
++++ contrib/unbound/libunbound/unbound-event.h
+@@ -52,8 +52,8 @@
+ * unbound was compiled with, otherwise it wouldn't work, the event and
+ * event_base structures would be different.
+ */
+-#ifndef _UB_UNBOUND_EVENT_H
+-#define _UB_UNBOUND_EVENT_H
++#ifndef UB_UNBOUND_EVENT_H
++#define UB_UNBOUND_EVENT_H
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -262,4 +262,4 @@
+ }
+ #endif
+
+-#endif /* _UB_UNBOUND_H */
++#endif /* UB_UNBOUND_EVENT_H */
+--- contrib/unbound/libunbound/unbound.h.orig
++++ contrib/unbound/libunbound/unbound.h
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -36,7 +36,7 @@
+ /**
+ * \file
+ *
+- * This file contains functions to resolve DNS queries and
++ * This file contains functions to resolve DNS queries and
+ * validate the answers. Synchronously and asynchronously.
+ *
+ * Several ways to use this interface from an application wishing
+@@ -65,7 +65,7 @@
+ * ... or process() calls my_callback() with results.
+ *
+ * ... if the application has nothing more to do, wait for answer
+- * ub_wait(ctx);
++ * ub_wait(ctx);
+ *
+ * Application threaded. Blocking.
+ * Blocking, same as above. The current thread does the work.
+@@ -83,7 +83,7 @@
+ * CRYPTO_set_id_callback and CRYPTO_set_locking_callback.
+ *
+ * If no threading is compiled in, the above async example uses fork(2) to
+- * create a process to perform the work. The forked process exits when the
++ * create a process to perform the work. The forked process exits when the
+ * calling process exits, or ctx_delete() is called.
+ * Otherwise, for asynchronous with threading, a worker thread is created.
+ *
+@@ -94,8 +94,8 @@
+ * The second calls another worker thread (or process) to perform the work.
+ * And no buffers need to be set up, but a context-switch happens.
+ */
+-#ifndef _UB_UNBOUND_H
+-#define _UB_UNBOUND_H
++#ifndef UB_UNBOUND_H
++#define UB_UNBOUND_H
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -128,10 +128,10 @@
+ /** the class asked for */
+ int qclass;
+
+- /**
+- * a list of network order DNS rdata items, terminated with a
++ /**
++ * a list of network order DNS rdata items, terminated with a
+ * NULL pointer, so that data[0] is the first result entry,
+- * data[1] the second, and the last entry is NULL.
++ * data[1] the second, and the last entry is NULL.
+ * If there was no data, data[0] is NULL.
+ */
+ char** data;
+@@ -139,8 +139,8 @@
+ /** the length in bytes of the data items, len[i] for data[i] */
+ int* len;
+
+- /**
+- * canonical name for the result (the final cname).
++ /**
++ * canonical name for the result (the final cname).
+ * zero terminated string.
+ * May be NULL if no canonical name exists.
+ */
+@@ -165,9 +165,9 @@
+ */
+ int havedata;
+
+- /**
++ /**
+ * If there was no data, and the domain did not exist, this is true.
+- * If it is false, and there was no data, then the domain name
++ * If it is false, and there was no data, then the domain name
+ * is purported to exist, but the requested data type is not available.
+ */
+ int nxdomain;
+@@ -182,19 +182,19 @@
+ */
+ int secure;
+
+- /**
+- * If the result was not secure (secure==0), and this result is due
++ /**
++ * If the result was not secure (secure==0), and this result is due
+ * to a security failure, bogus is true.
+ * This means the data has been actively tampered with, signatures
+- * failed, expected signatures were not present, timestamps on
++ * failed, expected signatures were not present, timestamps on
+ * signatures were out of date and so on.
+ *
+- * If !secure and !bogus, this can happen if the data is not secure
+- * because security is disabled for that domain name.
++ * If !secure and !bogus, this can happen if the data is not secure
++ * because security is disabled for that domain name.
+ * This means the data is from a domain where data is not signed.
+ */
+ int bogus;
+-
++
+ /**
+ * If the result is bogus this contains a string (zero terminated)
+ * that describes the failure. There may be other errors as well
+@@ -222,7 +222,7 @@
+ * The readable function definition looks like:
+ * void my_callback(void* my_arg, int err, struct ub_result* result);
+ * It is called with
+- * void* my_arg: your pointer to a (struct of) data of your choice,
++ * void* my_arg: your pointer to a (struct of) data of your choice,
+ * or NULL.
+ * int err: if 0 all is OK, otherwise an error occurred and no results
+ * are forthcoming.
+@@ -301,8 +301,8 @@
+ * This is a power-users interface that lets you specify all sorts
+ * of options.
+ * @param str: the string is malloced and returned here. NULL on error.
+- * The caller must free() the string. In cases with multiple
+- * entries (auto-trust-anchor-file), a newline delimited list is
++ * The caller must free() the string. In cases with multiple
++ * entries (auto-trust-anchor-file), a newline delimited list is
+ * returned in the string.
+ * @return 0 if OK else an error code (malloc failure, syntax error).
+ */
+@@ -321,10 +321,10 @@
+ int ub_ctx_config(struct ub_ctx* ctx, const char* fname);
+
+ /**
+- * Set machine to forward DNS queries to, the caching resolver to use.
+- * IP4 or IP6 address. Forwards all DNS requests to that machine, which
+- * is expected to run a recursive resolver. If the proxy is not
+- * DNSSEC-capable, validation may fail. Can be called several times, in
++ * Set machine to forward DNS queries to, the caching resolver to use.
++ * IP4 or IP6 address. Forwards all DNS requests to that machine, which
++ * is expected to run a recursive resolver. If the proxy is not
++ * DNSSEC-capable, validation may fail. Can be called several times, in
+ * that case the addresses are used as backup servers.
+ *
+ * To read the list of nameservers from /etc/resolv.conf (from DHCP or so),
+@@ -389,7 +389,7 @@
+
+ /**
+ * Read list of hosts from the filename given.
+- * Usually "/etc/hosts".
++ * Usually "/etc/hosts".
+ * These addresses are not flagged as DNSSEC secure when queried for.
+ *
+ * @param ctx: context.
+@@ -403,7 +403,7 @@
+ /**
+ * Add a trust anchor to the given context.
+ * The trust anchor is a string, on one line, that holds a valid DNSKEY or
+- * DS RR.
++ * DS RR.
+ * @param ctx: context.
+ * At this time it is only possible to add trusted keys before the
+ * first resolve is done.
+@@ -465,7 +465,7 @@
+ * Set debug verbosity for the context
+ * Output is directed to stderr.
+ * @param ctx: context.
+- * @param d: debug level, 0 is off, 1 is very minimal, 2 is detailed,
++ * @param d: debug level, 0 is off, 1 is very minimal, 2 is detailed,
+ * and 3 is lots.
+ * @return 0 if OK, else error.
+ */
+@@ -474,10 +474,10 @@
+ /**
+ * Set a context behaviour for asynchronous action.
+ * @param ctx: context.
+- * @param dothread: if true, enables threading and a call to resolve_async()
++ * @param dothread: if true, enables threading and a call to resolve_async()
+ * creates a thread to handle work in the background.
+ * If false, a process is forked to handle work in the background.
+- * Changes to this setting after async() calls have been made have
++ * Changes to this setting after async() calls have been made have
+ * no effect (delete and re-create the context to change).
+ * @return 0 if OK, else error.
+ */
+@@ -495,7 +495,7 @@
+
+ /**
+ * Wait for a context to finish with results. Calls ub_process() after
+- * the wait for you. After the wait, there are no more outstanding
++ * the wait for you. After the wait, there are no more outstanding
+ * asynchronous queries.
+ * @param ctx: context.
+ * @return: 0 if OK, else error.
+@@ -530,11 +530,11 @@
+ * @param rrtype: type of RR in host order, 1 is A (address).
+ * @param rrclass: class of RR in host order, 1 is IN (for internet).
+ * @param result: the result data is returned in a newly allocated result
+- * structure. May be NULL on return, return value is set to an error
++ * structure. May be NULL on return, return value is set to an error
+ * in that case (out of memory).
+ * @return 0 if OK, else error.
+ */
+-int ub_resolve(struct ub_ctx* ctx, const char* name, int rrtype,
++int ub_resolve(struct ub_ctx* ctx, const char* name, int rrtype,
+ int rrclass, struct ub_result** result);
+
+ /**
+@@ -561,11 +561,11 @@
+ * If an error happens during processing, your callback will be called
+ * with error set to a nonzero value (and result==NULL).
+ * @param async_id: if you pass a non-NULL value, an identifier number is
+- * returned for the query as it is in progress. It can be used to
++ * returned for the query as it is in progress. It can be used to
+ * cancel the query.
+ * @return 0 if OK, else error.
+ */
+-int ub_resolve_async(struct ub_ctx* ctx, const char* name, int rrtype,
++int ub_resolve_async(struct ub_ctx* ctx, const char* name, int rrtype,
+ int rrclass, void* mydata, ub_callback_type callback, int* async_id);
+
+ /**
+@@ -589,7 +589,7 @@
+ */
+ void ub_resolve_free(struct ub_result* result);
+
+-/**
++/**
+ * Convert error value to a human readable string.
+ * @param err: error code from one of the libunbound functions.
+ * The error codes are from the type enum ub_ctx_err.
+@@ -605,7 +605,7 @@
+ int ub_ctx_print_local_zones(struct ub_ctx* ctx);
+
+ /**
+- * Add a new zone with the zonetype to the local authority info of the
++ * Add a new zone with the zonetype to the local authority info of the
+ * library.
+ * @param ctx: context. Is finalized by the routine.
+ * @param zone_name: name of the zone in text, "example.com"
+@@ -613,7 +613,7 @@
+ * @param zone_type: type of the zone (like for unbound.conf) in text.
+ * @return 0 if OK, else error.
+ */
+-int ub_ctx_zone_add(struct ub_ctx* ctx, const char *zone_name,
++int ub_ctx_zone_add(struct ub_ctx* ctx, const char *zone_name,
+ const char *zone_type);
+
+ /**
+@@ -649,7 +649,7 @@
+ */
+ const char* ub_version(void);
+
+-/**
++/**
+ * Some global statistics that are not in struct stats_info,
+ * this struct is shared on a shm segment (shm-key in unbound.conf)
+ */
+@@ -695,13 +695,22 @@
+ long long num_queries;
+ /** number of queries that have been dropped/ratelimited by ip. */
+ long long num_queries_ip_ratelimited;
++ /** number of queries with a valid DNS Cookie. */
++ long long num_queries_cookie_valid;
++ /** number of queries with only the client part of the DNS Cookie. */
++ long long num_queries_cookie_client;
++ /** number of queries with invalid DNS Cookie. */
++ long long num_queries_cookie_invalid;
+ /** number of queries that had a cache-miss. */
+ long long num_queries_missed_cache;
+ /** number of prefetch queries - cachehits with prefetch */
+ long long num_queries_prefetch;
+-
++ /** number of queries which are too late to process */
++ long long num_queries_timed_out;
++ /** the longest wait time in the queue */
++ long long max_query_time_us;
+ /**
+- * Sum of the querylistsize of the worker for
++ * Sum of the querylistsize of the worker for
+ * every query that missed cache. To calculate average.
+ */
+ long long sum_query_list_size;
+@@ -773,12 +782,12 @@
+ long long tcp_accept_usage;
+ /** expired answers served from cache */
+ long long ans_expired;
+- /** histogram data exported to array
++ /** histogram data exported to array
+ * if the array is the same size, no data is lost, and
+ * if all histograms are same size (is so by default) then
+ * adding up works well. */
+ long long hist[UB_STATS_BUCKET_NUM];
+-
++
+ /** number of message cache entries */
+ long long msg_cache_count;
+ /** number of rrset cache entries */
+@@ -788,6 +797,11 @@
+ /** number of key cache entries */
+ long long key_cache_count;
+
++ /** maximum number of collisions in the msg cache */
++ long long msg_cache_max_collisions;
++ /** maximum number of collisions in the rrset cache */
++ long long rrset_cache_max_collisions;
++
+ /** number of queries that used dnscrypt */
+ long long num_query_dnscrypt_crypted;
+ /** number of queries that queried dnscrypt certificates */
+@@ -819,6 +833,8 @@
+ /** number of queries answered from edns-subnet specific data, and
+ * the answer was from the edns-subnet cache. */
+ long long num_query_subnet_cache;
++ /** number of queries served from cachedb */
++ long long num_query_cachedb;
+ /** number of bytes in the stream wait buffers */
+ long long mem_stream_wait;
+ /** number of bytes in the HTTP2 query buffers */
+@@ -831,7 +847,7 @@
+ long long rpz_action[UB_STATS_RPZ_ACTION_NUM];
+ };
+
+-/**
++/**
+ * Statistics to send over the control pipe when asked
+ * This struct is made to be memcopied, sent in binary.
+ * shm mapped with (number+1) at num_threads+1, with first as total
+@@ -860,4 +876,4 @@
+ }
+ #endif
+
+-#endif /* _UB_UNBOUND_H */
++#endif /* UB_UNBOUND_H */
+--- contrib/unbound/services/authzone.c.orig
++++ contrib/unbound/services/authzone.c
+@@ -1306,8 +1306,8 @@
+ auth_data_delete(node);
+ }
+ if(z->rpz) {
+- rpz_remove_rr(z->rpz, z->namelen, dname, dname_len, rr_type,
+- rr_class, rdata, rdatalen);
++ rpz_remove_rr(z->rpz, z->name, z->namelen, dname, dname_len,
++ rr_type, rr_class, rdata, rdatalen);
+ }
+ return 1;
+ }
+@@ -2475,6 +2475,7 @@
+ struct auth_rrset** rrset)
+ {
+ struct auth_data* n = node;
++ struct auth_rrset* lookrrset;
+ *ce = NULL;
+ *rrset = NULL;
+ if(!node_exact) {
+@@ -2497,21 +2498,23 @@
+ /* see if the current candidate has issues */
+ /* not zone apex and has type NS */
+ if(n->namelen != z->namelen &&
+- (*rrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) &&
++ (lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) &&
+ /* delegate here, but DS at exact the dp has notype */
+ (qinfo->qtype != LDNS_RR_TYPE_DS ||
+ n->namelen != qinfo->qname_len)) {
+ /* referral */
+ /* this is ce and the lowernode is nonexisting */
+ *ce = n;
+- return 0;
++ *rrset = lookrrset;
++ node_exact = 0;
+ }
+ /* not equal to qname and has type DNAME */
+ if(n->namelen != qinfo->qname_len &&
+- (*rrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) {
++ (lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) {
+ /* this is ce and the lowernode is nonexisting */
+ *ce = n;
+- return 0;
++ *rrset = lookrrset;
++ node_exact = 0;
+ }
+
+ if(*ce == NULL && !domain_has_only_nsec3(n)) {
+@@ -5420,6 +5423,8 @@
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+ edns.padding_block_size = 0;
++ edns.cookie_present = 0;
++ edns.cookie_valid = 0;
+ if(sldns_buffer_capacity(buf) < 65535)
+ edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
+ else edns.udp_size = 65535;
+@@ -6613,6 +6618,8 @@
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+ edns.padding_block_size = 0;
++ edns.cookie_present = 0;
++ edns.cookie_valid = 0;
+ if(sldns_buffer_capacity(buf) < 65535)
+ edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
+ else edns.udp_size = 65535;
+@@ -7510,7 +7517,7 @@
+ size_t j;
+ if(!rrlist[i])
+ continue;
+- if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
++ if(rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
+ query_dname_compare(z->name, node->name)==0) {
+ /* omit RRSIGs over type ZONEMD at apex */
+ continue;
+@@ -7767,6 +7774,7 @@
+ enum sec_status sec;
+ struct val_env* ve;
+ int m;
++ int verified = 0;
+ m = modstack_find(mods, "validator");
+ if(m == -1) {
+ auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have "
+@@ -7790,7 +7798,7 @@
+ "zonemd: verify %s RRset with DNSKEY", typestr);
+ }
+ sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, NULL,
+- LDNS_SECTION_ANSWER, NULL);
++ LDNS_SECTION_ANSWER, NULL, &verified);
+ if(sec == sec_status_secure) {
+ return 1;
+ }
+--- contrib/unbound/services/cache/dns.c.orig
++++ contrib/unbound/services/cache/dns.c
+@@ -132,31 +132,6 @@
+ slabhash_remove(env->msg_cache, h, &k);
+ }
+
+-/** remove servfail msg cache entry */
+-static void
+-msg_del_servfail(struct module_env* env, struct query_info* qinfo,
+- uint32_t flags)
+-{
+- struct msgreply_entry* e;
+- /* see if the entry is servfail, and then remove it, so that
+- * lookups move from the cacheresponse stage to the recursionresponse
+- * stage */
+- e = msg_cache_lookup(env, qinfo->qname, qinfo->qname_len,
+- qinfo->qtype, qinfo->qclass, flags, 0, 0);
+- if(!e) return;
+- /* we don't check for the ttl here, also expired servfail entries
+- * are removed. If the user uses serve-expired, they would still be
+- * used to answer from cache */
+- if(FLAGS_GET_RCODE(((struct reply_info*)e->entry.data)->flags)
+- != LDNS_RCODE_SERVFAIL) {
+- lock_rw_unlock(&e->entry.lock);
+- return;
+- }
+- lock_rw_unlock(&e->entry.lock);
+- msg_cache_remove(env, qinfo->qname, qinfo->qname_len, qinfo->qtype,
+- qinfo->qclass, flags);
+-}
+-
+ void
+ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
+ hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside,
+@@ -182,13 +157,20 @@
+ /* we do not store the message, but we did store the RRs,
+ * which could be useful for delegation information */
+ verbose(VERB_ALGO, "TTL 0: dropped msg from cache");
+- free(rep);
+- /* if the message is SERVFAIL in cache, remove that SERVFAIL,
++ reply_info_delete(rep, NULL);
++ /* if the message is in the cache, remove that msg,
+ * so that the TTL 0 response can be returned for future
+- * responses (i.e. don't get answered by the servfail from
++ * responses (i.e. don't get answered from
+ * cache, but instead go to recursion to get this TTL0
+- * response). */
+- msg_del_servfail(env, qinfo, flags);
++ * response).
++ * Possible messages that could be in the cache:
++ * - SERVFAIL
++ * - NXDOMAIN
++ * - NODATA
++ * - an older record that is expired
++ * - an older record that did not yet expire */
++ msg_cache_remove(env, qinfo->qname, qinfo->qname_len,
++ qinfo->qtype, qinfo->qclass, flags);
+ return;
+ }
+
+@@ -610,6 +592,7 @@
+ if(!msg->rep)
+ return NULL;
+ msg->rep->reason_bogus = LDNS_EDE_NONE;
++ msg->rep->reason_bogus_str = NULL;
+ if(num > RR_COUNT_MAX)
+ return NULL; /* integer overflow protection */
+ msg->rep->rrsets = (struct ub_packed_rrset_key**)
+@@ -672,6 +655,10 @@
+ msg->rep->rrset_count = r->rrset_count;
+ msg->rep->authoritative = r->authoritative;
+ msg->rep->reason_bogus = r->reason_bogus;
++ if(r->reason_bogus_str) {
++ msg->rep->reason_bogus_str = regional_strdup(region, r->reason_bogus_str);
++ }
++
+ if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) {
+ return NULL;
+ }
+@@ -703,6 +690,28 @@
+ return msg;
+ }
+
++struct dns_msg*
++dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region)
++{
++ size_t i;
++ struct dns_msg* res = NULL;
++ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count);
++ if(!res) return NULL;
++ *res->rep = *origin->rep;
++ if(origin->rep->reason_bogus_str) {
++ res->rep->reason_bogus_str = regional_strdup(region,
++ origin->rep->reason_bogus_str);
++ }
++ for(i=0; i<res->rep->rrset_count; i++) {
++ res->rep->rrsets[i] = packed_rrset_copy_region(
++ origin->rep->rrsets[i], region, 0);
++ if(!res->rep->rrsets[i]) {
++ return NULL;
++ }
++ }
++ return res;
++}
++
+ /** synthesize RRset-only response from cached RRset item */
+ static struct dns_msg*
+ rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
+@@ -1075,7 +1084,6 @@
+ /* ttl must be relative ;i.e. 0..86400 not time(0)+86400.
+ * the env->now is added to message and RRsets in this routine. */
+ /* the leeway is used to invalidate other rrsets earlier */
+-
+ if(is_referral) {
+ /* store rrsets */
+ struct rrset_ref ref;
+@@ -1092,7 +1100,7 @@
+ ((ntohs(ref.key->rk.type)==LDNS_RR_TYPE_NS
+ && !pside) ? qstarttime:*env->now + leeway));
+ }
+- free(rep);
++ reply_info_delete(rep, NULL);
+ return 1;
+ } else {
+ /* store msg, and rrsets */
+--- contrib/unbound/services/cache/dns.h.orig
++++ contrib/unbound/services/cache/dns.h
+@@ -164,6 +164,15 @@
+ struct reply_info* r, struct regional* region, time_t now,
+ int allow_expired, struct regional* scratch);
+
++/**
++ * Deep copy a dns_msg to a region.
++ * @param origin: the dns_msg to copy.
++ * @param region: the region to copy all the data to.
++ * @return the new dns_msg or NULL on malloc error.
++ */
++struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin,
++ struct regional* region);
++
+ /**
+ * Find cached message
+ * @param env: module environment with the DNS cache.
+--- contrib/unbound/services/cache/infra.c.orig
++++ contrib/unbound/services/cache/infra.c
+@@ -67,6 +67,11 @@
+ * in queries per second. */
+ int infra_ip_ratelimit = 0;
+
++/** ratelimit value for client ip addresses,
++ * in queries per second.
++ * For clients with a valid DNS Cookie. */
++int infra_ip_ratelimit_cookie = 0;
++
+ size_t
+ infra_sizefunc(void* k, void* ATTR_UNUSED(d))
+ {
+@@ -1051,9 +1056,50 @@
+ return s;
+ }
+
++/* Returns 1 if the limit has not been exceeded, 0 otherwise. */
++static int
++check_ip_ratelimit(struct sockaddr_storage* addr, socklen_t addrlen,
++ struct sldns_buffer* buffer, int premax, int max, int has_cookie)
++{
++ int limit;
++
++ if(has_cookie) limit = infra_ip_ratelimit_cookie;
++ else limit = infra_ip_ratelimit;
++
++ /* Disabled */
++ if(limit == 0) return 1;
++
++ if(premax <= limit && max > limit) {
++ char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12];
++ addr_to_str(addr, addrlen, client_ip, sizeof(client_ip));
++ qnm[0]=0;
++ if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE &&
++ LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) {
++ (void)sldns_wire2str_rrquestion_buf(
++ sldns_buffer_at(buffer, LDNS_HEADER_SIZE),
++ sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE,
++ qnm, sizeof(qnm));
++ if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n')
++ qnm[strlen(qnm)-1] = 0; /*remove newline*/
++ if(strchr(qnm, '\t'))
++ *strchr(qnm, '\t') = ' ';
++ if(strchr(qnm, '\t'))
++ *strchr(qnm, '\t') = ' ';
++ verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s %s",
++ client_ip, limit,
++ has_cookie?"(cookie)":"", qnm);
++ } else {
++ verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s (no query name)",
++ client_ip, limit,
++ has_cookie?"(cookie)":"");
++ }
++ }
++ return (max <= limit);
++}
++
+ int infra_ip_ratelimit_inc(struct infra_cache* infra,
+ struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
+- int backoff, struct sldns_buffer* buffer)
++ int has_cookie, int backoff, struct sldns_buffer* buffer)
+ {
+ int max;
+ struct lruhash_entry* entry;
+@@ -1070,31 +1116,8 @@
+ (*cur)++;
+ max = infra_rate_max(entry->data, timenow, backoff);
+ lock_rw_unlock(&entry->lock);
+-
+- if(premax <= infra_ip_ratelimit && max > infra_ip_ratelimit) {
+- char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12];
+- addr_to_str(addr, addrlen, client_ip, sizeof(client_ip));
+- qnm[0]=0;
+- if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE &&
+- LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) {
+- (void)sldns_wire2str_rrquestion_buf(
+- sldns_buffer_at(buffer, LDNS_HEADER_SIZE),
+- sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE,
+- qnm, sizeof(qnm));
+- if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n')
+- qnm[strlen(qnm)-1] = 0; /*remove newline*/
+- if(strchr(qnm, '\t'))
+- *strchr(qnm, '\t') = ' ';
+- if(strchr(qnm, '\t'))
+- *strchr(qnm, '\t') = ' ';
+- verbose(VERB_OPS, "ip_ratelimit exceeded %s %d %s",
+- client_ip, infra_ip_ratelimit, qnm);
+- } else {
+- verbose(VERB_OPS, "ip_ratelimit exceeded %s %d (no query name)",
+- client_ip, infra_ip_ratelimit);
+- }
+- }
+- return (max <= infra_ip_ratelimit);
++ return check_ip_ratelimit(addr, addrlen, buffer, premax, max,
++ has_cookie);
+ }
+
+ /* create */
+--- contrib/unbound/services/cache/infra.h.orig
++++ contrib/unbound/services/cache/infra.h
+@@ -153,6 +153,8 @@
+
+ /** ip ratelimit, 0 is off */
+ extern int infra_ip_ratelimit;
++/** ip ratelimit for DNS Cookie clients, 0 is off */
++extern int infra_ip_ratelimit_cookie;
+
+ /**
+ * key for ip_ratelimit lookups, a source IP.
+@@ -419,13 +421,14 @@
+ * @param addr: client address
+ * @param addrlen: client address length
+ * @param timenow: what time it is now.
++ * @param has_cookie: if the request came with a DNS Cookie.
+ * @param backoff: if backoff is enabled.
+ * @param buffer: with query for logging.
+ * @return 1 if it could be incremented. 0 if the increment overshot the
+ * ratelimit and the query should be dropped. */
+ int infra_ip_ratelimit_inc(struct infra_cache* infra,
+ struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
+- int backoff, struct sldns_buffer* buffer);
++ int has_cookie, int backoff, struct sldns_buffer* buffer);
+
+ /**
+ * Get memory used by the infra cache.
+--- contrib/unbound/services/listen_dnsport.c.orig
++++ contrib/unbound/services/listen_dnsport.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -79,9 +79,11 @@
+ #ifdef HAVE_NET_IF_H
+ #include <net/if.h>
+ #endif
+-
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++#include <linux/net_tstamp.h>
++#endif
+ /** number of queued TCP connections for listen() */
+-#define TCP_BACKLOG 256
++#define TCP_BACKLOG 256
+
+ #ifndef THREADS_DISABLED
+ /** lock on the counter of stream buffer memory */
+@@ -187,7 +189,7 @@
+ log_err("systemd sd_listen_fds(): %s", strerror(-r));
+ return -1;
+ }
+-
++
+ for(i = 0; i < r; i++) {
+ if(sd_is_socket(SD_LISTEN_FDS_START + i, family, socktype, listen)) {
+ s = SD_LISTEN_FDS_START + i;
+@@ -253,7 +255,7 @@
+ return -1;
+ }
+ #else
+- if(WSAGetLastError() == WSAEAFNOSUPPORT ||
++ if(WSAGetLastError() == WSAEAFNOSUPPORT ||
+ WSAGetLastError() == WSAEPROTONOSUPPORT) {
+ *noproto = 1;
+ return -1;
+@@ -270,7 +272,7 @@
+ #endif
+ if(listen) {
+ #ifdef SO_REUSEADDR
+- if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
++ if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
+ (socklen_t)sizeof(on)) < 0) {
+ log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s",
+ sock_strerror(errno));
+@@ -368,9 +370,9 @@
+ socklen_t slen = (socklen_t)sizeof(got);
+ # ifdef SO_RCVBUFFORCE
+ /* Linux specific: try to use root permission to override
+- * system limits on rcvbuf. The limit is stored in
++ * system limits on rcvbuf. The limit is stored in
+ * /proc/sys/net/core/rmem_max or sysctl net.core.rmem_max */
+- if(setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&rcv,
++ if(setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&rcv,
+ (socklen_t)sizeof(rcv)) < 0) {
+ if(errno != EPERM) {
+ log_err("setsockopt(..., SO_RCVBUFFORCE, "
+@@ -381,7 +383,7 @@
+ return -1;
+ }
+ # endif /* SO_RCVBUFFORCE */
+- if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&rcv,
++ if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&rcv,
+ (socklen_t)sizeof(rcv)) < 0) {
+ log_err("setsockopt(..., SO_RCVBUF, "
+ "...) failed: %s", sock_strerror(errno));
+@@ -392,7 +394,7 @@
+ }
+ /* check if we got the right thing or if system
+ * reduced to some system max. Warn if so */
+- if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&got,
++ if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&got,
+ &slen) >= 0 && got < rcv/2) {
+ log_warn("so-rcvbuf %u was not granted. "
+ "Got %u. To fix: start with "
+@@ -413,9 +415,9 @@
+ socklen_t slen = (socklen_t)sizeof(got);
+ # ifdef SO_SNDBUFFORCE
+ /* Linux specific: try to use root permission to override
+- * system limits on sndbuf. The limit is stored in
++ * system limits on sndbuf. The limit is stored in
+ * /proc/sys/net/core/wmem_max or sysctl net.core.wmem_max */
+- if(setsockopt(s, SOL_SOCKET, SO_SNDBUFFORCE, (void*)&snd,
++ if(setsockopt(s, SOL_SOCKET, SO_SNDBUFFORCE, (void*)&snd,
+ (socklen_t)sizeof(snd)) < 0) {
+ if(errno != EPERM) {
+ log_err("setsockopt(..., SO_SNDBUFFORCE, "
+@@ -426,7 +428,7 @@
+ return -1;
+ }
+ # endif /* SO_SNDBUFFORCE */
+- if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&snd,
++ if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&snd,
+ (socklen_t)sizeof(snd)) < 0) {
+ log_err("setsockopt(..., SO_SNDBUF, "
+ "...) failed: %s", sock_strerror(errno));
+@@ -437,7 +439,7 @@
+ }
+ /* check if we got the right thing or if system
+ * reduced to some system max. Warn if so */
+- if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&got,
++ if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&got,
+ &slen) >= 0 && got < snd/2) {
+ log_warn("so-sndbuf %u was not granted. "
+ "Got %u. To fix: start with "
+@@ -469,7 +471,7 @@
+ # endif
+ ) {
+ int val=(v6only==2)?0:1;
+- if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
++ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void*)&val, (socklen_t)sizeof(val)) < 0) {
+ log_err("setsockopt(..., IPV6_V6ONLY"
+ ", ...) failed: %s", sock_strerror(errno));
+@@ -576,7 +578,7 @@
+ int action;
+ # if defined(IP_PMTUDISC_OMIT)
+ action = IP_PMTUDISC_OMIT;
+- if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER,
++ if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER,
+ &action, (socklen_t)sizeof(action)) < 0) {
+
+ if (errno != EINVAL) {
+@@ -609,7 +611,7 @@
+ /* the IP_DONTFRAG option if defined in the 11.0 OSX headers,
+ * but does not work on that version, so we exclude it */
+ int off = 0;
+- if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG,
++ if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG,
+ &off, (socklen_t)sizeof(off)) < 0) {
+ log_err("setsockopt(..., IP_DONTFRAG, ...) failed: %s",
+ strerror(errno));
+@@ -647,7 +649,7 @@
+ if(WSAGetLastError() != WSAEADDRINUSE &&
+ WSAGetLastError() != WSAEADDRNOTAVAIL &&
+ !(WSAGetLastError() == WSAEACCES && verbosity < 4 && !listen)) {
+- log_err_addr("can't bind socket",
++ log_err_addr("can't bind socket",
+ wsa_strerror(WSAGetLastError()),
+ (struct sockaddr_storage*)addr, addrlen);
+ }
+@@ -749,7 +751,7 @@
+ }
+ #endif
+ #ifdef SO_REUSEADDR
+- if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
++ if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
+ (socklen_t)sizeof(on)) < 0) {
+ log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s",
+ sock_strerror(errno));
+@@ -793,7 +795,7 @@
+ && !got_fd_from_systemd
+ # endif
+ ) {
+- if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
++ if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void*)&on, (socklen_t)sizeof(on)) < 0) {
+ log_err("setsockopt(..., IPV6_V6ONLY, ...) failed: %s",
+ sock_strerror(errno));
+@@ -845,7 +847,7 @@
+ addr->ai_addrlen);
+ }
+ #else
+- log_err_addr("can't bind socket",
++ log_err_addr("can't bind socket",
+ wsa_strerror(WSAGetLastError()),
+ (struct sockaddr_storage*)addr->ai_addr,
+ addr->ai_addrlen);
+@@ -873,7 +875,7 @@
+ /* 5 is recommended on linux */
+ qlen = 5;
+ #endif
+- if ((setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &qlen,
++ if ((setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &qlen,
+ sizeof(qlen))) == -1 ) {
+ #ifdef ENOPROTOOPT
+ /* squelch ENOPROTOOPT: freebsd server mode with kernel support
+@@ -999,7 +1001,7 @@
+ * Create socket from getaddrinfo results
+ */
+ static int
+-make_sock(int stype, const char* ifname, const char* port,
++make_sock(int stype, const char* ifname, const char* port,
+ struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd,
+ int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind,
+ int use_systemd, int dscp, struct unbound_socket* ub_sock)
+@@ -1015,10 +1017,10 @@
+ return -1;
+ }
+ #endif
+- log_err("node %s:%s getaddrinfo: %s %s",
++ log_err("node %s:%s getaddrinfo: %s %s",
+ ifname?ifname:"default", port, gai_strerror(r),
+ #ifdef EAI_SYSTEM
+- r==EAI_SYSTEM?(char*)strerror(errno):""
++ (r==EAI_SYSTEM?(char*)strerror(errno):"")
+ #else
+ ""
+ #endif
+@@ -1055,7 +1057,7 @@
+
+ /** make socket and first see if ifname contains port override info */
+ static int
+-make_sock_port(int stype, const char* ifname, const char* port,
++make_sock_port(int stype, const char* ifname, const char* port,
+ struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd,
+ int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind,
+ int use_systemd, int dscp, struct unbound_socket* ub_sock)
+@@ -1114,9 +1116,28 @@
+ return 1;
+ }
+
++/** set fd to receive software timestamps */
++static int
++set_recvtimestamp(int s)
++{
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++ int opt = SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE;
++ if (setsockopt(s, SOL_SOCKET, SO_TIMESTAMPNS, (void*)&opt, (socklen_t)sizeof(opt)) < 0) {
++ log_err("setsockopt(..., SO_TIMESTAMPNS, ...) failed: %s",
++ strerror(errno));
++ return 0;
++ }
++ return 1;
++#else
++ log_err("packets timestamping is not supported on this platform");
++ (void)s;
++ return 0;
++#endif
++}
++
+ /** set fd to receive source address packet info */
+ static int
+-set_recvpktinfo(int s, int family)
++set_recvpktinfo(int s, int family)
+ {
+ #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) || (defined(IP_RECVDSTADDR) && defined(IP_SENDSRCADDR)) || defined(IP_PKTINFO)
+ int on = 1;
+@@ -1214,6 +1235,9 @@
+ * @param use_systemd: if true, fetch sockets from systemd.
+ * @param dnscrypt_port: dnscrypt service port number
+ * @param dscp: DSCP to use.
++ * @param sock_queue_timeout: the sock_queue_timeout from config. Seconds to
++ * wait to discard if UDP packets have waited for long in the socket
++ * buffer.
+ * @return: returns false on error.
+ */
+ static int
+@@ -1223,7 +1247,8 @@
+ struct config_strlist* tls_additional_port, int https_port,
+ struct config_strlist* proxy_protocol_port,
+ int* reuseport, int transparent, int tcp_mss, int freebind,
+- int http2_nodelay, int use_systemd, int dnscrypt_port, int dscp)
++ int http2_nodelay, int use_systemd, int dnscrypt_port, int dscp,
++ int sock_queue_timeout)
+ {
+ int s, noip6=0;
+ int is_https = if_is_https(ifname, port, https_port);
+@@ -1252,7 +1277,8 @@
+ if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1,
+ &noip6, rcv, snd, reuseport, transparent,
+ tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock)) == -1) {
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ if(noip6) {
+ log_warn("IPv6 protocol not available");
+@@ -1263,15 +1289,20 @@
+ /* getting source addr packet info is highly non-portable */
+ if(!set_recvpktinfo(s, hints->ai_family)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
++ if (sock_queue_timeout && !set_recvtimestamp(s)) {
++ log_warn("socket timestamping is not available");
++ }
+ if(!port_insert(list, s, is_dnscrypt
+ ?listen_type_udpancil_dnscrypt:listen_type_udpancil,
+ is_pp2, ub_sock)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
+@@ -1283,7 +1314,8 @@
+ if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1,
+ &noip6, rcv, snd, reuseport, transparent,
+ tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock)) == -1) {
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ if(noip6) {
+ log_warn("IPv6 protocol not available");
+@@ -1291,11 +1323,17 @@
+ }
+ return 0;
+ }
++ if (sock_queue_timeout && !set_recvtimestamp(s)) {
++ log_warn("socket timestamping is not available");
++ }
+ if(!port_insert(list, s, is_dnscrypt
+- ?listen_type_udp_dnscrypt:listen_type_udp,
++ ?listen_type_udp_dnscrypt :
++ (sock_queue_timeout ?
++ listen_type_udpancil:listen_type_udp),
+ is_pp2, ub_sock)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
+@@ -1318,7 +1356,8 @@
+ if((s = make_sock_port(SOCK_STREAM, ifname, port, hints, 1,
+ &noip6, 0, 0, reuseport, transparent, tcp_mss, nodelay,
+ freebind, use_systemd, dscp, ub_sock)) == -1) {
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ if(noip6) {
+ /*log_warn("IPv6 protocol not available");*/
+@@ -1330,7 +1369,8 @@
+ verbose(VERB_ALGO, "setup TCP for SSL service");
+ if(!port_insert(list, s, port_type, is_pp2, ub_sock)) {
+ sock_close(s);
+- freeaddrinfo(ub_sock->addr);
++ if(ub_sock->addr)
++ freeaddrinfo(ub_sock->addr);
+ free(ub_sock);
+ return 0;
+ }
+@@ -1338,7 +1378,7 @@
+ return 1;
+ }
+
+-/**
++/**
+ * Add items to commpoint list in front.
+ * @param c: commpoint to add.
+ * @param front: listen struct.
+@@ -1389,7 +1429,7 @@
+ }
+ }
+
+-struct listen_dnsport*
++struct listen_dnsport*
+ listen_create(struct comm_base* base, struct listen_port* ports,
+ size_t bufsize, int tcp_accept_count, int tcp_idle_timeout,
+ int harden_large_queries, uint32_t http_max_streams,
+@@ -1460,9 +1500,13 @@
+ }
+ } else if(ports->ftype == listen_type_udpancil ||
+ ports->ftype == listen_type_udpancil_dnscrypt) {
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
+ cp = comm_point_create_udp_ancil(base, ports->fd,
+ front->udp_buff, ports->pp2_enabled, cb,
+ cb_arg, ports->socket);
++#else
++ log_warn("This system does not support UDP ancilliary data.");
++#endif
+ }
+ if(!cp) {
+ log_err("can't create commpoint");
+@@ -1525,10 +1569,10 @@
+ }
+ }
+
+-void
++void
+ listen_delete(struct listen_dnsport* front)
+ {
+- if(!front)
++ if(!front)
+ return;
+ listen_list_delete(front->cps);
+ #ifdef USE_DNSCRYPT
+@@ -1802,7 +1846,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1819,7 +1863,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1838,7 +1882,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1854,7 +1898,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1872,7 +1916,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1888,7 +1932,7 @@
+ reuseport, cfg->ip_transparent,
+ cfg->tcp_mss, cfg->ip_freebind,
+ cfg->http_nodelay, cfg->use_systemd,
+- cfg->dnscrypt_port, cfg->ip_dscp)) {
++ cfg->dnscrypt_port, cfg->ip_dscp, cfg->sock_queue_timeout)) {
+ listening_ports_free(list);
+ return NULL;
+ }
+@@ -1908,7 +1952,8 @@
+ }
+ /* rc_ports don't have ub_socket */
+ if(list->socket) {
+- freeaddrinfo(list->socket->addr);
++ if(list->socket->addr)
++ freeaddrinfo(list->socket->addr);
+ free(list->socket);
+ }
+ free(list);
+@@ -1919,8 +1964,8 @@
+ size_t listen_get_mem(struct listen_dnsport* listen)
+ {
+ struct listen_list* p;
+- size_t s = sizeof(*listen) + sizeof(*listen->base) +
+- sizeof(*listen->udp_buff) +
++ size_t s = sizeof(*listen) + sizeof(*listen->base) +
++ sizeof(*listen->udp_buff) +
+ sldns_buffer_capacity(listen->udp_buff);
+ #ifdef USE_DNSCRYPT
+ s += sizeof(*listen->dnscrypt_udp_buff);
+@@ -2001,7 +2046,7 @@
+ }
+ req->open_req_list = NULL;
+ req->num_open_req = 0;
+-
++
+ /* free pending writable result packets */
+ item = req->done_req_list;
+ while(item) {
+@@ -2060,7 +2105,7 @@
+ wr = 1;
+ if(!req->read_is_closed)
+ rd = 1;
+-
++
+ if(wr) {
+ req->cp->tcp_is_reading = 0;
+ comm_point_stop_listening(req->cp);
+@@ -2196,7 +2241,7 @@
+ }
+ req->in_worker_handle = 0;
+ /* it should be waiting in the mesh for recursion.
+- * If mesh failed to add a new entry and called commpoint_drop_reply.
++ * If mesh failed to add a new entry and called commpoint_drop_reply.
+ * Then the mesh state has been cleared. */
+ if(req->is_drop) {
+ /* the reply has been dropped, stream has been closed. */
+@@ -2256,7 +2301,7 @@
+ last = req->done_req_list;
+ while(last && last->next)
+ last = last->next;
+-
++
+ /* create new element */
+ item = (struct tcp_req_done_item*)malloc(sizeof(*item));
+ if(!item) {
+@@ -2615,7 +2660,7 @@
+ "buffer already assigned to stream");
+ return -1;
+ }
+-
++
+ /* the c->buffer might be used by mesh_send_reply and no be cleard
+ * need to be cleared before use */
+ sldns_buffer_clear(h2_session->c->buffer);
+--- contrib/unbound/services/localzone.c.orig
++++ contrib/unbound/services/localzone.c
+@@ -1308,6 +1308,7 @@
+ else rep.ns_numrrsets = 1;
+ rep.rrset_count = 1;
+ rep.rrsets = &rrset;
++ rep.reason_bogus = LDNS_EDE_NONE;
+ udpsize = edns->udp_size;
+ edns->edns_version = EDNS_ADVERTISED_VERSION;
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+@@ -1603,7 +1604,7 @@
+ struct local_data key;
+ struct local_data* ld = NULL;
+ struct local_rrset* lr = NULL;
+- if(z->type == local_zone_always_transparent)
++ if(z->type == local_zone_always_transparent || z->type == local_zone_block_a)
+ return 1;
+ if(z->type != local_zone_transparent
+ && z->type != local_zone_typetransparent
+@@ -1679,6 +1680,16 @@
+ } else if(lz_type == local_zone_typetransparent
+ || lz_type == local_zone_always_transparent) {
+ /* no NODATA or NXDOMAINS for this zone type */
++ return 0;
++ } else if(lz_type == local_zone_block_a) {
++ /* Return NODATA for all A queries */
++ if(qinfo->qtype == LDNS_RR_TYPE_A) {
++ local_error_encode(qinfo, env, edns, repinfo, buf, temp,
++ LDNS_RCODE_NOERROR, (LDNS_RCODE_NOERROR|BIT_AA),
++ LDNS_EDE_NONE, NULL);
++ return 1;
++ }
++
+ return 0;
+ } else if(lz_type == local_zone_always_null) {
+ /* 0.0.0.0 or ::0 or noerror/nodata for this zone type,
+@@ -1846,7 +1857,8 @@
+ if(z && (lzt == local_zone_transparent ||
+ lzt == local_zone_typetransparent ||
+ lzt == local_zone_inform ||
+- lzt == local_zone_always_transparent) &&
++ lzt == local_zone_always_transparent ||
++ lzt == local_zone_block_a) &&
+ local_zone_does_not_cover(z, qinfo, labs)) {
+ lock_rw_unlock(&z->lock);
+ z = NULL;
+@@ -1894,6 +1906,7 @@
+
+ if(lzt != local_zone_always_refuse
+ && lzt != local_zone_always_transparent
++ && lzt != local_zone_block_a
+ && lzt != local_zone_always_nxdomain
+ && lzt != local_zone_always_nodata
+ && lzt != local_zone_always_deny
+@@ -1924,6 +1937,7 @@
+ case local_zone_inform_deny: return "inform_deny";
+ case local_zone_inform_redirect: return "inform_redirect";
+ case local_zone_always_transparent: return "always_transparent";
++ case local_zone_block_a: return "block_a";
+ case local_zone_always_refuse: return "always_refuse";
+ case local_zone_always_nxdomain: return "always_nxdomain";
+ case local_zone_always_nodata: return "always_nodata";
+@@ -1958,6 +1972,8 @@
+ *t = local_zone_inform_redirect;
+ else if(strcmp(type, "always_transparent") == 0)
+ *t = local_zone_always_transparent;
++ else if(strcmp(type, "block_a") == 0)
++ *t = local_zone_block_a;
+ else if(strcmp(type, "always_refuse") == 0)
+ *t = local_zone_always_refuse;
+ else if(strcmp(type, "always_nxdomain") == 0)
+--- contrib/unbound/services/localzone.h.orig
++++ contrib/unbound/services/localzone.h
+@@ -88,6 +88,8 @@
+ local_zone_inform_redirect,
+ /** resolve normally, even when there is local data */
+ local_zone_always_transparent,
++ /** resolve normally, even when there is local data but return NODATA for A queries */
++ local_zone_block_a,
+ /** answer with error, even when there is local data */
+ local_zone_always_refuse,
+ /** answer with nxdomain, even when there is local data */
+--- contrib/unbound/services/mesh.c.orig
++++ contrib/unbound/services/mesh.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -63,82 +63,13 @@
+ #include "util/data/dname.h"
+ #include "respip/respip.h"
+ #include "services/listen_dnsport.h"
++#include "util/timeval_func.h"
+
+ #ifdef CLIENT_SUBNET
+ #include "edns-subnet/subnetmod.h"
+ #include "edns-subnet/edns-subnet.h"
+ #endif
+
+-/** subtract timers and the values do not overflow or become negative */
+-static void
+-timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start)
+-{
+-#ifndef S_SPLINT_S
+- time_t end_usec = end->tv_usec;
+- d->tv_sec = end->tv_sec - start->tv_sec;
+- if(end_usec < start->tv_usec) {
+- end_usec += 1000000;
+- d->tv_sec--;
+- }
+- d->tv_usec = end_usec - start->tv_usec;
+-#endif
+-}
+-
+-/** add timers and the values do not overflow or become negative */
+-static void
+-timeval_add(struct timeval* d, const struct timeval* add)
+-{
+-#ifndef S_SPLINT_S
+- d->tv_sec += add->tv_sec;
+- d->tv_usec += add->tv_usec;
+- if(d->tv_usec >= 1000000 ) {
+- d->tv_usec -= 1000000;
+- d->tv_sec++;
+- }
+-#endif
+-}
+-
+-/** divide sum of timers to get average */
+-static void
+-timeval_divide(struct timeval* avg, const struct timeval* sum, size_t d)
+-{
+-#ifndef S_SPLINT_S
+- size_t leftover;
+- if(d <= 0) {
+- avg->tv_sec = 0;
+- avg->tv_usec = 0;
+- return;
+- }
+- avg->tv_sec = sum->tv_sec / d;
+- avg->tv_usec = sum->tv_usec / d;
+- /* handle fraction from seconds divide */
+- leftover = sum->tv_sec - avg->tv_sec*d;
+- if(leftover <= 0)
+- leftover = 0;
+- avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
+- if(avg->tv_sec < 0)
+- avg->tv_sec = 0;
+- if(avg->tv_usec < 0)
+- avg->tv_usec = 0;
+-#endif
+-}
+-
+-/** histogram compare of time values */
+-static int
+-timeval_smaller(const struct timeval* x, const struct timeval* y)
+-{
+-#ifndef S_SPLINT_S
+- if(x->tv_sec < y->tv_sec)
+- return 1;
+- else if(x->tv_sec == y->tv_sec) {
+- if(x->tv_usec <= y->tv_usec)
+- return 1;
+- else return 0;
+- }
+- else return 0;
+-#endif
+-}
+-
+ /**
+ * Compare two response-ip client info entries for the purpose of mesh state
+ * compare. It returns 0 if ci_a and ci_b are considered equal; otherwise
+@@ -249,7 +180,7 @@
+ return mesh_state_compare(a->s, b->s);
+ }
+
+-struct mesh_area*
++struct mesh_area*
+ mesh_create(struct module_stack* stack, struct module_env* env)
+ {
+ struct mesh_area* mesh = calloc(1, sizeof(struct mesh_area));
+@@ -275,6 +206,7 @@
+ mesh->stats_jostled = 0;
+ mesh->stats_dropped = 0;
+ mesh->ans_expired = 0;
++ mesh->ans_cachedb = 0;
+ mesh->max_reply_states = env->cfg->num_queries_per_thread;
+ mesh->max_forever_states = (mesh->max_reply_states+1)/2;
+ #ifndef S_SPLINT_S
+@@ -298,7 +230,7 @@
+ * traversal and rbtree rebalancing do not work together */
+ }
+
+-void
++void
+ mesh_delete(struct mesh_area* mesh)
+ {
+ if(!mesh)
+@@ -341,7 +273,7 @@
+ if(m && m->reply_list && m->list_select == mesh_jostle_list) {
+ /* how old is it? */
+ struct timeval age;
+- timeval_subtract(&age, mesh->env->now_tv,
++ timeval_subtract(&age, mesh->env->now_tv,
+ &m->reply_list->start_time);
+ if(timeval_smaller(&mesh->jostle_max, &age)) {
+ /* its a goner */
+@@ -517,6 +449,8 @@
+ comm_point_send_reply(rep);
+ return;
+ }
++ /* set detached (it is now) */
++ mesh->num_detached_states++;
+ if(unique)
+ mesh_state_make_unique(s);
+ s->s.rpz_passthru = rpz_passthru;
+@@ -525,13 +459,14 @@
+ s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in,
+ s->s.region);
+ if(!s->s.edns_opts_front_in) {
+- log_err("mesh_state_create: out of memory; SERVFAIL");
++ log_err("edns_opt_copy_region: out of memory; SERVFAIL");
+ if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL,
+ NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch, mesh->env->now_tv))
+ edns->opt_list_inplace_cb_out = NULL;
+ error_encode(r_buffer, LDNS_RCODE_SERVFAIL,
+ qinfo, qid, qflags, edns);
+ comm_point_send_reply(rep);
++ mesh_state_delete(&s->s);
+ return;
+ }
+ }
+@@ -543,8 +478,6 @@
+ #endif
+ rbtree_insert(&mesh->all, &s->node);
+ log_assert(n != NULL);
+- /* set detached (it is now) */
+- mesh->num_detached_states++;
+ added = 1;
+ }
+ if(!s->reply_list && !s->cb_list) {
+@@ -585,11 +518,11 @@
+ /* move to either the forever or the jostle_list */
+ if(mesh->num_forever_states < mesh->max_forever_states) {
+ mesh->num_forever_states ++;
+- mesh_list_insert(s, &mesh->forever_first,
++ mesh_list_insert(s, &mesh->forever_first,
+ &mesh->forever_last);
+ s->list_select = mesh_forever_list;
+ } else {
+- mesh_list_insert(s, &mesh->jostle_first,
++ mesh_list_insert(s, &mesh->jostle_first,
+ &mesh->jostle_last);
+ s->list_select = mesh_jostle_list;
+ }
+@@ -610,9 +543,9 @@
+ return;
+ }
+
+-int
++int
+ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo,
+- uint16_t qflags, struct edns_data* edns, sldns_buffer* buf,
++ uint16_t qflags, struct edns_data* edns, sldns_buffer* buf,
+ uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru)
+ {
+ struct mesh_state* s = NULL;
+@@ -637,6 +570,8 @@
+ if(!s) {
+ return 0;
+ }
++ /* set detached (it is now) */
++ mesh->num_detached_states++;
+ if(unique)
+ mesh_state_make_unique(s);
+ s->s.rpz_passthru = rpz_passthru;
+@@ -644,6 +579,7 @@
+ s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in,
+ s->s.region);
+ if(!s->s.edns_opts_front_in) {
++ mesh_state_delete(&s->s);
+ return 0;
+ }
+ }
+@@ -654,8 +590,6 @@
+ #endif
+ rbtree_insert(&mesh->all, &s->node);
+ log_assert(n != NULL);
+- /* set detached (it is now) */
+- mesh->num_detached_states++;
+ added = 1;
+ }
+ if(!s->reply_list && !s->cb_list) {
+@@ -672,6 +606,8 @@
+ }
+ /* add serve expired timer if not already there */
+ if(timeout && !mesh_serve_expired_init(s, timeout)) {
++ if(added)
++ mesh_state_delete(&s->s);
+ return 0;
+ }
+ /* update statistics */
+@@ -773,7 +709,7 @@
+ * attached its own ECS data. */
+ static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh,
+ struct query_info* qinfo, uint16_t qflags, time_t leeway, int run,
+- int rpz_passthru, struct comm_reply* rep, struct edns_option* edns_list)
++ int rpz_passthru, struct sockaddr_storage* addr, struct edns_option* edns_list)
+ {
+ struct mesh_state* s = NULL;
+ struct edns_option* opt = NULL;
+@@ -803,20 +739,10 @@
+ return;
+ }
+ } else {
+- /* Fake the ECS data from the client's IP */
+- struct ecs_data ecs;
+- memset(&ecs, 0, sizeof(ecs));
+- subnet_option_from_ss(&rep->client_addr, &ecs, mesh->env->cfg);
+- if(ecs.subnet_validdata == 0) {
+- log_err("prefetch_subnet subnet_option_from_ss: invalid data");
+- return;
+- }
+- subnet_ecs_opt_list_append(&ecs, &s->s.edns_opts_front_in,
+- &s->s, s->s.region);
+- if(!s->s.edns_opts_front_in) {
+- log_err("prefetch_subnet subnet_ecs_opt_list_append: out of memory");
+- return;
+- }
++ /* Store the client's address. Later in the subnet module,
++ * it is decided whether to include an ECS option or not.
++ */
++ s->s.client_addr = *addr;
+ }
+ #ifdef UNBOUND_DEBUG
+ n =
+@@ -863,14 +789,14 @@
+
+ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
+ uint16_t qflags, time_t leeway, int rpz_passthru,
+- struct comm_reply* rep, struct edns_option* opt_list)
++ struct sockaddr_storage* addr, struct edns_option* opt_list)
+ {
++ (void)addr;
+ (void)opt_list;
+- (void)rep;
+ #ifdef CLIENT_SUBNET
+- if(rep)
++ if(addr)
+ mesh_schedule_prefetch_subnet(mesh, qinfo, qflags, leeway, 1,
+- rpz_passthru, rep, opt_list);
++ rpz_passthru, addr, opt_list);
+ else
+ #endif
+ mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1,
+@@ -900,7 +826,7 @@
+ int i;
+ if(!region)
+ return NULL;
+- mstate = (struct mesh_state*)regional_alloc(region,
++ mstate = (struct mesh_state*)regional_alloc(region,
+ sizeof(struct mesh_state));
+ if(!mstate) {
+ alloc_reg_release(env->alloc, region);
+@@ -970,19 +896,13 @@
+ return mstate;
+ }
+
+-int
+-mesh_state_is_unique(struct mesh_state* mstate)
+-{
+- return mstate->unique != NULL;
+-}
+-
+ void
+ mesh_state_make_unique(struct mesh_state* mstate)
+ {
+ mstate->unique = mstate;
+ }
+
+-void
++void
+ mesh_state_cleanup(struct mesh_state* mstate)
+ {
+ struct mesh_area* mesh;
+@@ -1028,7 +948,7 @@
+ alloc_reg_release(mstate->s.env->alloc, mstate->s.region);
+ }
+
+-void
++void
+ mesh_state_delete(struct module_qstate* qstate)
+ {
+ struct mesh_area* mesh;
+@@ -1041,10 +961,10 @@
+ mesh_detach_subs(&mstate->s);
+ if(mstate->list_select == mesh_forever_list) {
+ mesh->num_forever_states --;
+- mesh_list_remove(mstate, &mesh->forever_first,
++ mesh_list_remove(mstate, &mesh->forever_first,
+ &mesh->forever_last);
+ } else if(mstate->list_select == mesh_jostle_list) {
+- mesh_list_remove(mstate, &mesh->jostle_first,
++ mesh_list_remove(mstate, &mesh->jostle_first,
+ &mesh->jostle_last);
+ }
+ if(!mstate->reply_list && !mstate->cb_list
+@@ -1116,7 +1036,7 @@
+ if(!ref->s->reply_list && !ref->s->cb_list
+ && ref->s->super_set.count == 0) {
+ mesh->num_detached_states++;
+- log_assert(mesh->num_detached_states +
++ log_assert(mesh->num_detached_states +
+ mesh->num_reply_states <= mesh->all.count);
+ }
+ }
+@@ -1181,7 +1101,7 @@
+ if(!mesh_state_attachment(qstate->mesh_info, sub))
+ return 0;
+ /* if it was a duplicate attachment, the count was not zero before */
+- if(!sub->reply_list && !sub->cb_list && was_detached &&
++ if(!sub->reply_list && !sub->cb_list && was_detached &&
+ sub->super_set.count == 1) {
+ /* it used to be detached, before this one got added */
+ log_assert(mesh->num_detached_states > 0);
+@@ -1251,7 +1171,7 @@
+ else secure = 0;
+ if(!rep && rcode == LDNS_RCODE_NOERROR)
+ rcode = LDNS_RCODE_SERVFAIL;
+- if(!rcode && (rep->security == sec_status_bogus ||
++ if(!rcode && rep && (rep->security == sec_status_bogus ||
+ rep->security == sec_status_secure_sentinel_fail)) {
+ if(!(reason = errinf_to_str_bogus(&m->s)))
+ rcode = LDNS_RCODE_SERVFAIL;
+@@ -1277,13 +1197,15 @@
+ r->edns.udp_size = EDNS_ADVERTISED_SIZE;
+ r->edns.ext_rcode = 0;
+ r->edns.bits &= EDNS_DO;
++ if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO))
++ r->edns.edns_present = 0;
+
+ if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep,
+ LDNS_RCODE_NOERROR, &r->edns, NULL, m->s.region, start_time) ||
+- !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
+- r->qflags, r->buf, 0, 1,
+- m->s.env->scratch, udp_size, &r->edns,
+- (int)(r->edns.bits & EDNS_DO), secure))
++ !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
++ r->qflags, r->buf, 0, 1,
++ m->s.env->scratch, udp_size, &r->edns,
++ (int)(r->edns.bits & EDNS_DO), secure))
+ {
+ fptr_ok(fptr_whitelist_mesh_cb(r->cb));
+ (*r->cb)(r->cb_arg, LDNS_RCODE_SERVFAIL, r->buf,
+@@ -1291,7 +1213,8 @@
+ } else {
+ fptr_ok(fptr_whitelist_mesh_cb(r->cb));
+ (*r->cb)(r->cb_arg, LDNS_RCODE_NOERROR, r->buf,
+- rep->security, reason, was_ratelimited);
++ (rep?rep->security:sec_status_unchecked),
++ reason, was_ratelimited);
+ }
+ }
+ free(reason);
+@@ -1303,18 +1226,45 @@
+ mesh_is_rpz_respip_tcponly_action(struct mesh_state const* m)
+ {
+ struct respip_action_info const* respip_info = m->s.respip_action_info;
+- return respip_info == NULL
++ return (respip_info == NULL
+ ? 0
+ : (respip_info->rpz_used
+ && !respip_info->rpz_disabled
+- && respip_info->action == respip_truncate);
++ && respip_info->action == respip_truncate))
++ || m->s.tcp_required;
+ }
+
+ static inline int
+-mesh_is_udp(struct mesh_reply const* r) {
++mesh_is_udp(struct mesh_reply const* r)
++{
+ return r->query_reply.c->type == comm_udp;
+ }
+
++static inline void
++mesh_find_and_attach_ede_and_reason(struct mesh_state* m,
++ struct reply_info* rep, struct mesh_reply* r)
++{
++ /* OLD note:
++ * During validation the EDE code can be received via two
++ * code paths. One code path fills the reply_info EDE, and
++ * the other fills it in the errinf_strlist. These paths
++ * intersect at some points, but where is opaque due to
++ * the complexity of the validator. At the time of writing
++ * we make the choice to prefer the EDE from errinf_strlist
++ * but a compelling reason to do otherwise is just as valid
++ * NEW note:
++ * The compelling reason is that with caching support, the value
++ * in the reply_info is cached.
++ * The reason members of the reply_info struct should be
++ * updated as they are already cached. No reason to
++ * try and find the EDE information in errinf anymore.
++ */
++ if(rep->reason_bogus != LDNS_EDE_NONE) {
++ edns_opt_list_append_ede(&r->edns.opt_list_out,
++ m->s.region, rep->reason_bogus, rep->reason_bogus_str);
++ }
++}
++
+ /**
+ * Send reply to mesh reply entry
+ * @param m: mesh state to send it for.
+@@ -1346,7 +1296,7 @@
+
+ /* examine security status */
+ if(m->s.env->need_to_validate && (!(r->qflags&BIT_CD) ||
+- m->s.env->cfg->ignore_cd) && rep &&
++ m->s.env->cfg->ignore_cd) && rep &&
+ (rep->security <= sec_status_bogus ||
+ rep->security == sec_status_secure_sentinel_fail)) {
+ rcode = LDNS_RCODE_SERVFAIL;
+@@ -1401,40 +1351,17 @@
+ if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s,
+ rep, rcode, &r->edns, &r->query_reply, m->s.region, &r->start_time))
+ r->edns.opt_list_inplace_cb_out = NULL;
+- } else {
++ } else {
+ if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode,
+ &r->edns, &r->query_reply, m->s.region, &r->start_time))
+ r->edns.opt_list_inplace_cb_out = NULL;
+ }
+- /* Send along EDE BOGUS EDNS0 option when answer is bogus */
+- if(m->s.env->cfg->ede && rcode == LDNS_RCODE_SERVFAIL &&
+- m->s.env->need_to_validate && (!(r->qflags&BIT_CD) ||
+- m->s.env->cfg->ignore_cd) && rep &&
+- (rep->security <= sec_status_bogus ||
+- rep->security == sec_status_secure_sentinel_fail)) {
+- char *reason = m->s.env->cfg->val_log_level >= 2
+- ? errinf_to_str_bogus(&m->s) : NULL;
+-
+- /* During validation the EDE code can be received via two
+- * code paths. One code path fills the reply_info EDE, and
+- * the other fills it in the errinf_strlist. These paths
+- * intersect at some points, but where is opaque due to
+- * the complexity of the validator. At the time of writing
+- * we make the choice to prefer the EDE from errinf_strlist
+- * but a compelling reason to do otherwise is just as valid
+- */
+- sldns_ede_code reason_bogus = errinf_to_reason_bogus(&m->s);
+- if ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS &&
+- rep->reason_bogus != LDNS_EDE_NONE) ||
+- reason_bogus == LDNS_EDE_NONE) {
+- reason_bogus = rep->reason_bogus;
+- }
+-
+- if(reason_bogus != LDNS_EDE_NONE) {
+- edns_opt_list_append_ede(&r->edns.opt_list_out,
+- m->s.region, reason_bogus, reason);
+- }
+- free(reason);
++ /* Send along EDE EDNS0 option when SERVFAILing; usually
++ * DNSSEC validation failures */
++ /* Since we are SERVFAILing here, CD bit and rep->security
++ * is already handled. */
++ if(m->s.env->cfg->ede && rep) {
++ mesh_find_and_attach_ede_and_reason(m, rep, r);
+ }
+ error_encode(r_buffer, rcode, &m->s.qinfo, r->qid,
+ r->qflags, &r->edns);
+@@ -1447,14 +1374,26 @@
+ r->edns.udp_size = EDNS_ADVERTISED_SIZE;
+ r->edns.ext_rcode = 0;
+ r->edns.bits &= EDNS_DO;
++ if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO))
++ r->edns.edns_present = 0;
+ m->s.qinfo.qname = r->qname;
+ m->s.qinfo.local_alias = r->local_alias;
++
++ /* Attach EDE without SERVFAIL if the validation failed.
++ * Need to explicitly check for rep->security otherwise failed
++ * validation paths may attach to a secure answer. */
++ if(m->s.env->cfg->ede && rep &&
++ (rep->security <= sec_status_bogus ||
++ rep->security == sec_status_secure_sentinel_fail)) {
++ mesh_find_and_attach_ede_and_reason(m, rep, r);
++ }
++
+ if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep,
+ LDNS_RCODE_NOERROR, &r->edns, &r->query_reply, m->s.region, &r->start_time) ||
+- !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
++ !reply_info_answer_encode(&m->s.qinfo, rep, r->qid,
+ r->qflags, r_buffer, 0, 1, m->s.env->scratch,
+ udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO),
+- secure))
++ secure))
+ {
+ if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s,
+ rep, LDNS_RCODE_SERVFAIL, &r->edns, &r->query_reply, m->s.region, &r->start_time))
+@@ -1503,6 +1442,7 @@
+ struct reply_info* rep = (mstate->s.return_msg?
+ mstate->s.return_msg->rep:NULL);
+ struct timeval tv = {0, 0};
++ int i = 0;
+ /* No need for the serve expired timer anymore; we are going to reply. */
+ if(mstate->s.serve_expired_data) {
+ comm_timer_delete(mstate->s.serve_expired_data->timer);
+@@ -1522,6 +1462,7 @@
+ }
+ }
+ for(r = mstate->reply_list; r; r = r->next) {
++ i++;
+ tv = r->start_time;
+
+ /* if a response-ip address block has been stored the
+@@ -1533,16 +1474,6 @@
+ mstate->s.qinfo.qclass, r->local_alias,
+ &r->query_reply.client_addr,
+ r->query_reply.client_addrlen);
+- if(mstate->s.env->cfg->stat_extended &&
+- mstate->s.respip_action_info->rpz_used) {
+- if(mstate->s.respip_action_info->rpz_disabled)
+- mstate->s.env->mesh->rpz_action[RPZ_DISABLED_ACTION]++;
+- if(mstate->s.respip_action_info->rpz_cname_override)
+- mstate->s.env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++;
+- else
+- mstate->s.env->mesh->rpz_action[respip_action_to_rpz_action(
+- mstate->s.respip_action_info->action)]++;
+- }
+ }
+
+ /* if this query is determined to be dropped during the
+@@ -1573,6 +1504,27 @@
+ prev_buffer = r_buffer;
+ }
+ }
++ /* Account for each reply sent. */
++ if(i > 0 && mstate->s.respip_action_info &&
++ mstate->s.respip_action_info->addrinfo &&
++ mstate->s.env->cfg->stat_extended &&
++ mstate->s.respip_action_info->rpz_used) {
++ if(mstate->s.respip_action_info->rpz_disabled)
++ mstate->s.env->mesh->rpz_action[RPZ_DISABLED_ACTION] += i;
++ if(mstate->s.respip_action_info->rpz_cname_override)
++ mstate->s.env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION] += i;
++ else
++ mstate->s.env->mesh->rpz_action[respip_action_to_rpz_action(
++ mstate->s.respip_action_info->action)] += i;
++ }
++ if(!mstate->s.is_drop && i > 0) {
++ if(mstate->s.env->cfg->stat_extended
++ && mstate->s.is_cachedb_answer) {
++ mstate->s.env->mesh->ans_cachedb += i;
++ }
++ }
++
++ /* Mesh area accounting */
+ if(mstate->reply_list) {
+ mstate->reply_list = NULL;
+ if(!mstate->reply_list && !mstate->cb_list) {
+@@ -1585,6 +1537,7 @@
+ mstate->s.env->mesh->num_detached_states++;
+ }
+ mstate->replies_sent = 1;
++
+ while((c = mstate->cb_list) != NULL) {
+ /* take this cb off the list; so that the list can be
+ * changed, eg. by adds from the callback routine */
+@@ -1611,7 +1564,7 @@
+ /* callback the function to inform super of result */
+ fptr_ok(fptr_whitelist_mod_inform_super(
+ mesh->mods.mod[ref->s->s.curmod]->inform_super));
+- (*mesh->mods.mod[ref->s->s.curmod]->inform_super)(&mstate->s,
++ (*mesh->mods.mod[ref->s->s.curmod]->inform_super)(&mstate->s,
+ ref->s->s.curmod, &ref->s->s);
+ /* copy state that is always relevant to super */
+ copy_state_to_super(&mstate->s, ref->s->s.curmod, &ref->s->s);
+@@ -1635,7 +1588,7 @@
+ * desire aggregation).*/
+ key.unique = NULL;
+ key.s.client_info = cinfo;
+-
++
+ result = (struct mesh_state*)rbtree_search(&mesh->all, &key);
+ return result;
+ }
+@@ -1644,7 +1597,7 @@
+ sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg,
+ uint16_t qid, uint16_t qflags)
+ {
+- struct mesh_cb* r = regional_alloc(s->s.region,
++ struct mesh_cb* r = regional_alloc(s->s.region,
+ sizeof(struct mesh_cb));
+ if(!r)
+ return 0;
+@@ -1776,7 +1729,7 @@
+ * Handles module finished.
+ * @param mesh: the mesh area.
+ * @param mstate: currently active mesh state.
+- * Deleted if finished, calls _done and _supers to
++ * Deleted if finished, calls _done and _supers to
+ * send replies to clients and inform other mesh states.
+ * This in turn may create additional runnable mesh states.
+ * @param s: state at which the current module exited.
+@@ -1810,7 +1763,7 @@
+ }
+ if(s == module_restart_next) {
+ int curmod = mstate->s.curmod;
+- for(; mstate->s.curmod < mesh->mods.num;
++ for(; mstate->s.curmod < mesh->mods.num;
+ mstate->s.curmod++) {
+ fptr_ok(fptr_whitelist_mod_clear(
+ mesh->mods.mod[mstate->s.curmod]->clear));
+@@ -1842,9 +1795,21 @@
+ if(s == module_finished) {
+ if(mstate->s.curmod == 0) {
+ struct query_info* qinfo = NULL;
++ struct edns_option* opt_list = NULL;
++ struct sockaddr_storage addr;
+ uint16_t qflags;
+ int rpz_p = 0;
+
++#ifdef CLIENT_SUBNET
++ struct edns_option* ecs;
++ if(mstate->s.need_refetch && mstate->reply_list &&
++ modstack_find(&mesh->mods, "subnetcache") != -1 &&
++ mstate->s.env->unique_mesh) {
++ addr = mstate->reply_list->query_reply.client_addr;
++ } else
++#endif
++ memset(&addr, 0, sizeof(addr));
++
+ mesh_query_done(mstate);
+ mesh_walk_supers(mesh, mstate);
+
+@@ -1854,13 +1819,28 @@
+ * we need to make a copy of the query info here. */
+ if(mstate->s.need_refetch) {
+ mesh_copy_qinfo(mstate, &qinfo, &qflags);
++#ifdef CLIENT_SUBNET
++ /* Make also a copy of the ecs option if any */
++ if((ecs = edns_opt_list_find(
++ mstate->s.edns_opts_front_in,
++ mstate->s.env->cfg->client_subnet_opcode)) != NULL) {
++ (void)edns_opt_list_append(&opt_list,
++ ecs->opt_code, ecs->opt_len,
++ ecs->opt_data,
++ mstate->s.env->scratch);
++ }
++#endif
+ rpz_p = mstate->s.rpz_passthru;
+ }
+
+- mesh_state_delete(&mstate->s);
+ if(qinfo) {
+- mesh_schedule_prefetch(mesh, qinfo, qflags,
+- 0, 1, rpz_p);
++ mesh_state_delete(&mstate->s);
++ mesh_new_prefetch(mesh, qinfo, qflags, 0,
++ rpz_p,
++ addr.ss_family!=AF_UNSPEC?&addr:NULL,
++ opt_list);
++ } else {
++ mesh_state_delete(&mstate->s);
+ }
+ return 0;
+ }
+@@ -1888,7 +1868,7 @@
+ mstate->s.reply = NULL;
+ regional_free_all(mstate->s.env->scratch);
+ s = mstate->s.ext_state[mstate->s.curmod];
+- verbose(VERB_ALGO, "mesh_run: %s module exit state is %s",
++ verbose(VERB_ALGO, "mesh_run: %s module exit state is %s",
+ mesh->mods.mod[mstate->s.curmod]->name, strextstate(s));
+ e = NULL;
+ if(mesh_continue(mesh, mstate, s, &ev))
+@@ -1908,14 +1888,14 @@
+ }
+ }
+
+-void
++void
+ mesh_log_list(struct mesh_area* mesh)
+ {
+ char buf[30];
+ struct mesh_state* m;
+ int num = 0;
+ RBTREE_FOR(m, struct mesh_state*, &mesh->all) {
+- snprintf(buf, sizeof(buf), "%d%s%s%s%s%s%s mod%d %s%s",
++ snprintf(buf, sizeof(buf), "%d%s%s%s%s%s%s mod%d %s%s",
+ num++, (m->s.is_priming)?"p":"", /* prime */
+ (m->s.is_valrec)?"v":"", /* prime */
+ (m->s.query_flags&BIT_RD)?"RD":"",
+@@ -1924,18 +1904,18 @@
+ (m->sub_set.count!=0)?"c":"", /* children */
+ m->s.curmod, (m->reply_list)?"rep":"", /*hasreply*/
+ (m->cb_list)?"cb":"" /* callbacks */
+- );
++ );
+ log_query_info(VERB_ALGO, buf, &m->s.qinfo);
+ }
+ }
+
+-void
++void
+ mesh_stats(struct mesh_area* mesh, const char* str)
+ {
+ verbose(VERB_DETAIL, "%s %u recursion states (%u with reply, "
+ "%u detached), %u waiting replies, %u recursion replies "
+- "sent, %d replies dropped, %d states jostled out",
+- str, (unsigned)mesh->all.count,
++ "sent, %d replies dropped, %d states jostled out",
++ str, (unsigned)mesh->all.count,
+ (unsigned)mesh->num_reply_states,
+ (unsigned)mesh->num_detached_states,
+ (unsigned)mesh->num_reply_addrs,
+@@ -1944,7 +1924,7 @@
+ (unsigned)mesh->stats_jostled);
+ if(mesh->replies_sent > 0) {
+ struct timeval avg;
+- timeval_divide(&avg, &mesh->replies_sum_wait,
++ timeval_divide(&avg, &mesh->replies_sum_wait,
+ mesh->replies_sent);
+ log_info("average recursion processing time "
+ ARG_LL "d.%6.6d sec",
+@@ -1954,7 +1934,7 @@
+ }
+ }
+
+-void
++void
+ mesh_stats_clear(struct mesh_area* mesh)
+ {
+ if(!mesh)
+@@ -1968,12 +1948,13 @@
+ mesh->ans_secure = 0;
+ mesh->ans_bogus = 0;
+ mesh->ans_expired = 0;
++ mesh->ans_cachedb = 0;
+ memset(&mesh->ans_rcode[0], 0, sizeof(size_t)*UB_STATS_RCODE_NUM);
+ memset(&mesh->rpz_action[0], 0, sizeof(size_t)*UB_STATS_RPZ_ACTION_NUM);
+ mesh->ans_nodata = 0;
+ }
+
+-size_t
++size_t
+ mesh_get_mem(struct mesh_area* mesh)
+ {
+ struct mesh_state* m;
+@@ -1987,7 +1968,7 @@
+ return s;
+ }
+
+-int
++int
+ mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo,
+ uint16_t flags, int prime, int valrec)
+ {
+@@ -2104,6 +2085,7 @@
+ struct timeval tv = {0, 0};
+ int must_validate = (!(qstate->query_flags&BIT_CD)
+ || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate;
++ int i = 0;
+ if(!qstate->serve_expired_data) return;
+ verbose(VERB_ALGO, "Serve expired: Trying to reply with expired data");
+ comm_timer_delete(qstate->serve_expired_data->timer);
+@@ -2175,6 +2157,7 @@
+ log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep);
+
+ for(r = mstate->reply_list; r; r = r->next) {
++ i++;
+ tv = r->start_time;
+
+ /* If address info is returned, it means the action should be an
+@@ -2184,16 +2167,6 @@
+ qstate->qinfo.qtype, qstate->qinfo.qclass,
+ r->local_alias, &r->query_reply.client_addr,
+ r->query_reply.client_addrlen);
+-
+- if(qstate->env->cfg->stat_extended && actinfo.rpz_used) {
+- if(actinfo.rpz_disabled)
+- qstate->env->mesh->rpz_action[RPZ_DISABLED_ACTION]++;
+- if(actinfo.rpz_cname_override)
+- qstate->env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++;
+- else
+- qstate->env->mesh->rpz_action[
+- respip_action_to_rpz_action(actinfo.action)]++;
+- }
+ }
+
+ /* Add EDE Stale Answer (RCF8914). Ignore global ede as this is
+@@ -2213,11 +2186,23 @@
+ tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate);
+ prev = r;
+ prev_buffer = r_buffer;
+-
+- /* Account for each reply sent. */
+- mesh->ans_expired++;
+-
+ }
++ /* Account for each reply sent. */
++ if(i > 0) {
++ mesh->ans_expired += i;
++ if(actinfo.addrinfo && qstate->env->cfg->stat_extended &&
++ actinfo.rpz_used) {
++ if(actinfo.rpz_disabled)
++ qstate->env->mesh->rpz_action[RPZ_DISABLED_ACTION] += i;
++ if(actinfo.rpz_cname_override)
++ qstate->env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION] += i;
++ else
++ qstate->env->mesh->rpz_action[
++ respip_action_to_rpz_action(actinfo.action)] += i;
++ }
++ }
++
++ /* Mesh area accounting */
+ if(mstate->reply_list) {
+ mstate->reply_list = NULL;
+ if(!mstate->reply_list && !mstate->cb_list) {
+@@ -2228,6 +2213,7 @@
+ }
+ }
+ }
++
+ while((c = mstate->cb_list) != NULL) {
+ /* take this cb off the list; so that the list can be
+ * changed, eg. by adds from the callback routine */
+--- contrib/unbound/services/mesh.h.orig
++++ contrib/unbound/services/mesh.h
+@@ -114,6 +114,8 @@
+ size_t stats_dropped;
+ /** stats, number of expired replies sent */
+ size_t ans_expired;
++ /** stats, number of cached replies from cachedb */
++ size_t ans_cachedb;
+ /** number of replies sent */
+ size_t replies_sent;
+ /** sum of waiting times for the replies */
+@@ -335,13 +337,13 @@
+ * @param leeway: TTL leeway what to expire earlier for this update.
+ * @param rpz_passthru: if true, the rpz passthru was previously found and
+ * further rpz processing is stopped.
+- * @param rep: comm_reply for the client; to be used when subnet is enabled.
++ * @param addr: sockaddr_storage for the client; to be used with subnet.
+ * @param opt_list: edns opt_list from the client; to be used when subnet is
+ * enabled.
+ */
+ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo,
+ uint16_t qflags, time_t leeway, int rpz_passthru,
+- struct comm_reply* rep, struct edns_option* opt_list);
++ struct sockaddr_storage* addr, struct edns_option* opt_list);
+
+ /**
+ * Handle new event from the wire. A serviced query has returned.
+@@ -478,14 +480,6 @@
+ struct query_info* qinfo, struct respip_client_info* cinfo,
+ uint16_t qflags, int prime, int valrec);
+
+-/**
+- * Check if the mesh state is unique.
+- * A unique mesh state uses it's unique member to point to itself, else NULL.
+- * @param mstate: mesh state to check.
+- * @return true if the mesh state is unique, false otherwise.
+- */
+-int mesh_state_is_unique(struct mesh_state* mstate);
+-
+ /**
+ * Make a mesh state unique.
+ * A unique mesh state uses it's unique member to point to itself.
+--- contrib/unbound/services/modstack.c.orig
++++ contrib/unbound/services/modstack.c
+@@ -120,12 +120,16 @@
+ stack->mod[i] = module_factory(&module_conf);
+ if(!stack->mod[i]) {
+ char md[256];
++ char * s = md;
+ snprintf(md, sizeof(md), "%s", module_conf);
+- if(strchr(md, ' ')) *(strchr(md, ' ')) = 0;
+- if(strchr(md, '\t')) *(strchr(md, '\t')) = 0;
++ /* Leading spaces are present on errors. */
++ while (*s && isspace((unsigned char)*s))
++ s++;
++ if(strchr(s, ' ')) *(strchr(s, ' ')) = 0;
++ if(strchr(s, '\t')) *(strchr(s, '\t')) = 0;
+ log_err("Unknown value in module-config, module: '%s'."
+ " This module is not present (not compiled in),"
+- " See the list of linked modules with unbound -V", md);
++ " See the list of linked modules with unbound -V", s);
+ return 0;
+ }
+ }
+--- contrib/unbound/services/outside_network.c.orig
++++ contrib/unbound/services/outside_network.c
+@@ -550,9 +550,28 @@
+ log_assert(&key_p.reuse != (struct reuse_tcp*)result);
+ log_assert(&key_p != ((struct reuse_tcp*)result)->pending);
+ }
++
++ /* It is possible that we search for something before the first element
++ * in the tree. Replace a null pointer with the first element.
++ */
++ if (!result) {
++ verbose(VERB_CLIENT, "reuse_tcp_find: taking first");
++ result = rbtree_first(&outnet->tcp_reuse);
++ }
++
+ /* not found, return null */
+ if(!result || result == RBTREE_NULL)
+ return NULL;
++
++ /* It is possible that we got the previous address, but that the
++ * address we are looking for is in the tree. If the address we got
++ * is less than the address we are looking, then take the next entry.
++ */
++ if (reuse_cmp_addrportssl(result->key, &key_p.reuse) < 0) {
++ verbose(VERB_CLIENT, "reuse_tcp_find: key too low");
++ result = rbtree_next(result);
++ }
++
+ verbose(VERB_CLIENT, "reuse_tcp_find check inexact match");
+ /* inexact match, find one of possibly several connections to the
+ * same destination address, with the correct port, ssl, and
+@@ -620,6 +639,15 @@
+ log_assert(w->addrlen > 0);
+ pend->c->tcp_do_toggle_rw = 0;
+ pend->c->tcp_do_close = 0;
++
++ /* Consistency check, if we have ssl_upstream but no sslctx, then
++ * log an error and return failure.
++ */
++ if (w->ssl_upstream && !w->outnet->sslctx) {
++ log_err("SSL upstream requested but no SSL context");
++ return 0;
++ }
++
+ /* open socket */
+ s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss, w->outnet->ip_dscp);
+
+--- contrib/unbound/services/rpz.c.orig
++++ contrib/unbound/services/rpz.c
+@@ -1188,6 +1188,22 @@
+ return z;
+ }
+
++/** Find entry for RR type in the list of rrsets for the clientip. */
++static struct local_rrset*
++rpz_find_synthesized_rrset(uint16_t qtype,
++ struct clientip_synthesized_rr* data)
++{
++ struct local_rrset* cursor = data->data;
++ while( cursor != NULL) {
++ struct packed_rrset_key* packed_rrset = &cursor->rrset->rk;
++ if(htons(qtype) == packed_rrset->type) {
++ return cursor;
++ }
++ cursor = cursor->next;
++ }
++ return NULL;
++}
++
+ /**
+ * Remove RR from RPZ's local-data
+ * @param z: local-zone for RPZ, holding write lock
+@@ -1270,15 +1286,15 @@
+
+ }
+
+-/** Remove RR from RPZ's local-zone */
++/** Remove RR from rpz localzones structure */
+ static void
+-rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+- enum rpz_action a, uint16_t rr_type, uint16_t rr_class,
+- uint8_t* rdatawl, size_t rdatalen)
++rpz_remove_local_zones_trigger(struct local_zones* zones, uint8_t* dname,
++ size_t dnamelen, enum rpz_action a, uint16_t rr_type,
++ uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen)
+ {
+ struct local_zone* z;
+ int delete_zone = 1;
+- z = rpz_find_zone(r->local_zones, dname, dnamelen, rr_class,
++ z = rpz_find_zone(zones, dname, dnamelen, rr_class,
+ 1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/);
+ if(!z) {
+ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, "
+@@ -1290,15 +1306,24 @@
+ dnamelen, rr_type, rdatawl, rdatalen);
+ else if(a != localzone_type_to_rpz_action(z->type)) {
+ lock_rw_unlock(&z->lock);
+- lock_rw_unlock(&r->local_zones->lock);
++ lock_rw_unlock(&zones->lock);
+ return;
+ }
+ lock_rw_unlock(&z->lock);
+ if(delete_zone) {
+- local_zones_del_zone(r->local_zones, z);
++ local_zones_del_zone(zones, z);
+ }
+- lock_rw_unlock(&r->local_zones->lock);
+- return;
++ lock_rw_unlock(&zones->lock);
++}
++
++/** Remove RR from RPZ's local-zone */
++static void
++rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint16_t rr_class,
++ uint8_t* rdatawl, size_t rdatalen)
++{
++ rpz_remove_local_zones_trigger(r->local_zones, dname, dnamelen,
++ a, rr_type, rr_class, rdatawl, rdatalen);
+ }
+
+ static void
+@@ -1335,15 +1360,159 @@
+ lock_rw_unlock(&r->respip_set->lock);
+ }
+
++/** find and remove type from list of local_rrset entries*/
++static void
++del_local_rrset_from_list(struct local_rrset** list_head, uint16_t dtype)
++{
++ struct local_rrset* prev=NULL, *p=*list_head;
++ while(p && ntohs(p->rrset->rk.type) != dtype) {
++ prev = p;
++ p = p->next;
++ }
++ if(!p)
++ return; /* rrset type not found */
++ /* unlink it */
++ if(prev) prev->next = p->next;
++ else *list_head = p->next;
++ /* no memory recycling for zone deletions ... */
++}
++
++/** Delete client-ip trigger RR from its RRset and perhaps also the rrset
++ * from the linked list. Returns if the local data is empty and the node can
++ * be deleted too, or not. */
++static int rpz_remove_clientip_rr(struct clientip_synthesized_rr* node,
++ uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct local_rrset* rrset;
++ struct packed_rrset_data* d;
++ size_t index;
++ rrset = rpz_find_synthesized_rrset(rr_type, node);
++ if(rrset == NULL)
++ return 0; /* type not found, ignore */
++ d = (struct packed_rrset_data*)rrset->rrset->entry.data;
++ if(!packed_rrset_find_rr(d, rdatawl, rdatalen, &index))
++ return 0; /* RR not found, ignore */
++ if(d->count == 1) {
++ /* regional alloc'd */
++ /* delete the type entry from the list */
++ del_local_rrset_from_list(&node->data, rr_type);
++ /* if the list is empty, the node can be removed too */
++ if(node->data == NULL)
++ return 1;
++ } else if (d->count > 1) {
++ if(!local_rrset_remove_rr(d, index))
++ return 0;
++ }
++ return 0;
++}
++
++/** remove trigger RR from clientip_syntheized set tree. */
++static void
++rpz_clientip_remove_trigger_rr(struct clientip_synthesized_rrset* set,
++ struct sockaddr_storage* addr, socklen_t addrlen, int net,
++ enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct clientip_synthesized_rr* node;
++ int delete_node = 1;
++
++ lock_rw_wrlock(&set->lock);
++ node = (struct clientip_synthesized_rr*)addr_tree_find(&set->entries,
++ addr, addrlen, net);
++ if(node == NULL) {
++ /* netblock not found */
++ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, "
++ "RPZ address, netblock not found");
++ lock_rw_unlock(&set->lock);
++ return;
++ }
++ lock_rw_wrlock(&node->lock);
++ if(a == RPZ_LOCAL_DATA_ACTION) {
++ /* remove RR, signal whether entry can be removed */
++ delete_node = rpz_remove_clientip_rr(node, rr_type, rdatawl,
++ rdatalen);
++ } else if(a != node->action) {
++ /* ignore the RR with different action specification */
++ delete_node = 0;
++ }
++ if(delete_node) {
++ rbtree_delete(&set->entries, node->node.node.key);
++ }
++ lock_rw_unlock(&set->lock);
++ lock_rw_unlock(&node->lock);
++ if(delete_node) {
++ lock_rw_destroy(&node->lock);
++ }
++}
++
++/** Remove clientip trigger RR from RPZ. */
++static void
++rpz_remove_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct sockaddr_storage addr;
++ socklen_t addrlen;
++ int net, af;
++ if(a == RPZ_INVALID_ACTION)
++ return;
++ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af))
++ return;
++ rpz_clientip_remove_trigger_rr(r->client_set, &addr, addrlen, net,
++ a, rr_type, rdatawl, rdatalen);
++}
++
++/** Remove nsip trigger RR from RPZ. */
++static void
++rpz_remove_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen)
++{
++ struct sockaddr_storage addr;
++ socklen_t addrlen;
++ int net, af;
++ if(a == RPZ_INVALID_ACTION)
++ return;
++ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af))
++ return;
++ rpz_clientip_remove_trigger_rr(r->ns_set, &addr, addrlen, net,
++ a, rr_type, rdatawl, rdatalen);
++}
++
++/** Remove nsdname trigger RR from RPZ. */
++static void
++rpz_remove_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
++ enum rpz_action a, uint16_t rr_type, uint16_t rr_class,
++ uint8_t* rdatawl, size_t rdatalen)
++{
++ uint8_t* dname_stripped = NULL;
++ size_t dnamelen_stripped = 0;
++ if(a == RPZ_INVALID_ACTION)
++ return;
++ if(!rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped,
++ &dnamelen_stripped))
++ return;
++ rpz_remove_local_zones_trigger(r->nsdname_zones, dname_stripped,
++ dnamelen_stripped, a, rr_type, rr_class, rdatawl, rdatalen);
++ free(dname_stripped);
++}
++
+ void
+-rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen,
+- uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen)
++rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
++ size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl,
++ size_t rdatalen)
+ {
+ size_t policydnamelen;
+ enum rpz_trigger t;
+ enum rpz_action a;
+ uint8_t* policydname;
+
++ if(rpz_type_ignored(rr_type)) {
++ /* this rpz action is not valid, eg. this is the SOA or NS RR */
++ return;
++ }
++ if(!dname_subdomain_c(dname, azname)) {
++ /* not subdomain of the RPZ zone. */
++ return;
++ }
++
+ if(!(policydname = calloc(1, LDNS_MAX_DOMAINLEN + 1)))
+ return;
+
+@@ -1358,13 +1527,28 @@
+ return;
+ }
+ t = rpz_dname_to_trigger(policydname, policydnamelen);
++ if(t == RPZ_INVALID_TRIGGER) {
++ /* skipping invalid trigger */
++ free(policydname);
++ return;
++ }
+ if(t == RPZ_QNAME_TRIGGER) {
+ rpz_remove_qname_trigger(r, policydname, policydnamelen, a,
+ rr_type, rr_class, rdatawl, rdatalen);
+ } else if(t == RPZ_RESPONSE_IP_TRIGGER) {
+ rpz_remove_response_ip_trigger(r, policydname, policydnamelen,
+ a, rr_type, rdatawl, rdatalen);
++ } else if(t == RPZ_CLIENT_IP_TRIGGER) {
++ rpz_remove_clientip_trigger(r, policydname, policydnamelen, a,
++ rr_type, rdatawl, rdatalen);
++ } else if(t == RPZ_NSIP_TRIGGER) {
++ rpz_remove_nsip_trigger(r, policydname, policydnamelen, a,
++ rr_type, rdatawl, rdatalen);
++ } else if(t == RPZ_NSDNAME_TRIGGER) {
++ rpz_remove_nsdname_trigger(r, policydname, policydnamelen, a,
++ rr_type, rr_class, rdatawl, rdatalen);
+ }
++ /* else it was an unsupported trigger, also skipped. */
+ free(policydname);
+ }
+
+@@ -1563,21 +1747,6 @@
+ return 1;
+ }
+
+-static struct local_rrset*
+-rpz_find_synthesized_rrset(uint16_t qtype,
+- struct clientip_synthesized_rr* data)
+-{
+- struct local_rrset* cursor = data->data;
+- while( cursor != NULL) {
+- struct packed_rrset_key* packed_rrset = &cursor->rrset->rk;
+- if(htons(qtype) == packed_rrset->type) {
+- return cursor;
+- }
+- cursor = cursor->next;
+- }
+- return NULL;
+-}
+-
+ /** allocate SOA record ubrrsetkey in region */
+ static struct ub_packed_rrset_key*
+ make_soa_ubrrset(struct auth_zone* auth_zone, struct auth_rrset* soa,
+@@ -1713,7 +1882,8 @@
+ 0, /* ns */
+ 0, /* ar */
+ 0, /* total */
+- sec_status_insecure);
++ sec_status_insecure,
++ LDNS_EDE_NONE);
+ if(msg->rep)
+ msg->rep->authoritative = 1;
+ if(!rpz_add_soa(msg->rep, ms, az))
+@@ -1742,7 +1912,8 @@
+ 0, /* ns */
+ 0, /* ar */
+ 0, /* total */
+- sec_status_insecure);
++ sec_status_insecure,
++ LDNS_EDE_NONE);
+ if(msg->rep)
+ msg->rep->authoritative = 1;
+ if(!rpz_add_soa(msg->rep, ms, az))
+@@ -1772,7 +1943,8 @@
+ 0, /* ns */
+ 0, /* ar */
+ 1, /* total */
+- sec_status_insecure);
++ sec_status_insecure,
++ LDNS_EDE_NONE);
+ if(new_reply_info == NULL) {
+ log_err("out of memory");
+ return NULL;
+@@ -1990,7 +2162,7 @@
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+- ms->respip_action_info->action = respip_truncate;
++ ms->tcp_required = 1;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+@@ -2045,7 +2217,7 @@
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+- ms->respip_action_info->action = respip_truncate;
++ ms->tcp_required = 1;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+@@ -2256,7 +2428,7 @@
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+- ms->respip_action_info->action = respip_truncate;
++ ms->tcp_required = 1;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+@@ -2276,6 +2448,10 @@
+ rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ ret = NULL;
+ }
++ if(r->log)
++ log_rpz_apply("qname", (z?z->name:NULL), NULL,
++ localzone_type_to_rpz_action(lzt),
++ &is->qchase, NULL, ms, r->log_name);
+ lock_rw_unlock(&z->lock);
+ lock_rw_unlock(&a->lock);
+ return ret;
+--- contrib/unbound/services/rpz.h.orig
++++ contrib/unbound/services/rpz.h
+@@ -84,10 +84,11 @@
+ RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/
+ };
+
+-struct clientip_synthesized_rrset{
++struct clientip_synthesized_rrset {
+ struct regional* region;
+ struct rbtree_type entries;
+- lock_rw_type lock; /* lock on the respip tree */
++ /** lock on the entries tree */
++ lock_rw_type lock;
+ };
+
+ struct clientip_synthesized_rr {
+@@ -95,10 +96,6 @@
+ struct addr_tree_node node;
+ /** lock on the node item */
+ lock_rw_type lock;
+- /** tag bitlist */
+- uint8_t* taglist;
+- /** length of the taglist (in bytes) */
+- size_t taglen;
+ /** action for this address span */
+ enum rpz_action action;
+ /** "local data" for this node */
+@@ -152,6 +149,7 @@
+ /**
+ * Delete policy matching RR, used for IXFR.
+ * @param r: the rpz to add the policy to.
++ * @param azname: dname of the auth-zone
+ * @param aznamelen: the length of the auth-zone name
+ * @param dname: dname of the RR
+ * @param dnamelen: length of the dname
+@@ -160,9 +158,9 @@
+ * @param rdatawl: rdata of the RR, prepended with the rdata size
+ * @param rdatalen: length if the RR, including the prepended rdata size
+ */
+-void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname,
+- size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl,
+- size_t rdatalen);
++void rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen,
++ uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class,
++ uint8_t* rdatawl, size_t rdatalen);
+
+ /**
+ * Walk over the RPZ zones to find and apply a QNAME trigger policy.
+--- contrib/unbound/sldns/rrdef.c.orig
++++ contrib/unbound/sldns/rrdef.c
+@@ -702,7 +702,11 @@
+
+ /* TYPEXX representation */
+ if (strlen(name) > 4 && strncasecmp(name, "TYPE", 4) == 0) {
+- return atoi(name + 4);
++ unsigned int a = atoi(name + 4);
++ if (a > LDNS_RR_TYPE_LAST) {
++ return (enum sldns_enum_rr_type)0;
++ }
++ return a;
+ }
+
+ /* Normal types */
+@@ -740,7 +744,11 @@
+
+ /* CLASSXX representation */
+ if (strlen(name) > 5 && strncasecmp(name, "CLASS", 5) == 0) {
+- return atoi(name + 5);
++ unsigned int a = atoi(name + 5);
++ if (a > LDNS_RR_CLASS_LAST) {
++ return (enum sldns_enum_rr_class)0;
++ }
++ return a;
+ }
+
+ /* Normal types */
+--- contrib/unbound/sldns/rrdef.h.orig
++++ contrib/unbound/sldns/rrdef.h
+@@ -433,10 +433,12 @@
+ LDNS_EDNS_DHU = 6, /* RFC6975 */
+ LDNS_EDNS_N3U = 7, /* RFC6975 */
+ LDNS_EDNS_CLIENT_SUBNET = 8, /* RFC7871 */
++ LDNS_EDNS_COOKIE = 10, /* RFC7873 */
+ LDNS_EDNS_KEEPALIVE = 11, /* draft-ietf-dnsop-edns-tcp-keepalive*/
+ LDNS_EDNS_PADDING = 12, /* RFC7830 */
+ LDNS_EDNS_EDE = 15, /* RFC8914 */
+- LDNS_EDNS_CLIENT_TAG = 16 /* draft-bellis-dnsop-edns-tags-01 */
++ LDNS_EDNS_CLIENT_TAG = 16, /* draft-bellis-dnsop-edns-tags-01 */
++ LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST = 65534
+ };
+ typedef enum sldns_enum_edns_option sldns_edns_option;
+
+@@ -482,6 +484,9 @@
+ #define LDNS_TSIG_ERROR_BADNAME 20
+ #define LDNS_TSIG_ERROR_BADALG 21
+
++/** DNS Cookie extended rcode */
++#define LDNS_EXT_RCODE_BADCOOKIE 23
++
+ /**
+ * Contains all information about resource record types.
+ *
+--- contrib/unbound/sldns/str2wire.c.orig
++++ contrib/unbound/sldns/str2wire.c
+@@ -357,7 +357,7 @@
+ break;
+ default : break;
+ }
+- return "\n\t ";
++ return "\n\t ";
+ }
+
+ /* Syntactic sugar for sldns_rr_new_frm_str_internal */
+@@ -448,7 +448,7 @@
+ sldns_buffer_position(strbuf));
+ }
+ hex_data_size = (size_t)atoi(token);
+- if(hex_data_size > LDNS_MAX_RDFLEN ||
++ if(hex_data_size > LDNS_MAX_RDFLEN ||
+ *rr_cur_len + hex_data_size > *rr_len) {
+ return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL,
+ sldns_buffer_position(strbuf));
+@@ -567,7 +567,7 @@
+ /* check if not quoted yet, and we have encountered quotes */
+ if(!*quoted && sldns_rdf_type_maybe_quoted(rdftype) &&
+ slen >= 2 &&
+- (token[0] == '"' || token[0] == '\'') &&
++ (token[0] == '"' || token[0] == '\'') &&
+ (token[slen-1] == '"' || token[slen-1] == '\'')) {
+ /* move token two smaller (quotes) with endnull */
+ memmove(token, token+1, slen-2);
+@@ -698,7 +698,7 @@
+ mandatory = svcparams[i];
+ }
+
+- /* 4. verify that all the SvcParamKeys in mandatory are present */
++ /* Verify that all the SvcParamKeys in mandatory are present */
+ if(mandatory) {
+ /* Divide by sizeof(uint16_t)*/
+ uint16_t mandatory_nkeys = sldns_read_uint16(mandatory + 2) / sizeof(uint16_t);
+@@ -785,7 +785,7 @@
+ token[2]=='\t')) {
+ was_unknown_rr_format = 1;
+ if((status=rrinternal_parse_unknown(strbuf, token,
+- token_len, rr, rr_len, &rr_cur_len,
++ token_len, rr, rr_len, &rr_cur_len,
+ pre_data_pos)) != 0)
+ return status;
+ } else if(token_strlen > 0 || quoted) {
+@@ -844,7 +844,7 @@
+ if (rr_type == LDNS_RR_TYPE_SVCB || rr_type == LDNS_RR_TYPE_HTTPS) {
+ size_t rdata_len = rr_cur_len - dname_len - 10;
+ uint8_t *rdata = rr+dname_len + 10;
+-
++
+ /* skip 1st rdata field SvcPriority (uint16_t) */
+ if (rdata_len < sizeof(uint16_t))
+ return LDNS_WIREPARSE_ERR_OK;
+@@ -1123,36 +1123,40 @@
+ return key_value;
+
+ } else switch (key_len) {
+- case sizeof("mandatory")-1:
+- if (!strncmp(key, "mandatory", sizeof("mandatory")-1))
+- return SVCB_KEY_MANDATORY;
+- if (!strncmp(key, "echconfig", sizeof("echconfig")-1))
+- return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */
++ case 3:
++ if (!strncmp(key, "ech", key_len))
++ return SVCB_KEY_ECH;
+ break;
+
+- case sizeof("alpn")-1:
+- if (!strncmp(key, "alpn", sizeof("alpn")-1))
++ case 4:
++ if (!strncmp(key, "alpn", key_len))
+ return SVCB_KEY_ALPN;
+- if (!strncmp(key, "port", sizeof("port")-1))
++ if (!strncmp(key, "port", key_len))
+ return SVCB_KEY_PORT;
+ break;
+
+- case sizeof("no-default-alpn")-1:
+- if (!strncmp( key , "no-default-alpn"
+- , sizeof("no-default-alpn")-1))
+- return SVCB_KEY_NO_DEFAULT_ALPN;
++ case 7:
++ if (!strncmp(key, "dohpath", key_len))
++ return SVCB_KEY_DOHPATH;
+ break;
+
+- case sizeof("ipv4hint")-1:
+- if (!strncmp(key, "ipv4hint", sizeof("ipv4hint")-1))
++ case 8:
++ if (!strncmp(key, "ipv4hint", key_len))
+ return SVCB_KEY_IPV4HINT;
+- if (!strncmp(key, "ipv6hint", sizeof("ipv6hint")-1))
++ if (!strncmp(key, "ipv6hint", key_len))
+ return SVCB_KEY_IPV6HINT;
+ break;
+
+- case sizeof("ech")-1:
+- if (!strncmp(key, "ech", sizeof("ech")-1))
+- return SVCB_KEY_ECH;
++ case 9:
++ if (!strncmp(key, "mandatory", key_len))
++ return SVCB_KEY_MANDATORY;
++ if (!strncmp(key, "echconfig", key_len))
++ return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */
++ break;
++
++ case 15:
++ if (!strncmp(key, "no-default-alpn", key_len))
++ return SVCB_KEY_NO_DEFAULT_ALPN;
+ break;
+
+ default:
+@@ -1477,7 +1481,7 @@
+ size_t str_len;
+ size_t dst_len;
+ size_t val_len;
+-
++
+ val_len = strlen(val);
+
+ if (val_len > sizeof(unescaped_dst)) {
+@@ -1511,7 +1515,34 @@
+ sldns_write_uint16(rd + 2, dst_len);
+ memcpy(rd + 4, unescaped_dst, dst_len);
+ *rd_len = 4 + dst_len;
+-
++
++ return LDNS_WIREPARSE_ERR_OK;
++}
++
++static int
++sldns_str2wire_svcbparam_dohpath_value(const char* val,
++ uint8_t* rd, size_t* rd_len)
++{
++ size_t val_len;
++
++ /* RFC6570#section-2.1
++ * "The characters outside of expressions in a URI Template string are
++ * intended to be copied literally"
++ * Practically this means we do not have to look for "double escapes"
++ * like in the alpn value list.
++ */
++
++ val_len = strlen(val);
++
++ if (*rd_len < 4 + val_len) {
++ return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
++ }
++
++ sldns_write_uint16(rd, SVCB_KEY_DOHPATH);
++ sldns_write_uint16(rd + 2, val_len);
++ memcpy(rd + 4, val, val_len);
++ *rd_len = 4 + val_len;
++
+ return LDNS_WIREPARSE_ERR_OK;
+ }
+
+@@ -1535,6 +1566,7 @@
+ case SVCB_KEY_PORT:
+ case SVCB_KEY_IPV4HINT:
+ case SVCB_KEY_IPV6HINT:
++ case SVCB_KEY_DOHPATH:
+ return LDNS_WIREPARSE_ERR_SVCB_MISSING_PARAM;
+ #endif
+ default:
+@@ -1566,6 +1598,8 @@
+ return sldns_str2wire_svcbparam_ech_value(val, rd, rd_len);
+ case SVCB_KEY_ALPN:
+ return sldns_str2wire_svcbparam_alpn_value(val, rd, rd_len);
++ case SVCB_KEY_DOHPATH:
++ return sldns_str2wire_svcbparam_dohpath_value(val, rd, rd_len);
+ default:
+ str_len = strlen(val);
+ if (*rd_len < 4 + str_len)
+@@ -1593,7 +1627,7 @@
+ /* case: key=value */
+ if (eq_pos != NULL && eq_pos[1]) {
+ val_in = eq_pos + 1;
+-
++
+ /* unescape characters and "" blocks */
+ if (*val_in == '"') {
+ val_in++;
+@@ -1610,11 +1644,11 @@
+ }
+ *val_out = 0;
+
+- return sldns_str2wire_svcparam_value(str, eq_pos - str,
+- unescaped_val[0] ? unescaped_val : NULL, rd, rd_len);
++ return sldns_str2wire_svcparam_value(str, eq_pos - str,
++ unescaped_val[0] ? unescaped_val : NULL, rd, rd_len);
+ }
+ /* case: key= */
+- else if (eq_pos != NULL && !(eq_pos[1])) {
++ else if (eq_pos != NULL && !(eq_pos[1])) {
+ return sldns_str2wire_svcparam_value(str, eq_pos - str, NULL, rd, rd_len);
+ }
+ /* case: key */
+@@ -2425,12 +2459,13 @@
+ (void)strlcpy(proto_str, token, sizeof(proto_str));
+ } else {
+ int serv_port;
+- struct servent *serv = getservbyname(token, proto_str);
+- if(serv) serv_port=(int)ntohs((uint16_t)serv->s_port);
++ if(atoi(token) != 0) serv_port=atoi(token);
++ else if(strcmp(token, "0") == 0) serv_port=0;
+ else if(strcasecmp(token, "domain")==0) serv_port=53;
+ else {
+- serv_port = atoi(token);
+- if(serv_port == 0 && strcmp(token, "0") != 0) {
++ struct servent *serv = getservbyname(token, proto_str);
++ if(serv) serv_port=(int)ntohs((uint16_t)serv->s_port);
++ else {
+ #ifdef HAVE_ENDSERVENT
+ endservent();
+ #endif
+@@ -2440,16 +2475,16 @@
+ return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX,
+ sldns_buffer_position(&strbuf));
+ }
+- if(serv_port < 0 || serv_port > 65535) {
++ }
++ if(serv_port < 0 || serv_port > 65535) {
+ #ifdef HAVE_ENDSERVENT
+- endservent();
++ endservent();
+ #endif
+ #ifdef HAVE_ENDPROTOENT
+- endprotoent();
++ endprotoent();
+ #endif
+- return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX,
+- sldns_buffer_position(&strbuf));
+- }
++ return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX,
++ sldns_buffer_position(&strbuf));
+ }
+ if(rd_len < 1+serv_port/8+1) {
+ /* bitmap is larger, init new bytes at 0 */
+--- contrib/unbound/sldns/str2wire.h.orig
++++ contrib/unbound/sldns/str2wire.h
+@@ -38,7 +38,8 @@
+ #define SVCB_KEY_IPV4HINT 4
+ #define SVCB_KEY_ECH 5
+ #define SVCB_KEY_IPV6HINT 6
+-#define SVCPARAMKEY_COUNT 7
++#define SVCB_KEY_DOHPATH 7
++#define SVCPARAMKEY_COUNT 8
+
+ #define MAX_NUMBER_OF_SVCPARAMS 64
+
+@@ -236,6 +237,7 @@
+ #define LDNS_WIREPARSE_ERR_SVCB_NO_DEFAULT_ALPN_VALUE 385
+ #define LDNS_WIREPARSE_ERR_SVCPARAM_BROKEN_RDATA 386
+
++
+ /**
+ * Get reference to a constant string for the (parse) error.
+ * @param e: error return value
+--- contrib/unbound/sldns/wire2str.c.orig
++++ contrib/unbound/sldns/wire2str.c
+@@ -159,7 +159,7 @@
+ "Mandatory SvcParamKey is missing"},
+ { LDNS_WIREPARSE_ERR_SVCB_MANDATORY_DUPLICATE_KEY,
+ "Keys in SvcParam mandatory MUST be unique" },
+- { LDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY,
++ { LDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY,
+ "mandatory MUST not be included as mandatory parameter" },
+ { LDNS_WIREPARSE_ERR_SVCB_PORT_VALUE_SYNTAX,
+ "Could not parse port SvcParamValue" },
+@@ -192,6 +192,7 @@
+ { 6, "DHU" },
+ { 7, "N3U" },
+ { 8, "edns-client-subnet" },
++ { 10, "COOKIE" },
+ { 11, "edns-tcp-keepalive"},
+ { 12, "Padding" },
+ { 15, "EDE"},
+@@ -199,6 +200,38 @@
+ };
+ sldns_lookup_table* sldns_edns_options = sldns_edns_options_data;
+
++/* From RFC8914 5.2 Table 3, the "Extended DNS Error Codes" registry. */
++static sldns_lookup_table sldns_edns_ede_codes_data[] = {
++ { LDNS_EDE_NONE, "None" },
++ { LDNS_EDE_OTHER, "Other Error" },
++ { LDNS_EDE_UNSUPPORTED_DNSKEY_ALG, "Unsupported DNSKEY Algorithm" },
++ { LDNS_EDE_UNSUPPORTED_DS_DIGEST, "Unsupported DS Digest Type" },
++ { LDNS_EDE_STALE_ANSWER, "Stale Answer" },
++ { LDNS_EDE_FORGED_ANSWER, "Forged Answer" },
++ { LDNS_EDE_DNSSEC_INDETERMINATE, "DNSSEC Indeterminate" },
++ { LDNS_EDE_DNSSEC_BOGUS, "DNSSEC Bogus" },
++ { LDNS_EDE_SIGNATURE_EXPIRED, "Signature Expired" },
++ { LDNS_EDE_SIGNATURE_NOT_YET_VALID, "Signature Not Yet Valid" },
++ { LDNS_EDE_DNSKEY_MISSING, "DNSKEY Missing" },
++ { LDNS_EDE_RRSIGS_MISSING, "RRSIGs Missing" },
++ { LDNS_EDE_NO_ZONE_KEY_BIT_SET, "No Zone Key Bit Set" },
++ { LDNS_EDE_NSEC_MISSING, "NSEC Missing" },
++ { LDNS_EDE_CACHED_ERROR, "Cached Error" },
++ { LDNS_EDE_NOT_READY, "Not Ready" },
++ { LDNS_EDE_BLOCKED, "Blocked" },
++ { LDNS_EDE_CENSORED, "Censored" },
++ { LDNS_EDE_FILTERED, "Filtered" },
++ { LDNS_EDE_PROHIBITED, "Prohibited" },
++ { LDNS_EDE_STALE_NXDOMAIN_ANSWER, "Stale NXDOMAIN Answer" },
++ { LDNS_EDE_NOT_AUTHORITATIVE, "Not Authoritative" },
++ { LDNS_EDE_NOT_SUPPORTED, "Not Supported" },
++ { LDNS_EDE_NO_REACHABLE_AUTHORITY, "No Reachable Authority" },
++ { LDNS_EDE_NETWORK_ERROR, "Network Error" },
++ { LDNS_EDE_INVALID_DATA, "Invalid Data" },
++ { 0, NULL}
++};
++sldns_lookup_table* sldns_edns_ede_codes = sldns_edns_ede_codes_data;
++
+ static sldns_lookup_table sldns_tsig_errors_data[] = {
+ { LDNS_TSIG_ERROR_NOERROR, "NOERROR" },
+ { LDNS_RCODE_FORMERR, "FORMERR" },
+@@ -224,7 +257,7 @@
+ /* draft-ietf-dnsop-svcb-https-06: 6. Initial SvcParamKeys */
+ const char *svcparamkey_strs[] = {
+ "mandatory", "alpn", "no-default-alpn", "port",
+- "ipv4hint", "ech", "ipv6hint"
++ "ipv4hint", "ech", "ipv6hint", "dohpath"
+ };
+
+ char* sldns_wire2str_pkt(uint8_t* data, size_t len)
+@@ -487,7 +520,7 @@
+ uint8_t* rr = *d;
+ size_t rrlen = *dlen, dname_off, rdlen, ordlen;
+ uint16_t rrtype = 0;
+-
++
+ if(*dlen >= 3 && (*d)[0]==0 &&
+ sldns_read_uint16((*d)+1)==LDNS_RR_TYPE_OPT) {
+ /* perform EDNS OPT processing */
+@@ -1119,7 +1152,7 @@
+ w += sldns_str_print(s, slen, "%s", ",");
+ }
+ w += sldns_str_print(s, slen, "\"");
+-
++
+ return w;
+ }
+
+@@ -1139,7 +1172,7 @@
+ (*s) += size;
+ (*slen) -= size;
+
+- w += sldns_str_print(s, slen, "\"");
++ w += sldns_str_print(s, slen, "\"");
+
+ return w + size;
+ }
+@@ -1162,7 +1195,7 @@
+
+ /* verify that we have data_len data */
+ if (data_len > *dlen)
+- return -1;
++ return -1;
+
+ written_chars += sldns_print_svcparamkey(s, slen, svcparamkey);
+ if (!data_len) {
+@@ -1174,6 +1207,7 @@
+ case SVCB_KEY_IPV4HINT:
+ case SVCB_KEY_IPV6HINT:
+ case SVCB_KEY_MANDATORY:
++ case SVCB_KEY_DOHPATH:
+ return -1;
+ default:
+ return written_chars;
+@@ -1201,6 +1235,8 @@
+ case SVCB_KEY_ECH:
+ r = sldns_wire2str_svcparam_ech2str(s, slen, data_len, *d);
+ break;
++ case SVCB_KEY_DOHPATH:
++ /* fallthrough */
+ default:
+ r = sldns_str_print(s, slen, "=\"");
+
+@@ -1222,7 +1258,7 @@
+ }
+ if (r <= 0)
+ return -1; /* wireformat error */
+-
++
+ written_chars += r;
+ *d += data_len;
+ *dlen -= data_len;
+@@ -1551,7 +1587,7 @@
+ unsigned i, bit, window, block_len;
+ uint16_t t;
+ int w = 0;
+-
++
+ /* check for errors */
+ while(pl) {
+ if(pl < 2) return -1;
+@@ -2231,6 +2267,52 @@
+ return w;
+ }
+
++int sldns_wire2str_edns_ede_print(char** s, size_t* sl,
++ uint8_t* data, size_t len)
++{
++ uint16_t ede_code;
++ int w = 0;
++ sldns_lookup_table *lt;
++ size_t i;
++ int printable;
++
++ if(len < 2) {
++ w += sldns_str_print(s, sl, "malformed ede ");
++ w += print_hex_buf(s, sl, data, len);
++ return w;
++ }
++
++ ede_code = sldns_read_uint16(data);
++ lt = sldns_lookup_by_id(sldns_edns_ede_codes, (int)ede_code);
++ if(lt && lt->name)
++ w += sldns_str_print(s, sl, "%s", lt->name);
++ else w += sldns_str_print(s, sl, "%d", (int)ede_code);
++
++ if(len == 2)
++ return w;
++
++ w += sldns_str_print(s, sl, " ");
++
++ /* If it looks like text, show it as text. */
++ printable=1;
++ for(i=2; i<len; i++) {
++ if(isprint((unsigned char)data[i]) || data[i] == '\t')
++ continue;
++ printable = 0;
++ break;
++ }
++ if(printable) {
++ w += sldns_str_print(s, sl, "\"");
++ for(i=2; i<len; i++) {
++ w += str_char_print(s, sl, data[i]);
++ }
++ w += sldns_str_print(s, sl, "\"");
++ } else {
++ w += print_hex_buf(s, sl, data+2, len-2);
++ }
++ return w;
++}
++
+ int sldns_wire2str_edns_option_print(char** s, size_t* sl,
+ uint16_t option_code, uint8_t* optdata, size_t optlen)
+ {
+@@ -2265,6 +2347,9 @@
+ case LDNS_EDNS_PADDING:
+ w += print_hex_buf(s, sl, optdata, optlen);
+ break;
++ case LDNS_EDNS_EDE:
++ w += sldns_wire2str_edns_ede_print(s, sl, optdata, optlen);
++ break;
+ default:
+ /* unknown option code */
+ w += print_hex_buf(s, sl, optdata, optlen);
+--- contrib/unbound/sldns/wire2str.h.orig
++++ contrib/unbound/sldns/wire2str.h
+@@ -36,6 +36,8 @@
+ extern struct sldns_struct_lookup_table* sldns_edns_flags;
+ /** EDNS option codes */
+ extern struct sldns_struct_lookup_table* sldns_edns_options;
++/** EDNS EDE codes */
++extern struct sldns_struct_lookup_table* sldns_edns_ede_codes;
+ /** error string from wireparse */
+ extern struct sldns_struct_lookup_table* sldns_wireparse_errors;
+ /** tsig errors are the rcodes with extra (higher) values */
+@@ -1020,6 +1022,17 @@
+ int sldns_wire2str_edns_subnet_print(char** str, size_t* str_len,
+ uint8_t* option_data, size_t option_len);
+
++/**
++ * Print EDNS EDE option data to string. User buffers, moves string pointers.
++ * @param str: string buffer.
++ * @param str_len: length of string buffer.
++ * @param option_data: buffer with EDNS option code data.
++ * @param option_len: length of the data for this option.
++ * @return number of characters (except null) needed to print.
++ */
++int sldns_wire2str_edns_ede_print(char** str, size_t* str_len,
++ uint8_t* option_data, size_t option_len);
++
+ /**
+ * Print an EDNS option as OPT: VALUE. User buffers, moves string pointers.
+ * @param str: string buffer.
+--- contrib/unbound/smallapp/unbound-anchor.c.orig
++++ contrib/unbound/smallapp/unbound-anchor.c
+@@ -1582,8 +1582,7 @@
+
+ /**
+ * Perform XML parsing of the root-anchors file
+- * Its format description can be read here
+- * https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.txt
++ * Its format description can be found in RFC 7958.
+ * It uses libexpat.
+ * @param xml: BIO with xml data.
+ * @param now: the current time for checking DS validity periods.
+--- contrib/unbound/smallapp/unbound-checkconf.c.orig
++++ contrib/unbound/smallapp/unbound-checkconf.c
+@@ -707,6 +707,23 @@
+ cfg->auto_trust_anchor_file_list, cfg->chrootdir, cfg);
+ check_chroot_filelist_wild("trusted-keys-file",
+ cfg->trusted_keys_file_list, cfg->chrootdir, cfg);
++ if(cfg->disable_edns_do && strstr(cfg->module_conf, "validator")
++ && (cfg->trust_anchor_file_list
++ || cfg->trust_anchor_list
++ || cfg->auto_trust_anchor_file_list
++ || cfg->trusted_keys_file_list)) {
++ char* key = NULL;
++ if(cfg->auto_trust_anchor_file_list)
++ key = cfg->auto_trust_anchor_file_list->str;
++ if(!key && cfg->trust_anchor_file_list)
++ key = cfg->trust_anchor_file_list->str;
++ if(!key && cfg->trust_anchor_list)
++ key = cfg->trust_anchor_list->str;
++ if(!key && cfg->trusted_keys_file_list)
++ key = cfg->trusted_keys_file_list->str;
++ if(!key) key = "";
++ fatal_exit("disable-edns-do does not allow DNSSEC to work, but the validator module uses a trust anchor %s, turn off disable-edns-do or disable validation", key);
++ }
+ #ifdef USE_IPSECMOD
+ if(cfg->ipsecmod_enabled && strstr(cfg->module_conf, "ipsecmod")) {
+ /* only check hook if enabled */
+@@ -714,7 +731,7 @@
+ cfg->chrootdir, cfg);
+ }
+ #endif
+- /* remove chroot setting so that modules are not stripping pathnames*/
++ /* remove chroot setting so that modules are not stripping pathnames */
+ free(cfg->chrootdir);
+ cfg->chrootdir = NULL;
+
+--- contrib/unbound/smallapp/unbound-control.c.orig
++++ contrib/unbound/smallapp/unbound-control.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -59,6 +59,7 @@
+ #include "util/locks.h"
+ #include "util/net_help.h"
+ #include "util/shm_side/shm_main.h"
++#include "util/timeval_func.h"
+ #include "daemon/stats.h"
+ #include "sldns/wire2str.h"
+ #include "sldns/pkthdr.h"
+@@ -186,31 +187,6 @@
+ #ifdef HAVE_SHMGET
+ /** what to put on statistics lines between var and value, ": " or "=" */
+ #define SQ "="
+-/** divide sum of timers to get average */
+-static void
+-timeval_divide(struct timeval* avg, const struct timeval* sum, long long d)
+-{
+-#ifndef S_SPLINT_S
+- size_t leftover;
+- if(d <= 0) {
+- avg->tv_sec = 0;
+- avg->tv_usec = 0;
+- return;
+- }
+- avg->tv_sec = sum->tv_sec / d;
+- avg->tv_usec = sum->tv_usec / d;
+- /* handle fraction from seconds divide */
+- leftover = sum->tv_sec - avg->tv_sec*d;
+- if(leftover <= 0)
+- leftover = 0;
+- avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
+- if(avg->tv_sec < 0)
+- avg->tv_sec = 0;
+- if(avg->tv_usec < 0)
+- avg->tv_usec = 0;
+-#endif
+-}
+-
+ /** print unsigned long stats value */
+ #define PR_UL_NM(str, var) printf("%s."str SQ"%lu\n", nm, (unsigned long)(var));
+ #define PR_UL(str, var) printf(str SQ"%lu\n", (unsigned long)(var));
+@@ -226,12 +202,20 @@
+ {
+ struct timeval sumwait, avg;
+ PR_UL_NM("num.queries", s->svr.num_queries);
+- PR_UL_NM("num.queries_ip_ratelimited",
++ PR_UL_NM("num.queries_ip_ratelimited",
+ s->svr.num_queries_ip_ratelimited);
++ PR_UL_NM("num.queries_cookie_valid",
++ s->svr.num_queries_cookie_valid);
++ PR_UL_NM("num.queries_cookie_client",
++ s->svr.num_queries_cookie_client);
++ PR_UL_NM("num.queries_cookie_invalid",
++ s->svr.num_queries_cookie_invalid);
+ PR_UL_NM("num.cachehits",
+ s->svr.num_queries - s->svr.num_queries_missed_cache);
+ PR_UL_NM("num.cachemiss", s->svr.num_queries_missed_cache);
+ PR_UL_NM("num.prefetch", s->svr.num_queries_prefetch);
++ PR_UL_NM("num.queries_timed_out", s->svr.num_queries_timed_out);
++ PR_UL_NM("query.queue_time_us.max", s->svr.max_query_time_us);
+ PR_UL_NM("num.expired", s->svr.ans_expired);
+ PR_UL_NM("num.recursivereplies", s->mesh_replies_sent);
+ #ifdef USE_DNSCRYPT
+@@ -403,6 +387,9 @@
+ PR_UL("rrset.cache.count", s->svr.rrset_cache_count);
+ PR_UL("infra.cache.count", s->svr.infra_cache_count);
+ PR_UL("key.cache.count", s->svr.key_cache_count);
++ /* max collisions */
++ PR_UL("msg.cache.max_collisions", s->svr.msg_cache_max_collisions);
++ PR_UL("rrset.cache.max_collisions", s->svr.rrset_cache_max_collisions);
+ /* applied RPZ actions */
+ for(i=0; i<UB_STATS_RPZ_ACTION_NUM; i++) {
+ if(i == RPZ_NO_OVERRIDE_ACTION)
+@@ -426,6 +413,9 @@
+ PR_UL("num.query.subnet", s->svr.num_query_subnet);
+ PR_UL("num.query.subnet_cache", s->svr.num_query_subnet_cache);
+ #endif
++#ifdef USE_CACHEDB
++ PR_UL("num.query.cachedb", s->svr.num_query_cachedb);
++#endif
+ }
+
+ /** print statistics out of memory structures */
+@@ -989,7 +979,7 @@
+ fatal_exit("could not exec unbound: %s",
+ strerror(ENOSYS));
+ #else
+- if(execlp("unbound", "unbound", "-c", cfgfile,
++ if(execlp("unbound", "unbound", "-c", cfgfile,
+ (char*)NULL) < 0) {
+ fatal_exit("could not exec unbound: %s",
+ strerror(errno));
+--- contrib/unbound/smallapp/unbound-host.c.orig
++++ contrib/unbound/smallapp/unbound-host.c
+@@ -482,6 +482,7 @@
+ case '?':
+ case 'h':
+ default:
++ ub_ctx_delete(ctx);
+ usage();
+ }
+ }
+@@ -495,8 +496,10 @@
+ }
+ argc -= optind;
+ argv += optind;
+- if(argc != 1)
++ if(argc != 1) {
++ ub_ctx_delete(ctx);
+ usage();
++ }
+
+ #ifdef HAVE_SSL
+ #ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
+--- /dev/null
++++ contrib/unbound/testdata/00-lint.tdir/00-lint.pre
+@@ -0,0 +1,14 @@
++# #-- 00-lint.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++. ../common.sh
++PRE="../.."
++
++if test -f $PRE/unbound_test_00-lint ; then
++ echo test enabled
++else
++ skip_test "test skipped; clang linter preferred over splint"
++fi
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_cached_ede.crpl
+@@ -0,0 +1,91 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: no
++ minimal-responses: no
++ module-config: "cachedb validator iterator"
++ trust-anchor-signaling: no
++ verbosity: 4
++ ede: yes
++ val-log-level: 2
++ trust-anchor: "example.nl. DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29B22446B1"
++
++
++cachedb:
++ backend: "testframe"
++ secret-seed: "testvalue"
++
++stub-zone:
++ name: "example.nl"
++ stub-addr: 193.0.14.129
++CONFIG_END
++
++SCENARIO_BEGIN Test cachedb support for caching EDEs.
++
++RANGE_BEGIN 0 10
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN DNSKEY
++SECTION ANSWER
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN A
++SECTION ANSWER
++example.nl. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; get the entry in cache.
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ FF FE ; option code = 65534 (LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST)
++ 00 00 ; option length
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; get the answer for it
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++; query again for the cached entry
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ FF FE ; option code = 65534 (LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST)
++ 00 00 ; option length
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; this must be a cached answer since stub is not answering in this range
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.conf
+@@ -0,0 +1,29 @@
++server:
++ verbosity: 4
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ module-config: "cachedb iterator"
++ do-not-query-localhost: no
++ qname-minimisation: no
++
++forward-zone:
++ name: "."
++ forward-addr: 127.0.0.1@@TOPORT@
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 127.0.0.1@@TOPORT@
++
++remote-control:
++ control-enable: yes
++ control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
++ control-use-cert: no
++
++cachedb:
++ backend: "testframe"
++ secret-seed: "testvalue"
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.dsc
+@@ -0,0 +1,16 @@
++BaseName: cachedb_no_store
++Version: 1.0
++Description: cachedb test the cachedb-no-store option
++CreationDate: Wed 11 Oct 11:00:00 CEST 2023
++Maintainer: dr. W.C.A. Wijngaards
++Category:
++Component:
++CmdDepends:
++Depends:
++Help:
++Pre: cachedb_no_store.pre
++Post: cachedb_no_store.post
++Test: cachedb_no_store.test
++AuxFiles:
++Passed:
++Failure:
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.post
+@@ -0,0 +1,20 @@
++# #-- cachedb_no_store.post --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# source the test var file when it's there
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++#
++# do your teardown here
++PRE="../.."
++. ../common.sh
++
++echo "> cat logfiles"
++cat fwd.log
++if test -f fwd2.log; then cat fwd2.log; else echo "no fwd2.log"; fi
++if test -f fwd3.log; then cat fwd3.log; else echo "no fwd3.log"; fi
++if test -f fwd4.log; then cat fwd4.log; else echo "no fwd4.log"; fi
++cat unbound.log
++if test -f unbound2.log; then cat unbound2.log; else echo "no unbound2.log"; fi
++kill_pid $FWD_PID
++kill_pid `cat unbound.pid`
++rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.pre
+@@ -0,0 +1,36 @@
++# #-- cachedb_no_store.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++if grep "define USE_CACHEDB 1" $PRE/config.h; then echo test enabled; else skip_test "test skipped"; fi
++
++get_random_port 2
++UNBOUND_PORT=$RND_PORT
++FWD_PORT=$(($RND_PORT + 1))
++echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
++echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
++
++# start forwarder
++get_ldns_testns
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.testns >fwd.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++
++# make config file
++CONTROL_PATH=/tmp
++CONTROL_PID=$$
++sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < cachedb_no_store.conf > ub.conf
++# start unbound in the background
++$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
++echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
++
++cat .tpkg.var.test
++wait_ldns_testns_up fwd.log
++wait_unbound_up unbound.log
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.servfail.testns
+@@ -0,0 +1,8 @@
++ENTRY_BEGIN
++MATCH opcode
++ADJUST copy_id copy_query
++REPLY QR AA SERVFAIL
++SECTION QUESTION
++txt1.example.com. IN TXT
++SECTION ANSWER
++ENTRY_END
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.test
+@@ -0,0 +1,132 @@
++# #-- cachedb_no_store.test --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++
++# do the test
++get_ldns_testns
++
++# query for a text record that is stored by unbound's cache and cachedb
++# in the testframe cache.
++echo "> dig txt1.example.com."
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# stop the forwarder with servfail, to check the answer came from the cache
++echo "> stop ldns-testns"
++kill_pid $FWD_PID
++echo "> start ldns-testns with servfails"
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.servfail.testns >fwd2.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++wait_ldns_testns_up fwd2.log
++
++echo "> dig txt1.example.com. from unbound cache"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# clear the cache of unbound, but not cachedb testframe cache
++echo "> unbound-control flush"
++$PRE/unbound-control -c ub.conf flush_type txt1.example.com. TXT
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++else
++ echo "exit value: OK"
++fi
++
++echo "> dig txt1.example.com. from cachedb"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# start the forwarder again.
++echo "> stop ldns-testns"
++kill_pid $FWD_PID
++echo "> start ldns-testns"
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.testns >fwd3.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++wait_ldns_testns_up fwd3.log
++
++# stop unbound to flush the cachedb cache
++echo "> stop unbound"
++kill_pid `cat unbound.pid`
++
++echo ""
++echo "> config unbound with cachedb-no-store: yes"
++echo "cachedb: cachedb-no-store: yes" >> ub.conf
++
++# start unbound again.
++echo "> start unbound"
++$PRE/unbound -d -c ub.conf >unbound2.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++wait_unbound_up unbound2.log
++
++echo ""
++echo "> dig txt1.example.com."
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# stop the forwarder with servfail, to check the answer came from the cache
++echo "> stop ldns-testns"
++kill_pid $FWD_PID
++echo "> start ldns-testns with servfails"
++$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.servfail.testns >fwd4.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++wait_ldns_testns_up fwd4.log
++
++echo "> dig txt1.example.com. from unbound cache"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "example text message" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++# clear the cache of unbound, but not cachedb testframe cache
++echo "> unbound-control flush"
++$PRE/unbound-control -c ub.conf flush_type txt1.example.com. TXT
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++else
++ echo "exit value: OK"
++fi
++
++echo "> dig txt1.example.com. from cachedb, but that has no message stored"
++dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
++if grep "SERVFAIL" outfile; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++exit 0
+--- /dev/null
++++ contrib/unbound/testdata/cachedb_no_store.tdir/cachedb_no_store.testns
+@@ -0,0 +1,9 @@
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++txt1.example.com. IN TXT
++SECTION ANSWER
++txt1.example.com. IN TXT "example text message"
++ENTRY_END
+--- /dev/null
++++ contrib/unbound/testdata/disable_edns_do.rpl
+@@ -0,0 +1,164 @@
++; config options
++; The island of trust is at example.com
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ trust-anchor-signaling: no
++ minimal-responses: no
++ disable-edns-do: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test lookup with disable-edns-do
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; 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.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response to query of interest, when sent with EDNS DO
++ENTRY_BEGIN
++MATCH opcode qtype qname DO
++ADJUST copy_id
++REPLY QR AA DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN A 10.20.30.40
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
++ENTRY_END
++
++; response to query of interest, when sent without DO
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA 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 DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; recursion happens here.
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/edns_downstream_cookies.rpl
+@@ -0,0 +1,235 @@
++; config options
++server:
++ answer-cookie: yes
++ cookie-secret: "000102030405060708090a0b0c0d0e0f"
++ access-control: 127.0.0.1 allow_cookie
++ access-control: 1.2.3.4 allow
++ local-data: "test. TXT test"
++
++CONFIG_END
++
++SCENARIO_BEGIN Test downstream DNS Cookies
++
++; Note: When a valid hash was required, it was generated by running this test
++; with an invalid one and checking the output for the valid one.
++; Actual hash generation is tested with unit tests.
++
++; Query without a client cookie ...
++STEP 0 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++; ... get TC and refused
++STEP 1 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA TC REFUSED
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query without a client cookie on TCP ...
++STEP 10 QUERY
++ENTRY_BEGIN
++REPLY RD
++MATCH TCP
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++; ... get an answer
++STEP 11 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with only a client cookie ...
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 08 ; Length 8
++ 31 32 33 34 35 36 37 38 ; Random bits
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get BADCOOKIE and a new cookie
++STEP 21 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query with an invalid cookie ...
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 02 00 00 00 ; wrong version
++ 00 00 00 00 ; Timestamp
++ 31 32 33 34 35 36 37 38 ; wrong hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get BADCOOKIE and a new cookie
++STEP 31 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query with an invalid cookie from a non-cookie protected address ...
++STEP 40 QUERY ADDRESS 1.2.3.4
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 02 00 00 00 ; wrong version
++ 00 00 00 00 ; Timestamp
++ 31 32 33 34 35 36 37 38 ; wrong hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get answer and a cookie
++STEP 41 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with a valid cookie ...
++STEP 50 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 00 00 ; Timestamp
++ 38 52 7b a8 c6 a4 ea 96 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get answer and the cookie
++STEP 51 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with a valid >30 minutes old cookie ...
++STEP 59 TIME_PASSES ELAPSE 1801
++STEP 60 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 00 00 ; Timestamp
++ 38 52 7b a8 c6 a4 ea 96 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... Get answer and a refreshed cookie
++; (we don't check the re-freshness here; it has its own unit test)
++STEP 61 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++; Query with a hash-valid >60 minutes old cookie ...
++STEP 69 TIME_PASSES ELAPSE 3601
++STEP 70 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 07 09 ; Timestamp (1801)
++ 77 81 38 e3 8f aa 72 86 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get BADCOOKIE and a new cookie
++STEP 71 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
++SECTION QUESTION
++test. IN TXT
++ENTRY_END
++
++; Query with a valid future (<5 minutes) cookie ...
++STEP 80 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test. IN TXT
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 0a ; Opcode 10
++ 00 18 ; Length 24
++ 31 32 33 34 35 36 37 38 ; Random bits
++ 01 00 00 00 ; Version/Reserved
++ 00 00 16 45 ; Timestamp (1801 + 3601 + 299)
++ 4a f5 0f df f0 e8 c7 09 ; Hash
++HEX_EDNSDATA_END
++ENTRY_END
++; ... get an answer
++STEP 81 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all server_cookie
++REPLY QR RD RA AA DO NOERROR
++SECTION QUESTION
++test. IN TXT
++SECTION ANSWER
++test. IN TXT "test"
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.conf
+@@ -0,0 +1,28 @@
++server:
++ verbosity: 5
++ # num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: .
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ local-data: "test. IN TXT localdata"
++
++ ip-ratelimit: 1
++ ip-ratelimit-cookie: 0
++ ip-ratelimit-factor: 0
++ ip-ratelimit-backoff: yes
++ answer-cookie: yes
++ access-control: 127.0.0.0/8 allow_cookie
++
++remote-control:
++ control-enable: yes
++ control-interface: 127.0.0.1
++ # control-interface: ::1
++ control-port: @CONTROL_PORT@
++ server-key-file: "unbound_server.key"
++ server-cert-file: "unbound_server.pem"
++ control-key-file: "unbound_control.key"
++ control-cert-file: "unbound_control.pem"
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.dsc
+@@ -0,0 +1,16 @@
++BaseName: ip_ratelimit
++Version: 1.0
++Description: Test IP source ratelimit.
++CreationDate: Tue Aug 8 00:00:00 CET 2023
++Maintainer: Yorgos Thessalonikefs
++Category:
++Component:
++CmdDepends:
++Depends:
++Help:
++Pre: ip_ratelimit.pre
++Post: ip_ratelimit.post
++Test: ip_ratelimit.test
++AuxFiles:
++Passed:
++Failure:
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.post
+@@ -0,0 +1,13 @@
++# #-- ip_ratelimit.post --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# source the test var file when it's there
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++#
++# do your teardown here
++. ../common.sh
++kill_pid $UNBOUND_PID
++if test -f unbound.log; then
++ echo ">>> unbound log"
++ cat unbound.log
++fi
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.pre
+@@ -0,0 +1,24 @@
++# #-- ip_ratelimit.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++get_random_port 2
++UNBOUND_PORT=$RND_PORT
++CONTROL_PORT=$(($RND_PORT + 1))
++echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
++echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test
++
++# make config file
++sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < ip_ratelimit.conf > ub.conf
++# start unbound in the background
++$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++
++wait_unbound_up unbound.log
++
++cat .tpkg.var.test
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/ip_ratelimit.test
+@@ -0,0 +1,165 @@
++# #-- ip_ratelimit.test --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++. ../common.sh
++
++get_make
++(cd $PRE; $MAKE streamtcp)
++
++# These tests rely on second time precision. To combat false negatives the
++# tests run multiple times and we allow 1/3 of the runs to fail.
++total_runs=6
++success_threshold=4 # 2/3*total_runs
++
++if dig -h 2>&1 | grep "cookie" >/dev/null; then
++ nocookie="+nocookie"
++else
++ nocookie=""
++fi
++
++echo "> First get a valid cookie"
++dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:0102030405060708 $nocookie +tcp +retry=0 +time=1 test. TXT >outfile 2>&1
++if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++fi
++if test `grep "COOKIE: " outfile | wc -l` -ne 1; then
++ echo "Could not get cookie"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++fi
++cookie=`grep "COOKIE: " outfile | cut -d ' ' -f 3`
++
++successes=0
++echo "> Three parallel queries with backoff and cookie"
++# For this test we send three parallel queries. The ratelimit should be reached
++# for that second. We send a query to verify that there is no reply.
++# Then for the next second we again send three parallel queries and we expect
++# none of them to be allowed through because of the backoff logic that keeps
++# rolling the RATE_WINDOW based on demand.
++# Again we send another query but with a valid cookie and we expect to receive
++# an answer.
++for i in $(seq 1 $total_runs); do
++ # Try to hit limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect no answer because of limit
++ dig @127.0.0.1 -p $UNBOUND_PORT $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -eq 0; then
++ continue
++ fi
++ # Try to keep limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect answer because of DNS cookie
++ dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$cookie $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -ne 0; then
++ continue
++ fi
++ ((successes++))
++ # We don't have to wait for all the runs to complete if we know
++ # we passed the threshold.
++ if test $successes -ge $success_threshold; then
++ break
++ fi
++done
++
++if test $successes -ge $success_threshold; then
++ echo "Three parallel queries with backoff and cookie OK"
++else
++ echo "Three parallel queries with backoff and cookie NOT OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Three parallel queries with backoff and cookie NOT OK"
++ exit 1
++fi
++
++echo "> Activating ip-ratelimit-cookie"
++echo "$PRE/unbound-control -c ub.conf set_option ip-ratelimit-cookie: 1"
++$PRE/unbound-control -c ub.conf set_option ip-ratelimit-cookie: 1
++if test $? -ne 0; then
++ echo "wrong exit value after success"
++ exit 1
++fi
++
++successes=0
++echo "> Three parallel queries with backoff and cookie with ip-ratelimit-cookie"
++# This is the exact same test as above with the exception that we don't expect
++# an answer on the last query because ip-ratelimit-cookie is now enabled.
++for i in $(seq 1 $total_runs); do
++ # Try to hit limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect no answer because of limit
++ dig @127.0.0.1 -p $UNBOUND_PORT $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -eq 0; then
++ continue
++ fi
++ # Try to keep limit
++ $PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
++ if test "$?" -ne 0; then
++ echo "exit status not OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Not OK"
++ exit 1
++ fi
++ # Expect no answer because of ip-ratelimit-cookie
++ dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$cookie $nocookie +retry=0 +time=1 test. TXT >outfile 2>&1
++ if test "$?" -eq 0; then
++ continue
++ fi
++ ((successes++))
++ # We don't have to wait for all the runs to complete if we know
++ # we passed the threshold.
++ if test $successes -ge $success_threshold; then
++ break
++ fi
++done
++
++if test $successes -ge $success_threshold; then
++ echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie OK"
++else
++ echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie NOT OK"
++ echo "> cat logfiles"
++ cat outfile
++ cat unbound.log
++ echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie NOT OK"
++ exit 1
++fi
++
++exit 0
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_control.key
+@@ -0,0 +1,39 @@
++-----BEGIN RSA PRIVATE KEY-----
++MIIG4gIBAAKCAYEAstEp+Pyh8XGrtZ77A4FhYjvbeB3dMa7Q2rGWxobzlA9przhA
++1aChAvUtCOAuM+rB6NTNB8YWfZJbQHawyMNpmC77cg6vXLYCGUQHZyAqidN049RJ
++F5T7j4N8Vniv17LiRdr0S6swy4PRvEnIPPV43EQHZqC5jVvHsKkhIfmBF/Dj5TXR
++ypeawWV/m5jeU6/4HRYMfytBZdO1mPXuWLh0lgbQ4SCbgrOUVD3rniMk1yZIbQOm
++vlDHYqekjDb/vOW2KxUQLG04aZMJ1mWfdbwG0CKQkSjISEDZ1l76vhM6mTM0fwXb
++IvyFZ9yPPCle1mF5aSlxS2cmGuGVSRQaw8XF9fe3a9ACJJTr33HdSpyaZkKRAUzL
++cKqLCl323daKv3NwwAT03Tj4iQM416ASMoiyfFa/2GWTKQVjddu8Crar7tGaf5xr
++lig4DBmrBvdYA3njy72/RD71hLwmlRoCGU7dRuDr9O6KASUm1Ri91ONZ/qdjMvov
++15l2vj4GV+KXR00dAgMBAAECggGAHepIL1N0dEQkCdpy+/8lH54L9WhpnOo2HqAf
++LU9eaKK7d4jdr9+TkD8cLaPzltPrZNxVALvu/0sA4SP6J1wpyj/x6P7z73qzly5+
++Xo5PD4fEwmi9YaiW/UduAblnEZrnp/AddptJKoL/D5T4XtpiQddPtael4zQ7kB57
++YIexRSQTvEDovA/o3/nvA0TrzOxfgd4ycQP3iOWGN/TMzyLsvjydrUwbOB567iz9
++whL3Etdgvnwh5Sz2blbFfH+nAR8ctvFFz+osPvuIVR21VMEI6wm7kTpSNnQ6sh/c
++lrLb/bTADn4g7z/LpIZJ+MrLvyEcoqValrLYeFBhM9CV8woPxvkO2P3pU47HVGax
++tC7GV6a/kt5RoKFd/TNdiA3OC7NGZtaeXv9VkPf4fVwBtSO9d5ZZXTGEynDD/rUQ
++U4KFJe6OD23APjse08HiiKqTPhsOneOONU67iqoaTdIkT2R4EdlkVEDpXVtWb+G9
++Q+IqYzVljlzuyHrhWXLJw/FMa2aBAoHBAOnZbi4gGpH+P6886WDWVgIlTccuXoyc
++Mg9QQYk9UDeXxL0AizR5bZy49Sduegz9vkHpAiZARQsUnizHjZ8YlRcrmn4t6tx3
++ahTIKAjdprnxJfYINM580j8CGbXvX5LhIlm3O267D0Op+co3+7Ujy+cjsIuFQrP+
++1MqMgXSeBjzC1APivmps7HeFE+4w0k2PfN5wSMDNCzLo99PZuUG5XZ93OVOS5dpN
++b+WskdcD8NOoJy/X/5A08veEI/jYO/DyqQKBwQDDwUQCOWf41ecvJLtBHKmEnHDz
++ftzHino9DRKG8a9XaN4rmetnoWEaM2vHGX3pf3mwH+dAe8vJdAQueDhBKYeEpm6C
++TYNOpou1+Zs5s99BilCTNYo8fkMOAyqwRwmz9zgHS6QxXuPwsghKefLJGt6o6RFF
++tfWVTfLlYJ+I3GQe3ySsk3wjVz4oUTKiyiq5+KzD+HhEkS7u+RQ7Z0ZI2xd2cF8Y
++aN2hjKDpcOiFf3CDoqka5D1qMNLgIHO52AHww1UCgcA1h7o7AMpURRka6hyaODY0
++A4oMYEbwdQjYjIyT998W+rzkbu1us6UtzQEBZ760npkgyU/epbOoV63lnkCC/MOU
++LD0PST+L/CHiY/cWIHb79YG1EifUZKpUFg0Aoq0EGFkepF0MefGCkbRGYA5UZr9U
++R80wAu9D+L+JJiS0J0BSRF74DL196zUuHt5zFeXuLzxsRtPAnq9DliS08BACRYZy
++7H3I7cWD9Vn5/0jbKWHFcaaWwyETR6uekTcSzZzbCRECgcBeoE3/xUA9SSk34Mmj
++7/cB4522Ft0imA3+9RK/qJTZ7Bd5fC4PKjOGNtUiqW/0L2rjeIiQ40bfWvWqgPKw
++jSK1PL6uvkl6+4cNsFsYyZpiVDoe7wKju2UuoNlB3RUTqa2r2STFuNj2wRjA57I1
++BIgdnox65jqQsd14g/yaa+75/WP9CE45xzKEyrtvdcqxm0Pod3OrsYK+gikFjiar
++kT0GQ8u0QPzh2tjt/2ZnIfOBrl+QYERP0MofDZDjhUdq2wECgcB0Lu841+yP5cdR
++qbJhXO4zJNh7oWNcJlOuQp3ZMNFrA1oHpe9pmLukiROOy01k9WxIMQDzU5GSqRv3
++VLkYOIcbhJ3kClKAcM3j95SkKbU2H5/RENb3Ck52xtl4pNU1x/3PnVFZfDVuuHO9
++MZ9YBcIeK98MyP2jr5JtFKnOyPE7xKq0IHIhXadpbc2wjje5FtZ1cUtMyEECCXNa
++C1TpXebHGyXGpY9WdWXhjdE/1jPvfS+uO5WyuDpYPr339gsdq1g=
++-----END RSA PRIVATE KEY-----
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_control.pem
+@@ -0,0 +1,22 @@
++-----BEGIN CERTIFICATE-----
++MIIDszCCAhsCFGD5193whHQ2bVdzbaQfdf1gc4SkMA0GCSqGSIb3DQEBCwUAMBIx
++EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjMwWhcNNDAwMzI1MTMzMjMw
++WjAaMRgwFgYDVQQDDA91bmJvdW5kLWNvbnRyb2wwggGiMA0GCSqGSIb3DQEBAQUA
++A4IBjwAwggGKAoIBgQCy0Sn4/KHxcau1nvsDgWFiO9t4Hd0xrtDasZbGhvOUD2mv
++OEDVoKEC9S0I4C4z6sHo1M0HxhZ9kltAdrDIw2mYLvtyDq9ctgIZRAdnICqJ03Tj
++1EkXlPuPg3xWeK/XsuJF2vRLqzDLg9G8Scg89XjcRAdmoLmNW8ewqSEh+YEX8OPl
++NdHKl5rBZX+bmN5Tr/gdFgx/K0Fl07WY9e5YuHSWBtDhIJuCs5RUPeueIyTXJkht
++A6a+UMdip6SMNv+85bYrFRAsbThpkwnWZZ91vAbQIpCRKMhIQNnWXvq+EzqZMzR/
++Bdsi/IVn3I88KV7WYXlpKXFLZyYa4ZVJFBrDxcX197dr0AIklOvfcd1KnJpmQpEB
++TMtwqosKXfbd1oq/c3DABPTdOPiJAzjXoBIyiLJ8Vr/YZZMpBWN127wKtqvu0Zp/
++nGuWKDgMGasG91gDeePLvb9EPvWEvCaVGgIZTt1G4Ov07ooBJSbVGL3U41n+p2My
+++i/XmXa+PgZX4pdHTR0CAwEAATANBgkqhkiG9w0BAQsFAAOCAYEAd++Wen6l8Ifj
++4h3p/y16PhSsWJWuJ4wdNYy3/GM84S26wGjzlEEwiW76HpH6VJzPOiBAeWnFKE83
++hFyetEIxgJeIPbcs9ZP/Uoh8GZH9tRISBSN9Hgk2Slr9llo4t1H0g/XTgA5HqMQU
++9YydlBh43G7Vw3FVwh09OM6poNOGQKNc/tq2/QdKeUMtyBbLWpRmjH5XcCT35fbn
++ZiVOUldqSHD4kKrFO4nJYXZyipRbcXybsLiX9GP0GLemc3IgIvOXyJ2RPp06o/SJ
++pzlMlkcAfLJaSuEW57xRakhuNK7m051TKKzJzIEX+NFYOVdafFHS8VwGrYsdrFvD
++72tMfu+Fu55y3awdWWGc6YlaGogZiuMnJkvQphwgn+5qE/7CGEckoKEsH601rqIZ
++muaIc85+nEcHJeijd/ZlBN9zeltjFoMuqTUENgmv8+tUAdVm/UMY9Vjme6b43ydP
++uv6DS02+k9z8toxXworLiPr94BGaiGV1NxgwZKLZigYJt/Fi2Qte
++-----END CERTIFICATE-----
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_server.key
+@@ -0,0 +1,39 @@
++-----BEGIN RSA PRIVATE KEY-----
++MIIG5AIBAAKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI
++0x41iG32a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+Nqq
++GRS7XVQ24vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Z
++uh9MDgotaBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8K
++WaBe1ca4TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5
++FzUReSXZuTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xP
++q6O9UPj4+nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XL
++A5UoZgRzXgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP
++7kFZSngxdy1+A/bNAgMBAAECggGBALpTOIqQwVg4CFBylL/a8K1IWJTI/I65sklf
++XxYL7G7SB2HlEJ//z+E+F0+S4Vlao1vyLQ5QkgE82pAUB8FoMWvY1qF0Y8A5wtm6
++iZSGk4OLK488ZbT8Ii9i+AGKgPe2XbVxsJwj8N4k7Zooqec9hz73Up8ATEWJkRz7
++2u7oMGG4z91E0PULA64dOi3l/vOQe5w/Aa+CwVbAWtI05o7kMvQEBMDJn6C7CByo
++MB5op9wueJMnz7PM7hns+U7Dy6oE4ljuolJUy51bDzFWwoM54cRoQqLFNHd8JVQj
++WxldCkbfF43iyprlsEcUrTyUjtdA+ZeiG39vg/mtdmgNpGmdupHJZQvSuG8IcVlz
++O+eMSeQS1QXPD6Ik8UK4SU0h+zOl8xIWtRrsxQuh4fnTN40udm/YUWl/6gOebsBI
++IrVLlKGqJSfB3tMjpCRqdTzJ0dA9keVpkqm2ugZkxEf1+/efq/rFIQ2pUBLCqNTN
++qpNqruK8y8FphP30I2uI4Ej2UIB8AQKBwQDd2Yptj2FyDyaXCycsyde0wYkNyzGU
++dRnzdibfHnMZwjgTjwAwgIUBVIS8H0/z7ZJQKN7osJfddMrtjJtYYUk9g/dCpHXs
++bNh2QSoWah3FdzNGuWd0iRf9+LFxhjAAMo/FS8zFJAJKrFsBdCGTfFUMdsLC0bjr
++YjiWBuvV72uKf8XIZX5KIZruKdWBBcWukcb21R1UDyFYyXRBsly5XHaIYKZql3km
++7pV7MKWO0IYgHbHIqGUqPQlzZ/lkunS1jKECgcEA23wHffD6Ou9/x3okPx2AWpTr
++gh8rgqbyo6hQkBW5Y90Wz824cqaYebZDaBR/xlVx/YwjKkohv8Bde2lpH/ZxRZ1Z
++5Sk2s6GJ/vU0L9RsJZgCgj4L6Coal1NMxuZtCXAlnOpiCdxSZgfqbshbTVz30KsG
++ZJG361Cua1ScdAHxlZBxT52/1Sm0zRC2hnxL7h4qo7Idmtzs40LAJvYOKekR0pPN
++oWeJfra7vgx/jVNvMFWoOoSLpidVO4g+ot4ery6tAoHAdW3rCic1C2zdnmH28Iw+
++s50l8Lk3mz+I5wgJd1zkzCO0DxZIoWPGA3g7cmCYr6N3KRsZMs4W9NAXgjpFGDkW
++zYsG3K21BdpvkdjYcFjnPVjlOXB2RIc0vehf9Jl02wXoeCSxVUDEPcaRvWk9RJYx
++ZpGOchUU7vNkxHURbIJ4yCzuAi9G8/Jp0dsu+kaV5tufF5SjG5WOrzKjaQsCbdN1
++oqaWMCHRrTvov/Z2C+xwsptFOdN5CSyZzg6hQiI4GMlBAoHAXyb6KINcOEi0YMp3
++BFXJ23tMTnEs78tozcKeipigcsbaqORK3omS+NEnj+uzKUzJyl4CsMbKstK2tFYS
++mSTCHqgE3PBtIpsZtEqhgUraR8IK9GPpzZDTTl9ynZgwFTNlWw3RyuyVXF56J+T8
++kCGJ3hEHCHqT/ZRQyX85BKIDFhA0z4tYKxWVqIFiYBNq56R0X9tMMmMs36mEnF93
++7Ht6mowxTZQRa7nU0qOgeKh/P7ki4Zus3y+WJ+T9IqahLtlRAoHBAIhqMrcxSAB8
++RpB9jukJlAnidw2jCMPgrFE8tP0khhVvGrXMldxAUsMKntDIo8dGCnG1KTcWDI0O
++jepvSPHSsxVLFugL79h0eVIS5z4huW48i9xgU8VlHdgAcgEPIAOFcOw2BCu/s0Vp
++O+MM/EyUOdo3NsibB3qc/GJI6iNBYS7AljYEVo6rXo5V/MZvZUF4vClen6Obzsre
++MTTb+4sJjfqleWuvr1XNMeu2mBfXBQkWGZP1byBK0MvD/aQ2PWq92A==
++-----END RSA PRIVATE KEY-----
+--- /dev/null
++++ contrib/unbound/testdata/ip_ratelimit.tdir/unbound_server.pem
+@@ -0,0 +1,22 @@
++-----BEGIN CERTIFICATE-----
++MIIDqzCCAhMCFBHWXeQ6ZIa9QcQbXLFfC6tj+KA+MA0GCSqGSIb3DQEBCwUAMBIx
++EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjI5WhcNNDAwMzI1MTMzMjI5
++WjASMRAwDgYDVQQDDAd1bmJvdW5kMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
++igKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI0x41iG32
++a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+NqqGRS7XVQ2
++4vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Zuh9MDgot
++aBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8KWaBe1ca4
++TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5FzUReSXZ
++uTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xPq6O9UPj4
+++nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XLA5UoZgRz
++XgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP7kFZSngx
++dy1+A/bNAgMBAAEwDQYJKoZIhvcNAQELBQADggGBABunf93MKaCUHiZgnoOTinsW
++84/EgInrgtKzAyH+BhnKkJOhhR0kkIAx5d9BpDlaSiRTACFon9moWCgDIIsK/Ar7
++JE0Kln9cV//wiiNoFU0O4mnzyGUIMvlaEX6QHMJJQYvL05+w/3AAcf5XmMJtR5ca
++fJ8FqvGC34b2WxX9lTQoyT52sRt+1KnQikiMEnEyAdKktMG+MwKsFDdOwDXyZhZg
++XZhRrfX3/NVJolqB6EahjWIGXDeKuSSKZVtCyib6LskyeMzN5lcRfvubKDdlqFVF
++qlD7rHBsKhQUWK/IO64mGf7y/de+CgHtED5vDvr/p2uj/9sABATfbrOQR3W/Of25
++sLBj4OEfrJ7lX8hQgFaxkMI3x6VFT3W8dTCp7xnQgb6bgROWB5fNEZ9jk/gjSRmD
++yIU+r0UbKe5kBk/CmZVFXL2TyJ92V5NYEQh8V4DGy19qZ6u/XKYyNJL4ocs35GGe
++CA8SBuyrmdhx38h1RHErR2Skzadi1S7MwGf1y431fQ==
++-----END CERTIFICATE-----
+--- /dev/null
++++ contrib/unbound/testdata/iter_cname_minimise_nx.rpl
+@@ -0,0 +1,246 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: yes
++ module-config: "validator iterator"
++ trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b"
++ val-override-date: "20070916134226"
++ fake-sha1: yes
++ trust-anchor-signaling: no
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test cname chain resolution of nxdomain with qname minimisation.
++; the qtype CNAME lookup has NXDOMAIN.
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.44
++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.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++ns.example.com. IN A
++SECTION ANSWER
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++ns.example.com. IN AAAA
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.44
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. AAZrcta3WCyz0iq2p78gmcPpXbmXPP9nQXM/czH1R9ilCaEoV8E27UU=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NXDOMAIN
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. 300 IN SOA a. b. 1 2 3 4 300
++example.com. 300 IN RRSIG SOA 3 2 300 20070926134150 20070829134150 2854 example.com. AFPx1ZhcHixnxfB90ha4zgp7A+EdM8L63tUnVdlI5B14NiRIXONPDB4=
++v.example.com. IN NSEC x.example.com. A AAAA RRSIG NSEC
++v.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. AFT0Ao01lUN8Ppa9QPayQIN9ZtNIj4TzyhUQV31+FhNRK5uSQhiVwMc=
++example.com. 3600 IN NSEC abc.example.com. NS SOA RRSIG NSEC DNSKEY
++example.com. 3600 IN RRSIG NSEC 3 2 3600 20070926134150 20070829134150 2854 example.com. ABEOu6iietfjKY1MS0TutZZxUtRYA6XKsC1rMTrenwBF2darY3/Emco=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NXDOMAIN
++SECTION QUESTION
++c.example.com. IN A
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++SECTION AUTHORITY
++example.com. 300 IN SOA a. b. 1 2 3 4 300
++example.com. 300 IN RRSIG SOA 3 2 300 20070926134150 20070829134150 2854 example.com. AFPx1ZhcHixnxfB90ha4zgp7A+EdM8L63tUnVdlI5B14NiRIXONPDB4=
++v.example.com. IN NSEC x.example.com. A AAAA RRSIG NSEC
++v.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. AFT0Ao01lUN8Ppa9QPayQIN9ZtNIj4TzyhUQV31+FhNRK5uSQhiVwMc=
++example.com. 3600 IN NSEC abc.example.com. NS SOA RRSIG NSEC DNSKEY
++example.com. 3600 IN RRSIG NSEC 3 2 3600 20070926134150 20070829134150 2854 example.com. ABEOu6iietfjKY1MS0TutZZxUtRYA6XKsC1rMTrenwBF2darY3/Emco=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++c.example.com. IN CNAME
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++c.example.com. IN CNAME
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++c.example.com. IN CNAME
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++ENTRY_END
++
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++c.example.com. IN CNAME
++ENTRY_END
++
++STEP 40 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++c.example.com. IN CNAME
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++ENTRY_END
++
++STEP 50 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++c.example.com. IN A
++ENTRY_END
++
++STEP 60 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NXDOMAIN
++SECTION QUESTION
++c.example.com. IN A
++SECTION ANSWER
++c.example.com. 10 IN CNAME www.example.com.
++c.example.com. 10 IN RRSIG CNAME 3 3 10 20070926134150 20070829134150 2854 example.com. ABT7twnK5qkCBKnaOHxFthUOK+3rBge1wEMItoFPdf16OoVdfccYU2U=
++SECTION AUTHORITY
++example.com. 300 IN SOA a. b. 1 2 3 4 300
++example.com. 300 IN RRSIG SOA 3 2 300 20070926134150 20070829134150 2854 example.com. AFPx1ZhcHixnxfB90ha4zgp7A+EdM8L63tUnVdlI5B14NiRIXONPDB4=
++v.example.com. IN NSEC x.example.com. A AAAA RRSIG NSEC
++v.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. AFT0Ao01lUN8Ppa9QPayQIN9ZtNIj4TzyhUQV31+FhNRK5uSQhiVwMc=
++example.com. 3600 IN NSEC abc.example.com. NS SOA RRSIG NSEC DNSKEY
++example.com. 3600 IN RRSIG NSEC 3 2 3600 20070926134150 20070829134150 2854 example.com. ABEOu6iietfjKY1MS0TutZZxUtRYA6XKsC1rMTrenwBF2darY3/Emco=
++ENTRY_END
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_failreply.rpl
+@@ -0,0 +1,132 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ minimal-responses: no
++ log-servfail: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test iterator fail_reply report
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. IN NS ns2.example.net.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. IN AAAA ::1
++ns2.example.net. IN AAAA ::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example.net. IN A
++SECTION ANSWER
++ns2.example.net. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example.net. IN AAAA
++SECTION ANSWER
++ns2.example.net. IN AAAA ::1
++ENTRY_END
++
++RANGE_END
++
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.4
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR SERVFAIL
++SECTION QUESTION
++ns.example.com. IN A
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR SERVFAIL
++SECTION QUESTION
++ns.example.com. IN AAAA
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 20 CHECK_OUT_QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 21 TIMEOUT
++STEP 22 TIMEOUT
++STEP 23 TIMEOUT
++STEP 24 TIMEOUT
++STEP 25 TIMEOUT
++
++STEP 31 TIMEOUT
++STEP 32 TIMEOUT
++STEP 33 TIMEOUT
++STEP 34 TIMEOUT
++
++; recursion happens here.
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_ignore_empty.rpl
+@@ -0,0 +1,248 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ minimal-responses: no
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test ignore of an empty response.
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. IN NS ns2.example2.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example2.com. IN NS
++SECTION AUTHORITY
++example2.com. IN NS ns2.example2.com.
++SECTION ADDITIONAL
++ns2.example2.com. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++foo.com. IN NS
++SECTION AUTHORITY
++foo.com. IN NS ns.foo.com.
++SECTION ADDITIONAL
++ns.foo.com. IN A 1.2.3.5
++ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.4
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION ANSWER
++example.com. IN NS ns.example.com.
++example.com. IN NS ns2.example.net.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.example.com. IN A
++SECTION ANSWER
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.example.com. IN AAAA
++SECTION AUTHORITY
++example.com. IN SOA ns root 4 14400 3600 604800 3600
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++SECTION AUTHORITY
++SECTION ADDITIONAL
++ENTRY_END
++RANGE_END
++
++; ns2.example2.com.
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.5
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example2.com. IN NS
++SECTION ANSWER
++example2.com. IN NS ns2.example2.com.
++SECTION ADDITIONAL
++ns2.example2.com. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example2.com. IN A
++SECTION ANSWER
++ns2.example2.com. IN A 1.2.3.5
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns2.example2.com. IN AAAA
++SECTION AUTHORITY
++example2.com. IN SOA ns2 root 4 14400 3600 604800 3600
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN A 10.20.30.40
++ENTRY_END
++
++; foo.com
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++www.foo.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.foo.com. IN AAAA
++SECTION ANSWER
++SECTION AUTHORITY
++;foo.com. IN SOA ns2.foo.com root.foo.com 4 14400 3600 604800 3600
++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 all
++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
++
++; wait for pending nameserver lookups.
++STEP 20 TRAFFIC
++
++; Test that a nodata stays a nodata.
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.foo.com. IN A
++ENTRY_END
++
++STEP 40 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.foo.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_nat64.rpl
+@@ -0,0 +1,117 @@
++; config options
++server:
++ do-nat64: yes
++ target-fetch-policy: "0 0 0 0 0"
++
++stub-zone:
++ name: "."
++ stub-addr: 2001:db8::1
++CONFIG_END
++
++SCENARIO_BEGIN Test NAT64 transport for a v4-only server.
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8::1
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS FAKE.ROOT.
++SECTION ADDITIONAL
++FAKE.ROOT. IN AAAA 2001:db8::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++; replies from NS over "NAT64"
++
++RANGE_BEGIN 0 100
++ ADDRESS 64:ff9b::c000:0201
++
++; A over NAT64
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN A
++SECTION ANSWER
++ns.v4only. IN A 192.0.2.1
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++ENTRY_END
++
++; no AAAA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN AAAA
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION ANSWER
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test.v4only. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_nat64_prefix.rpl
+@@ -0,0 +1,119 @@
++; config options
++server:
++ do-nat64: yes
++ nat64-prefix: 2001:db8:1234::/96
++ target-fetch-policy: "0 0 0 0 0"
++ do-ip4: no
++
++stub-zone:
++ name: "."
++ stub-addr: 2001:db8::1
++CONFIG_END
++
++SCENARIO_BEGIN Test NAT64 transport for a v4-only server, custom NAT64 prefix.
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8::1
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS FAKE.ROOT.
++SECTION ADDITIONAL
++FAKE.ROOT. IN AAAA 2001:db8::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++; replies from NS over "NAT64"
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8:1234::c000:0201
++
++; A over NAT64
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN A
++SECTION ANSWER
++ns.v4only. IN A 192.0.2.1
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++ENTRY_END
++
++; no AAAA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN AAAA
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION ANSWER
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test.v4only. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_nat64_prefix48.rpl
+@@ -0,0 +1,118 @@
++; config options
++server:
++ do-nat64: yes
++ nat64-prefix: 2001:db8:2345::/48
++ target-fetch-policy: "0 0 0 0 0"
++
++stub-zone:
++ name: "."
++ stub-addr: 2001:db8::1
++CONFIG_END
++
++SCENARIO_BEGIN Test NAT64 transport, this time with /48 NAT64 prefix.
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8::1
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS FAKE.ROOT.
++SECTION ADDITIONAL
++FAKE.ROOT. IN AAAA 2001:db8::1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++; replies from NS over "NAT64"
++
++RANGE_BEGIN 0 100
++ ADDRESS 2001:db8:2345:c000:0002:0100::
++
++; A over NAT64
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN A
++SECTION ANSWER
++ns.v4only. IN A 192.0.2.1
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++ENTRY_END
++
++; no AAAA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++ns.v4only. IN AAAA
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++v4only. IN NS
++SECTION ANSWER
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY AA QR NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++SECTION AUTHORITY
++v4only. IN NS ns.v4only.
++SECTION ADDITIONAL
++ns.v4only. IN A 192.0.2.1
++ENTRY_END
++
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++test.v4only. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++test.v4only. IN A
++SECTION ANSWER
++test.v4only. IN A 192.0.2.2
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/iter_scrub_rr_length.rpl
+@@ -0,0 +1,298 @@
++; config options
++server:
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ minimal-responses: no
++ rrset-roundrobin: no
++ ede: yes
++ log-servfail: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test scrub of RRs of inappropriate length
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 200
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 200
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 200
++ 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 AA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN A 10.20.30.40
++www.example.com. IN A \# 3 030405
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++www.example.com. IN AAAA
++SECTION ANSWER
++www.example.com. IN AAAA 2001:db8::1234
++www.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++broken1.example.com. IN A
++SECTION ANSWER
++broken1.example.com. IN A \# 3 030405
++broken1.example.com. IN A \# 3 030406
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++broken1.example.com. IN AAAA
++SECTION ANSWER
++broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F
++broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E30
++broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E31
++SECTION AUTHORITY
++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 AA NOERROR
++SECTION QUESTION
++broken2.example.com. IN A
++SECTION ANSWER
++broken2.example.com. IN A 1.2.3.4
++broken2.example.com. IN A \# 3 030405
++broken2.example.com. IN A 1.2.3.5
++broken2.example.com. IN A \# 3 030406
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A \# 3 030407
++ns.example.com. IN A 1.2.3.6
++ns.example.com. IN A \# 3 030408
++ns.example.com. IN A \# 3 030409
++ns.example.com. IN A 1.2.3.7
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN AAAA
++ENTRY_END
++
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN AAAA
++SECTION ANSWER
++www.example.com. IN AAAA 2001:db8::1234
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++STEP 40 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++broken1.example.com. IN A
++ENTRY_END
++
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++broken1.example.com. IN A
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++STEP 60 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++broken1.example.com. IN AAAA
++ENTRY_END
++
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++broken1.example.com. IN AAAA
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++
++STEP 80 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++broken2.example.com. IN A
++ENTRY_END
++
++STEP 90 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++broken2.example.com. IN A
++SECTION ANSWER
++broken2.example.com. IN A 1.2.3.4
++broken2.example.com. IN A 1.2.3.5
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.6
++ns.example.com. IN A 1.2.3.7
++ENTRY_END
++
++STEP 100 QUERY
++ENTRY_BEGIN
++REPLY RD CD DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++STEP 110 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=0
++REPLY QR RD CD RA DO 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.6
++ns.example.com. IN A 1.2.3.7
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.conf
+@@ -0,0 +1,34 @@
++server:
++ verbosity: 7
++ # num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ do-not-query-localhost: no
++ # for the test, so that DNSSEC verification works.
++ #val-override-date: 20230929090000
++ trust-anchor: ". DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"
++
++remote-control:
++ control-enable: yes
++ control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
++ control-use-cert: no
++
++# for the test, an upstream server in the test setup.
++stub-zone:
++ name: "."
++ stub-addr: 127.0.0.1@@TOPORT@
++
++# hyperlocal root zone
++auth-zone:
++ name: "."
++ fallback-enabled: yes
++ for-downstream: no
++ for-upstream: yes
++ zonefile: "root.zone"
++ zonemd-check: yes
++ zonemd-reject-absence: yes
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.dsc
+@@ -0,0 +1,16 @@
++BaseName: root_zonemd
++Version: 1.0
++Description: ZONEMD check for root zone
++CreationDate: Fri 29 Sep 09:00:00 CEST 2023
++Maintainer: dr. W.C.A. Wijngaards
++Category:
++Component:
++CmdDepends:
++Depends:
++Help:
++Pre: root_zonemd.pre
++Post: root_zonemd.post
++Test: root_zonemd.test
++AuxFiles:
++Passed:
++Failure:
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.post
+@@ -0,0 +1,14 @@
++# #-- root_zonemd.post --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# source the test var file when it's there
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++#
++# do your teardown here
++. ../common.sh
++echo "> cat logfiles"
++cat fwd.log
++cat unbound.log
++kill_pid $FWD_PID
++kill_pid $UNBOUND_PID
++rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.pre
+@@ -0,0 +1,50 @@
++# #-- root_zonemd.pre--#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++. ../common.sh
++
++# attempt to download the root zone
++from=k.root-servers.net
++dig @$from . AXFR > root.txt
++if test $? -ne 0; then
++ echo "could not fetch root zone"
++ skip_test "could not fetch root zone"
++fi
++grep " SOA " root.txt | head -1 > root.soa
++cat root.soa >> root.zone
++grep -v " SOA " root.txt >> root.zone
++echo "fetched root.zone"
++ls -l root.zone
++cat root.soa
++
++get_random_port 2
++UNBOUND_PORT=$RND_PORT
++FWD_PORT=$(($RND_PORT + 1))
++echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
++echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
++
++# start forwarder
++get_ldns_testns
++$LDNS_TESTNS -p $FWD_PORT root_zonemd.testns >fwd.log 2>&1 &
++FWD_PID=$!
++echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
++
++# make config file
++CONTROL_PATH=/tmp
++CONTROL_PID=$$
++sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < root_zonemd.conf > ub.conf
++# start unbound in the background
++PRE="../.."
++$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
++UNBOUND_PID=$!
++echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
++echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
++echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
++
++cat .tpkg.var.test
++wait_ldns_testns_up fwd.log
++wait_unbound_up unbound.log
++
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.test
+@@ -0,0 +1,51 @@
++# #-- root_zonemd.test --#
++# source the master var file when it's there
++[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
++# use .tpkg.var.test for in test variable passing
++[ -f .tpkg.var.test ] && source .tpkg.var.test
++
++PRE="../.."
++# do the test
++echo "> dig www.example.com."
++dig @localhost -p $UNBOUND_PORT . SOA | tee outfile
++echo "> check answer"
++if grep root-servers outfile | grep "nstld.verisign-grs.com"; then
++ echo "OK"
++else
++ echo "Not OK"
++ exit 1
++fi
++
++echo "> unbound-control status"
++$PRE/unbound-control -c ub.conf status
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++else
++ echo "exit value: OK"
++fi
++
++# This is the output when an unsupported algorithm is used.
++if grep "auth zone . ZONEMD unsupported algorithm" unbound.log; then
++ echo "OK"
++else
++ echo "ZONEMD verification not OK"
++ exit 1
++fi
++
++echo "> unbound-control auth_zone_reload ."
++$PRE/unbound-control -c ub.conf auth_zone_reload . 2>&1 | tee outfile
++if test $? -ne 0; then
++ echo "wrong exit value."
++ exit 1
++fi
++# The output of the reload can be checked.
++#echo "> check unbound-control output"
++#if grep "example.com: ZONEMD verification successful" outfile; then
++ #echo "OK"
++#else
++ #echo "Not OK"
++ #exit 1
++#fi
++
++exit 0
+--- /dev/null
++++ contrib/unbound/testdata/root_zonemd.tdir/root_zonemd.testns
+@@ -0,0 +1,9 @@
++# reply to everything
++ENTRY_BEGIN
++MATCH opcode
++ADJUST copy_id copy_query
++REPLY QR SERVFAIL
++SECTION QUESTION
++example.com. IN SOA
++SECTION ANSWER
++ENTRY_END
+--- /dev/null
++++ contrib/unbound/testdata/rpz_cached_cname.rpl
+@@ -0,0 +1,122 @@
++; config options
++server:
++ module-config: "respip validator iterator"
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: no
++ rrset-roundrobin: no
++ access-control: 192.0.0.0/8 allow
++
++rpz:
++ name: "rpz.example.com"
++ rpz-log: yes
++ rpz-log-name: "rpz.example.com"
++ zonefile:
++TEMPFILE_NAME rpz.example.com
++TEMPFILE_CONTENTS rpz.example.com
++rpz.example.com. 3600 IN SOA ns.rpz.example.com. hostmaster.rpz.example.com. 1 3600 900 86400 3600
++rpz.example.com. 3600 IN NS ns.rpz.example.net.
++a.foo.rpz.example.com. 120 IN A 10.99.99.99
++TEMPFILE_END
++
++stub-zone:
++ name: "."
++ stub-addr: 10.20.30.40
++
++CONFIG_END
++
++SCENARIO_BEGIN Test RPZ with cached CNAME to A record
++
++RANGE_BEGIN 0 100
++ ADDRESS 10.20.30.40
++
++ENTRY_BEGIN
++MATCH opcode qname qtype
++ADJUST copy_id
++REPLY QR NOERROR AA
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS ns.
++SECTION ADDITIONAL
++ns. IN NS 10.20.30.40
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qname qtype
++ADJUST copy_id
++REPLY QR NOERROR AA
++SECTION QUESTION
++b.foo. IN A
++SECTION ANSWER
++b.foo. 30 CNAME a.foo.
++a.foo. 30 A 1.2.3.4
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qname qtype
++ADJUST copy_id
++REPLY QR NOERROR AA
++SECTION QUESTION
++a.foo. IN A
++SECTION ANSWER
++a.foo. A 1.2.3.4
++ENTRY_END
++
++RANGE_END
++
++STEP 10 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++a.foo. IN A
++ENTRY_END
++
++STEP 20 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++a.foo. IN A
++SECTION ANSWER
++a.foo. 120 A 10.99.99.99
++ENTRY_END
++
++STEP 30 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++b.foo. IN A
++ENTRY_END
++
++STEP 40 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++b.foo. IN A
++SECTION ANSWER
++b.foo. 30 CNAME a.foo.
++a.foo. 120 A 10.99.99.99
++ENTRY_END
++
++STEP 50 TIME_PASSES ELAPSE 3
++
++STEP 60 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++b.foo. IN A
++ENTRY_END
++
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AA NOERROR
++SECTION QUESTION
++b.foo. IN A
++SECTION ANSWER
++b.foo. 30 CNAME a.foo.
++a.foo. 120 A 10.99.99.99
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_0ttl_nodata.rpl
+@@ -0,0 +1,154 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with NXDOMAIN followed by 0 TTL
++; Scenario overview:
++; - query for 0ttl.example.com. IN A
++; - answer from upstream is NODATA; will be cached for the SOA negative TTL.
++; - check that the client gets the NODATA; also cached
++; - query again right after the TTL expired
++; - this time the server answers with a 0 TTL RRset
++; - check that we get the correct answer
++
++; ns.example.com.
++RANGE_BEGIN 0 20
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 30 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the NODATA (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NODATA
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Wait for the NXDOMAIN to expire
++STEP 31 TIME_PASSES ELAPSE 32
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NODATA
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 60 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we got the correct answer
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_0ttl_nxdomain.rpl
+@@ -0,0 +1,154 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with NXDOMAIN followed by 0 TTL
++; Scenario overview:
++; - query for 0ttl.example.com. IN A
++; - answer from upstream is NXDOMAIN; will be cached for the SOA negative TTL.
++; - check that the client gets the NXDOMAIN; also cached
++; - query again right after the TTL expired
++; - this time the server answers with a 0 TTL RRset
++; - check that we get the correct answer
++
++; ns.example.com.
++RANGE_BEGIN 0 20
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 30 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NXDOMAIN
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Wait for the NXDOMAIN to expire
++STEP 31 TIME_PASSES ELAPSE 32
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached NXDOMAIN
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA NXDOMAIN
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION AUTHORITY
++ example.com IN SOA ns.example.com dns.example.com 1 7200 3600 2419200 10
++ENTRY_END
++
++; Query again
++STEP 60 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we got the correct answer
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_0ttl_servfail.rpl
+@@ -0,0 +1,129 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with SERVFAIL followed by 0 TTL
++; Scenario overview:
++; - query for 0ttl.example.com. IN A
++; - answer from upstream is SERVFAIL; will be cached for NORR_TTL(5)
++; - check that the client gets the SERVFAIL; also cached
++; - query again right after the TTL expired
++; - this time the server answers with a 0 TTL RRset
++; - check that we get the correct answer
++
++; ns.example.com.
++RANGE_BEGIN 0 20
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA SERVFAIL
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 30 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we get the cached SERVFAIL
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Wait for the SERVFAIL to expire
++STEP 31 TIME_PASSES ELAPSE 32
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ENTRY_END
++
++; Check that we got the correct answer
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl
++ REPLY QR RD RA NOERROR
++ SECTION QUESTION
++ 0ttl.example.com. IN A
++ SECTION ANSWER
++ 0ttl.example.com. 0 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/serve_expired_cached_servfail_refresh.rpl
+@@ -0,0 +1,145 @@
++; config options
++server:
++ module-config: "validator iterator"
++ qname-minimisation: "no"
++ minimal-responses: no
++ serve-expired: yes
++ serve-expired-reply-ttl: 123
++ log-servfail: yes
++ ede: yes
++ ede-serve-expired: yes
++
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired with client-timeout and a SERVFAIL upstream reply
++; Scenario overview:
++; - query for example.com. IN A
++; - answer from upstream is SERVFAIL; will be cached for NORR_TTL(5)
++; - check that the client gets the SERVFAIL; also cached
++; - query again right after the TTL expired
++; - cached SERVFAIL should be ignored and upstream queried
++; - answer from upstream is still SERVFAIL; the cached error response will be
++; refreshed for another NORR_TTL(5)
++; - check that the client gets the SERVFAIL
++; - query again; the upstream now has the answer available
++; - check that we get the refreshed cached response instead
++
++; ns.example.com.
++RANGE_BEGIN 0 50
++ ADDRESS 1.2.3.4
++ ; response to A query
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR AA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 60 100
++ ADDRESS 1.2.3.4
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. 10 IN NS
++ SECTION ANSWER
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ example.com. IN A
++ SECTION ANSWER
++ example.com. 10 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 10 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 10 IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; Query with RD flag
++STEP 0 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be cached)
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Query again
++STEP 20 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the cached SERVFAIL
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Wait for the SERVFAIL to expire
++STEP 31 TIME_PASSES ELAPSE 6
++
++; Query again
++STEP 40 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the SERVFAIL (will be refreshed)
++STEP 50 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Query again, upstream has the real answer available
++STEP 60 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check that we get the refreshed cached SERVFAIL
++STEP 70 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all
++ REPLY QR RD RA SERVFAIL
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/stat_values.tdir/stat_values_cachedb.conf
+@@ -0,0 +1,36 @@
++server:
++ verbosity: 5
++ module-config: "cachedb iterator"
++ serve-expired: yes
++ num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ do-not-query-localhost: no
++ extended-statistics: yes
++ identity: "stat_values"
++ outbound-msg-retry: 0
++ root-key-sentinel: no
++ trust-anchor-signaling: no
++
++ local-zone: local.zone static
++ local-data: "www.local.zone A 192.0.2.1"
++remote-control:
++ control-enable: yes
++ control-interface: 127.0.0.1
++ # control-interface: ::1
++ control-port: @CONTROL_PORT@
++ server-key-file: "unbound_server.key"
++ server-cert-file: "unbound_server.pem"
++ control-key-file: "unbound_control.key"
++ control-cert-file: "unbound_control.pem"
++forward-zone:
++ name: "."
++ forward-addr: "127.0.0.1@@TOPORT@"
++forward-zone:
++ name: "expired."
++ forward-addr: "127.0.0.1@@EXPIREDPORT@"
+--- /dev/null
++++ contrib/unbound/testdata/stat_values.tdir/stat_values_downstream_cookies.conf
+@@ -0,0 +1,32 @@
++server:
++ verbosity: 5
++ module-config: "iterator"
++ num-threads: 1
++ interface: 127.0.0.1
++ port: @PORT@
++ use-syslog: no
++ directory: ""
++ pidfile: "unbound.pid"
++ chroot: ""
++ username: ""
++ extended-statistics: yes
++ identity: "stat_values"
++ outbound-msg-retry: 0
++ root-key-sentinel: no
++ trust-anchor-signaling: no
++
++ local-zone: local.zone static
++ local-data: "www.local.zone A 192.0.2.1"
++
++ answer-cookie: yes
++ access-control: 127.0.0.1 allow_cookie
++
++remote-control:
++ control-enable: yes
++ control-interface: 127.0.0.1
++ # control-interface: ::1
++ control-port: @CONTROL_PORT@
++ server-key-file: "unbound_server.key"
++ server-cert-file: "unbound_server.pem"
++ control-key-file: "unbound_control.key"
++ control-cert-file: "unbound_control.pem"
+--- /dev/null
++++ contrib/unbound/testdata/subnet_cached_ede.crpl
+@@ -0,0 +1,114 @@
++; Ask the same question twice. Check to see second is answered
++; from cache
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ send-client-subnet: 1.2.3.4
++ max-client-subnet-ipv4: 17
++ module-config: "subnetcache validator iterator"
++ verbosity: 3
++ qname-minimisation: no
++ minimal-responses: no
++ ede: yes
++ val-log-level: 2
++ trust-anchor: "example.nl. DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29B22446B1"
++
++stub-zone:
++ name: "example.nl"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test subnetcache support for caching EDEs.
++
++; ns.example.com.
++RANGE_BEGIN 0 10
++ ADDRESS 1.2.3.4
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN DNSKEY
++SECTION ANSWER
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 11 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++example.nl. IN A
++SECTION ANSWER
++example.nl. IN A 1.2.3.4
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 11 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ENTRY_END
++RANGE_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; get the entry in cache.
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ 00 08 00 07 ; OPC, optlen
++ 00 01 11 00 ; ip4, scope 17, source 0
++ 7f 00 00 ; 127.0.0.0/17
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; get the answer for it
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++; query again for the cached entry
++STEP 20 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++example.nl. IN A
++SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ 00 08 00 07 ; OPC, optlen
++ 00 01 11 00 ; ip4, scope 17, source 0
++ 7f 00 00 ; 127.0.0.0/17
++ HEX_EDNSDATA_END
++ENTRY_END
++
++; this must be a cached answer since stub is not answering in this range
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=9
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++example.nl. IN A
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_global_prefetch.crpl
+@@ -0,0 +1,236 @@
++; Check if the prefetch option works properly for messages stored in the global
++; cache for non-ECS clients. The prefetch query needs to result in an ECS
++; outgoing query based on the client's IP.
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ send-client-subnet: 1.2.3.4
++ max-client-subnet-ipv4: 21
++ module-config: "subnetcache iterator"
++ verbosity: 3
++ access-control: 127.0.0.1 allow_snoop
++ qname-minimisation: no
++ minimal-responses: no
++ prefetch: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test prefetch option for global cache with ECS enabled
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ . IN NS
++ SECTION ANSWER
++ . IN NS K.ROOT-SERVERS.NET.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ com. IN NS
++ SECTION ANSWER
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 10
++ 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 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
++
++; ns.example.com.
++RANGE_BEGIN 11 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id copy_ednsdata_assume_clientsubnet
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 IN A 10.20.30.40
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 15 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ 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
++
++; This answer should be in the global cache (because no ECS from upstream)
++STEP 2 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++; Try to trigger a prefetch
++STEP 3 TIME_PASSES ELAPSE 9
++
++STEP 11 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the global cache and a prefetch was triggered.
++STEP 12 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 1 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3591 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3591 IN A 1.2.3.4
++ENTRY_END
++
++; Allow time to pass so that the global cache record is expired.
++STEP 13 TIME_PASSES ELAPSE 2
++
++; Query again to verify that the record was prefetched and stored in the ECS
++; cache.
++STEP 15 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the ECS cache.
++STEP 16 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 8 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3598 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3598 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_global_prefetch_always_forward.crpl
+@@ -0,0 +1,167 @@
++; Check if the prefetch option works properly when serve-expired is combined
++; with client-subnet-always-forward for non-ECS clients. The prefetch query
++; needs to result in an outgoing query without ECS.
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ serve-expired: yes
++ client-subnet-always-forward: yes
++ module-config: "subnetcache iterator"
++ verbosity: 3
++ access-control: 127.0.0.1 allow_snoop
++ qname-minimisation: no
++ minimal-responses: no
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test serve-expired and client-subnet-always-forward without ECS in the request
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ . IN NS
++ SECTION ANSWER
++ . IN NS K.ROOT-SERVERS.NET.
++ SECTION ADDITIONAL
++ K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ com. IN NS
++ SECTION ANSWER
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; 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
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 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
++
++; This answer should be in the global cache
++STEP 2 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++; Wait for the TTL to expire
++STEP 3 TIME_PASSES ELAPSE 20
++
++STEP 11 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the global cache and a prefetch was triggered
++STEP 12 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 30 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3580 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3580 IN A 1.2.3.4
++ENTRY_END
++
++STEP 13 CHECK_OUT_QUERY
++ENTRY_BEGIN
++ MATCH all
++ REPLY NOERROR DO
++ SECTION QUESTION
++ www.example.com. IN A
++ENTRY_END
++
++STEP 14 TRAFFIC
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_global_prefetch_expired.crpl
+@@ -0,0 +1,241 @@
++; Check if the prefetch option works properly for messages stored in the global
++; cache for non-ECS clients. The prefetch query needs to result in an ECS
++; outgoing query based on the client's IP.
++; Prefetch initiated via serve-expired.
++
++server:
++ trust-anchor-signaling: no
++ target-fetch-policy: "0 0 0 0 0"
++ send-client-subnet: 1.2.3.4
++ max-client-subnet-ipv4: 21
++ module-config: "subnetcache iterator"
++ verbosity: 3
++ access-control: 127.0.0.1 allow_snoop
++ qname-minimisation: no
++ minimal-responses: no
++ serve-expired: yes
++ serve-expired-ttl: 1
++ prefetch: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test prefetch option for global cache with ECS enabled (initiated via serve-expired)
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ . IN NS
++ SECTION ANSWER
++ . IN NS K.ROOT-SERVERS.NET.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ com. IN NS
++ SECTION ANSWER
++ com. IN NS a.gtld-servers.net.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ a.gtld-servers.net. IN A 192.5.6.30
++ ENTRY_END
++
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++RANGE_END
++
++; ns.example.com.
++RANGE_BEGIN 0 10
++ 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname
++ ADJUST copy_id
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 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
++
++; ns.example.com.
++RANGE_BEGIN 11 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
++ HEX_EDNSDATA_BEGIN
++ ;; we expect to receive empty
++ HEX_EDNSDATA_END
++ ns.example.com. IN A 1.2.3.4
++ ENTRY_END
++
++ ; response to query of interest
++ ENTRY_BEGIN
++ MATCH opcode qtype qname ednsdata
++ ADJUST copy_id copy_ednsdata_assume_clientsubnet
++ REPLY QR NOERROR
++ SECTION QUESTION
++ www.example.com. IN A
++ SECTION ANSWER
++ www.example.com. 10 IN A 10.20.30.40
++ SECTION AUTHORITY
++ example.com. IN NS ns.example.com.
++ SECTION ADDITIONAL
++ HEX_EDNSDATA_BEGIN
++ ; client is 127.0.0.1
++ 00 08 ; OPC
++ 00 07 ; option length
++ 00 01 ; Family
++ 15 00 ; source mask, scopemask
++ 7f 00 00 ; address
++ HEX_EDNSDATA_END
++ 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
++
++; This answer should be in the global cache (because no ECS from upstream)
++STEP 2 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA 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
++
++; Try to trigger a prefetch with expired data
++STEP 3 TIME_PASSES ELAPSE 11
++
++STEP 11 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This expired record came from the global cache and a prefetch is triggered.
++STEP 12 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 30 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3589 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3589 IN A 1.2.3.4
++ENTRY_END
++
++;STEP 13 TRAFFIC
++; Allow enough time to pass so that the expired record from the global cache
++; cannot be used anymore.
++STEP 14 TIME_PASSES ELAPSE 1
++
++; Query again to verify that the record was prefetched and stored in the ECS
++; cache.
++STEP 15 QUERY
++ENTRY_BEGIN
++REPLY RD
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; This record came from the ECS cache.
++STEP 16 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl
++REPLY QR RD RA NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 9 IN A 10.20.30.40
++SECTION AUTHORITY
++example.com. 3599 IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. 3599 IN A 1.2.3.4
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/subnet_prezero.crpl
+@@ -0,0 +1,155 @@
++; subnet unit test
++server:
++ trust-anchor-signaling: no
++ send-client-subnet: 1.2.3.4
++ send-client-subnet: 1.2.3.5
++ target-fetch-policy: "0 0 0 0 0"
++ module-config: "subnetcache validator iterator"
++ qname-minimisation: no
++ minimal-responses: no
++
++stub-zone:
++ name: "example.com"
++ stub-addr: 1.2.3.4
++CONFIG_END
++
++SCENARIO_BEGIN Test subnetcache source prefix zero from client.
++; In RFC7871 section-7.1.2 (para. 2).
++; It says that the recursor must send no EDNS subnet or its own address
++; in the EDNS subnet to the upstream server. And use that answer for the
++; source prefix length zero query. That type of query is for privacy.
++; The authority server is then going to use the resolver's IP, if any, to
++; tailor the answer to the query source address.
++
++; ns.example.com
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.4
++
++; reply with 0.0.0.0/0 in reply
++; For the test the answers for 0.0.0.0/0 queries are SERVFAIL, the normal
++; answers are NOERROR.
++ENTRY_BEGIN
++MATCH opcode qtype qname ednsdata
++ADJUST copy_id
++REPLY QR AA DO SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN CNAME star.c10r.example.com.
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++; reply without subnet
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN CNAME star.c10r.example.com.
++ENTRY_END
++
++; delegation answer for c10r.example.com, with subnet /0
++ENTRY_BEGIN
++MATCH opcode subdomain ednsdata
++ADJUST copy_id copy_query
++REPLY QR DO SERVFAIL
++SECTION QUESTION
++c10r.example.com. IN NS
++SECTION AUTHORITY
++c10r.example.com. IN NS ns.c10r.example.com.
++SECTION ADDITIONAL
++ns.c10r.example.com. IN A 1.2.3.5
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++; delegation answer for c10r.example.com, without subnet
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR DO NOERROR
++SECTION QUESTION
++c10r.example.com. IN NS
++SECTION AUTHORITY
++c10r.example.com. IN NS ns.c10r.example.com.
++SECTION ADDITIONAL
++ns.c10r.example.com. IN A 1.2.3.5
++ENTRY_END
++RANGE_END
++
++; ns.c10r.example.com
++RANGE_BEGIN 0 100
++ ADDRESS 1.2.3.5
++
++; reply with 0.0.0.0/0 in reply
++ENTRY_BEGIN
++MATCH opcode qtype qname ednsdata
++ADJUST copy_id
++REPLY QR AA DO SERVFAIL
++SECTION QUESTION
++star.c10r.example.com. IN A
++SECTION ANSWER
++star.c10r.example.com. IN A 1.2.3.6
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++; reply without subnet
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA DO NOERROR
++SECTION QUESTION
++star.c10r.example.com. IN A
++SECTION ANSWER
++star.c10r.example.com. IN A 1.2.3.6
++ENTRY_END
++RANGE_END
++
++; ask for www.example.com
++; server answers with CNAME to a delegation, that then
++; returns a /24 answer.
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++www.example.com. IN A
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ednsdata
++REPLY QR RD RA DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. IN CNAME star.c10r.example.com.
++star.c10r.example.com. IN A 1.2.3.6
++SECTION ADDITIONAL
++HEX_EDNSDATA_BEGIN
++ 00 08 00 04 ; OPCODE=subnet, optlen
++ 00 01 00 00 ; ip4, scope 0, source 0
++ ; 0.0.0.0/0
++HEX_EDNSDATA_END
++ENTRY_END
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/val_any_negcache.rpl
+@@ -0,0 +1,243 @@
++; config options
++; The island of trust is at example.com
++server:
++ trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b"
++ val-override-date: "20070916134226"
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ fake-sha1: yes
++ trust-anchor-signaling: no
++ rrset-roundrobin: no
++ aggressive-nsec: yes
++ harden-unknown-additional: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test validator with response to qtype ANY and negative cache.
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode subdomain
++ADJUST copy_id copy_query
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN NS
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; 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.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
++ENTRY_END
++
++; response with NODATA
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN LOC
++SECTION AUTHORITY
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++ENTRY_END
++
++; response to query of interest
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN ANY
++SECTION ANSWER
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJIIs70j+sDS/UT2QRp61SE7S3EEXopNXoFE73JLRmvpi/UrOO/Vz4Se6wXv/CYCKjGw06U4WRgRYXcpEhJROyNapmdIKSxhOzfLVE1gqA0PweZR8dtY3aNQSRn3sPpwJr6Mi/PqQKAMMrZ9ckJpf1+bQMOOvxgzz2U1GS18b3yZKcgTMEaJzd/GZYzi/BN2DzQ0MsrSwYXfsNLFOBbs8PJMW4LYIxeeOe6rUgkWOF7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFHq7BNVAeLW+Uw/rkjVS08lrMDk/AhR+bvChHfiE4jLb6uoyE54/irCuqA== ;{id = 2854}
++example.com. 600 IN NAPTR 20 0 "s" "SIP+D2U" "" _sip._udp.example.com.
++example.com. 600 IN RRSIG NAPTR 3 2 600 20070926134150 20070829134150 2854 example.com. MC0CFE8qs66bzuOyKmTIacamrmqabMRzAhUAn0MujX1LB0UpTHuLMgdgMgJJlq4= ;{id = 2854}
++example.com. 86400 IN AAAA 2001:7b8:206:1::1
++example.com. 86400 IN RRSIG AAAA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFEqS4WHyqhUkv7t42TsBZJk/Q9paAhUAtTZ8GaXGpot0PmsM0oGzQU+2iw4= ;{id = 2854}
++example.com. 86400 IN TXT "Stichting NLnet Labs"
++example.com. 86400 IN RRSIG TXT 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH3otn2u8zXczBS8L0VKpyAYZGSkAhQLGaQclkzMAzlB5j73opFjdkh8TA== ;{id = 2854}
++example.com. 86400 IN MX 100 v.net.example.
++example.com. 86400 IN MX 50 open.example.com.
++example.com. 86400 IN RRSIG MX 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFEKh3jeqh69zcOqWWv3GNKlMECPyAhR9HJkcPLqlyVWUccWDFJfGGcQfdg== ;{id = 2854}
++example.com. 86400 IN NS v.net.example.
++example.com. 86400 IN NS open.example.com.
++example.com. 86400 IN NS ns7.domain-registry.example.
++example.com. 86400 IN RRSIG NS 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCaRn30X4neKW7KYoTa2kcsoOLgfgIURvKEyDczLypWlx99KpxzMxRYhEc= ;{id = 2854}
++example.com. 86400 IN A 213.154.224.1
++example.com. 86400 IN RRSIG A 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH8kSLxmRTwzlGDxvF1e4y/gM+5dAhQkzyQ2a6Gf+CMaHzVScaUvTt9HhQ== ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++SECTION AUTHORITY
++SECTION ADDITIONAL
++ns7.domain-registry.example. 80173 IN A 62.4.86.230
++open.example.com. 600 IN A 213.154.224.1
++open.example.com. 600 IN AAAA 2001:7b8:206:1::53
++open.example.com. 600 IN AAAA 2001:7b8:206:1::1
++v.net.example. 28800 IN A 213.154.224.17
++v.net.example. 28800 IN AAAA 2001:7b8:206:1:200:39ff:fe59:b187
++johnny.example.com. 600 IN A 213.154.224.44
++open.example.com. 600 IN RRSIG A 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCh8bja923UJmg1+sYXMK8WIE4dpgIUQe9sZa0GOcUYSgb2rXoogF8af+Y= ;{id = 2854}
++open.example.com. 600 IN RRSIG AAAA 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCRGJgIS6kEVG7aJfovuG/q3cgOWwIUYEIFCnfRQlMIYWF7BKMQoMbdkE0= ;{id = 2854}
++johnny.example.com. 600 IN RRSIG A 3 3 600 20070926134150 20070829134150 2854 example.com. MCwCFAh0/zSpCd/9eMNz7AyfnuGQFD1ZAhQEpNFNw4XByNEcbi/vsVeii9kp7g== ;{id = 2854}
++_sip._udp.example.com. 600 IN RRSIG SRV 3 4 600 20070926134150 20070829134150 2854 example.com. MCwCFFSRVgOcq1ihVuO6MhCuzWs6SxpVAhRPHHCKy0JxymVkYeFOxTkbVSWMMw== ;{id = 2854}
++_sip._udp.example.com. 600 IN SRV 0 0 5060 johnny.example.com.
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++MATCH TCP
++REPLY RD DO
++SECTION QUESTION
++example.com. IN LOC
++ENTRY_END
++
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++example.com. IN LOC
++SECTION ANSWER
++SECTION AUTHORITY
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++ENTRY_END
++
++STEP 20 QUERY
++ENTRY_BEGIN
++MATCH TCP
++REPLY RD DO
++SECTION QUESTION
++example.com. IN ANY
++ENTRY_END
++
++; Allow validation resuming for the RRSIGs
++STEP 21 TIME_PASSES ELAPSE 0.05
++
++; recursion happens here.
++STEP 30 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++example.com. IN ANY
++SECTION ANSWER
++example.com. 86400 IN SOA open.example.com. hostmaster.example.com. 2007090400 28800 7200 604800 18000
++example.com. 86400 IN RRSIG SOA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCSs8KJepwaIp5vu++/0hk04lkXvgIUdphJSAE/MYob30WcRei9/nL49tE= ;{id = 2854}
++example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJIIs70j+sDS/UT2QRp61SE7S3EEXopNXoFE73JLRmvpi/UrOO/Vz4Se6wXv/CYCKjGw06U4WRgRYXcpEhJROyNapmdIKSxhOzfLVE1gqA0PweZR8dtY3aNQSRn3sPpwJr6Mi/PqQKAMMrZ9ckJpf1+bQMOOvxgzz2U1GS18b3yZKcgTMEaJzd/GZYzi/BN2DzQ0MsrSwYXfsNLFOBbs8PJMW4LYIxeeOe6rUgkWOF7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
++example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFHq7BNVAeLW+Uw/rkjVS08lrMDk/AhR+bvChHfiE4jLb6uoyE54/irCuqA== ;{id = 2854}
++example.com. 600 IN NAPTR 20 0 "s" "SIP+D2U" "" _sip._udp.example.com.
++example.com. 600 IN RRSIG NAPTR 3 2 600 20070926134150 20070829134150 2854 example.com. MC0CFE8qs66bzuOyKmTIacamrmqabMRzAhUAn0MujX1LB0UpTHuLMgdgMgJJlq4= ;{id = 2854}
++example.com. 86400 IN AAAA 2001:7b8:206:1::1
++example.com. 86400 IN RRSIG AAAA 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFEqS4WHyqhUkv7t42TsBZJk/Q9paAhUAtTZ8GaXGpot0PmsM0oGzQU+2iw4= ;{id = 2854}
++example.com. 86400 IN TXT "Stichting NLnet Labs"
++example.com. 86400 IN RRSIG TXT 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH3otn2u8zXczBS8L0VKpyAYZGSkAhQLGaQclkzMAzlB5j73opFjdkh8TA== ;{id = 2854}
++example.com. 86400 IN MX 100 v.net.example.
++example.com. 86400 IN MX 50 open.example.com.
++example.com. 86400 IN RRSIG MX 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFEKh3jeqh69zcOqWWv3GNKlMECPyAhR9HJkcPLqlyVWUccWDFJfGGcQfdg== ;{id = 2854}
++example.com. 86400 IN NS v.net.example.
++example.com. 86400 IN NS open.example.com.
++example.com. 86400 IN NS ns7.domain-registry.example.
++example.com. 86400 IN RRSIG NS 3 2 86400 20070926134150 20070829134150 2854 example.com. MC0CFQCaRn30X4neKW7KYoTa2kcsoOLgfgIURvKEyDczLypWlx99KpxzMxRYhEc= ;{id = 2854}
++example.com. 86400 IN A 213.154.224.1
++example.com. 86400 IN RRSIG A 3 2 86400 20070926134150 20070829134150 2854 example.com. MCwCFH8kSLxmRTwzlGDxvF1e4y/gM+5dAhQkzyQ2a6Gf+CMaHzVScaUvTt9HhQ== ;{id = 2854}
++example.com. 18000 IN NSEC _sip._udp.example.com. A NS SOA MX TXT AAAA NAPTR RRSIG NSEC DNSKEY
++example.com. 18000 IN RRSIG NSEC 3 2 18000 20070926134150 20070829134150 2854 example.com. MCwCFBzOGtpgq4uJ2jeuLPYl2HowIRzDAhQVXNz1haQ1mI7z9lt5gcvWW+lFhA== ;{id = 2854}
++SECTION AUTHORITY
++SECTION ADDITIONAL
++open.example.com. 600 IN A 213.154.224.1
++open.example.com. 600 IN AAAA 2001:7b8:206:1::53
++open.example.com. 600 IN AAAA 2001:7b8:206:1::1
++open.example.com. 600 IN RRSIG A 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCh8bja923UJmg1+sYXMK8WIE4dpgIUQe9sZa0GOcUYSgb2rXoogF8af+Y= ;{id = 2854}
++open.example.com. 600 IN RRSIG AAAA 3 3 600 20070926134150 20070829134150 2854 example.com. MC0CFQCRGJgIS6kEVG7aJfovuG/q3cgOWwIUYEIFCnfRQlMIYWF7BKMQoMbdkE0= ;{id = 2854}
++ENTRY_END
++
++SCENARIO_END
+--- /dev/null
++++ contrib/unbound/testdata/val_scrub_rr_length.rpl
+@@ -0,0 +1,164 @@
++; config options
++; The island of trust is at example.com
++server:
++ trust-anchor: "example.com. IN DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
++ val-override-date: "20070916134226"
++ target-fetch-policy: "0 0 0 0 0"
++ qname-minimisation: "no"
++ trust-anchor-signaling: no
++ minimal-responses: no
++ rrset-roundrobin: no
++ ede: yes
++ log-servfail: yes
++
++stub-zone:
++ name: "."
++ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
++CONFIG_END
++
++SCENARIO_BEGIN Test validator with scrub of RR for inappropriate length
++
++; K.ROOT-SERVERS.NET.
++RANGE_BEGIN 0 100
++ ADDRESS 193.0.14.129
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++. IN NS
++SECTION ANSWER
++. IN NS K.ROOT-SERVERS.NET.
++SECTION ADDITIONAL
++K.ROOT-SERVERS.NET. IN A 193.0.14.129
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++RANGE_END
++
++; a.gtld-servers.net.
++RANGE_BEGIN 0 100
++ ADDRESS 192.5.6.30
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++com. IN NS
++SECTION ANSWER
++com. IN NS a.gtld-servers.net.
++SECTION ADDITIONAL
++a.gtld-servers.net. IN A 192.5.6.30
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ENTRY_END
++RANGE_END
++
++; 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.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++
++; response to DNSKEY priming query
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR NOERROR
++SECTION QUESTION
++example.com. IN DNSKEY
++SECTION ANSWER
++example.com. IN DNSKEY 256 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55566 (zsk), size = 1024b}
++example.com. 3600 IN RRSIG DNSKEY 8 2 3600 20070926134150 20070829134150 55566 example.com. Ni7Q17l2dzKcAnHdU3Mycpdwo0I6qgGxRvBhBNI43xIUFHJpgKpbeMFxKvVTkbwHyMPMIuHmOaC82IBhOpGD10SExVh4erQhWS3Hvl+m4Cwl3WI9N+AW6CTB9yj+d4xzX3bHjjBt6MSk4bU8ABR7qIoAjgjY7zdtUDWQlaM+d18=
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++
++ENTRY_BEGIN
++MATCH opcode qtype qname
++ADJUST copy_id
++REPLY QR AA NOERROR
++SECTION QUESTION
++ns.example.com. IN AAAA
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++
++; response to query of interest
++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
++www.example.com. IN A \# 5 0102030405
++; RRSIG includes the malformed record.
++www.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. W4WFu9B81uRvp3Dj8uLIscypznKWuLuKrZqVg1on5/45/3/xyjHvj3TjTL3gruWFXPiQpldvOstXLZ5eN3OpqILdkVey0eqVATujpHwIruY6GWztVx5WptmFfK6E6zzshZ3RmAARqq/czQ+IZli2A9xixdY2H0o1dSU6gohEjjE=
++SECTION AUTHORITY
++example.com. IN NS ns.example.com.
++example.com. 3600 IN RRSIG NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
++SECTION ADDITIONAL
++ns.example.com. IN A 1.2.3.4
++ns.example.com. 3600 IN RRSIG A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
++ENTRY_END
++RANGE_END
++
++STEP 1 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; recursion happens here.
++STEP 10 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ede=0
++REPLY QR RD RA DO SERVFAIL
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++ENTRY_END
++
++SCENARIO_END
+--- contrib/unbound/util/config_file.c.orig
++++ contrib/unbound/util/config_file.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -55,6 +55,7 @@
+ #include "util/regional.h"
+ #include "util/fptr_wlist.h"
+ #include "util/data/dname.h"
++#include "util/random.h"
+ #include "util/rtt.h"
+ #include "services/cache/infra.h"
+ #include "sldns/wire2str.h"
+@@ -87,7 +88,10 @@
+ /** init ports possible for use */
+ static void init_outgoing_availports(int* array, int num);
+
+-struct config_file*
++/** init cookie with random data */
++static void init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len);
++
++struct config_file*
+ config_create(void)
+ {
+ struct config_file* cfg;
+@@ -116,6 +120,7 @@
+ cfg->tcp_auth_query_timeout = 3 * 1000; /* 3s in millisecs */
+ cfg->do_tcp_keepalive = 0;
+ cfg->tcp_keepalive_timeout = 120 * 1000; /* 120s in millisecs */
++ cfg->sock_queue_timeout = 0; /* do not check timeout */
+ cfg->ssl_service_key = NULL;
+ cfg->ssl_service_pem = NULL;
+ cfg->ssl_port = UNBOUND_DNS_OVER_TLS_PORT;
+@@ -153,7 +158,7 @@
+ cfg->outgoing_num_ports = 48; /* windows is limited in num fds */
+ cfg->num_queries_per_thread = 24;
+ cfg->outgoing_num_tcp = 2; /* leaves 64-52=12 for: 4if,1stop,thread4 */
+- cfg->incoming_num_tcp = 2;
++ cfg->incoming_num_tcp = 2;
+ #endif
+ cfg->stream_wait_size = 4 * 1024 * 1024;
+ cfg->edns_buffer_size = 1232; /* from DNS flagday recommendation */
+@@ -233,6 +238,7 @@
+ cfg->harden_below_nxdomain = 1;
+ cfg->harden_referral_path = 0;
+ cfg->harden_algo_downgrade = 0;
++ cfg->harden_unknown_additional = 0;
+ cfg->use_caps_bits_for_id = 0;
+ cfg->caps_whitelist = NULL;
+ cfg->private_address = NULL;
+@@ -265,6 +271,7 @@
+ cfg->val_permissive_mode = 0;
+ cfg->aggressive_nsec = 1;
+ cfg->ignore_cd = 0;
++ cfg->disable_edns_do = 0;
+ cfg->serve_expired = 0;
+ cfg->serve_expired_ttl = 0;
+ cfg->serve_expired_ttl_reset = 0;
+@@ -300,14 +307,14 @@
+ cfg->minimal_responses = 1;
+ cfg->rrset_roundrobin = 1;
+ cfg->unknown_server_time_limit = 376;
+- cfg->max_udp_size = 4096;
+- if(!(cfg->server_key_file = strdup(RUN_DIR"/unbound_server.key")))
++ cfg->max_udp_size = 1232; /* value taken from edns_buffer_size */
++ if(!(cfg->server_key_file = strdup(RUN_DIR"/unbound_server.key")))
+ goto error_exit;
+- if(!(cfg->server_cert_file = strdup(RUN_DIR"/unbound_server.pem")))
++ if(!(cfg->server_cert_file = strdup(RUN_DIR"/unbound_server.pem")))
+ goto error_exit;
+- if(!(cfg->control_key_file = strdup(RUN_DIR"/unbound_control.key")))
++ if(!(cfg->control_key_file = strdup(RUN_DIR"/unbound_control.key")))
+ goto error_exit;
+- if(!(cfg->control_cert_file = strdup(RUN_DIR"/unbound_control.pem")))
++ if(!(cfg->control_cert_file = strdup(RUN_DIR"/unbound_control.pem")))
+ goto error_exit;
+
+ #ifdef CLIENT_SUBNET
+@@ -315,7 +322,7 @@
+ #else
+ if(!(cfg->module_conf = strdup("validator iterator"))) goto error_exit;
+ #endif
+- if(!(cfg->val_nsec3_key_iterations =
++ if(!(cfg->val_nsec3_key_iterations =
+ strdup("1024 150 2048 150 4096 150"))) goto error_exit;
+ #if defined(DNSTAP_SOCKET_PATH)
+ if(!(cfg->dnstap_socket_path = strdup(DNSTAP_SOCKET_PATH)))
+@@ -324,6 +331,7 @@
+ cfg->dnstap_bidirectional = 1;
+ cfg->dnstap_tls = 1;
+ cfg->disable_dnssec_lame_check = 0;
++ cfg->ip_ratelimit_cookie = 0;
+ cfg->ip_ratelimit = 0;
+ cfg->ratelimit = 0;
+ cfg->ip_ratelimit_slabs = 4;
+@@ -367,14 +375,22 @@
+ cfg->ipsecmod_whitelist = NULL;
+ cfg->ipsecmod_strict = 0;
+ #endif
++ cfg->do_answer_cookie = 0;
++ memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret));
++ cfg->cookie_secret_len = 16;
++ init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len);
+ #ifdef USE_CACHEDB
+ if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
+ if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
++ cfg->cachedb_no_store = 0;
+ #ifdef USE_REDIS
+ if(!(cfg->redis_server_host = strdup("127.0.0.1"))) goto error_exit;
++ cfg->redis_server_path = NULL;
++ cfg->redis_server_password = NULL;
+ cfg->redis_timeout = 100;
+ cfg->redis_server_port = 6379;
+ cfg->redis_expire_records = 0;
++ cfg->redis_logical_db = 0;
+ #endif /* USE_REDIS */
+ #endif /* USE_CACHEDB */
+ #ifdef USE_IPSET
+@@ -487,10 +503,10 @@
+ /* not supported, library must have 1 thread in bgworker */
+ return 0;
+ } else if(strcmp(opt, "outgoing-port-permit:") == 0) {
+- return cfg_mark_ports(val, 1,
++ return cfg_mark_ports(val, 1,
+ cfg->outgoing_avail_ports, 65536);
+ } else if(strcmp(opt, "outgoing-port-avoid:") == 0) {
+- return cfg_mark_ports(val, 0,
++ return cfg_mark_ports(val, 0,
+ cfg->outgoing_avail_ports, 65536);
+ } else if(strcmp(opt, "local-zone:") == 0) {
+ return cfg_parse_local_zone(cfg, val);
+@@ -504,7 +520,7 @@
+ if(atoi(val) == 0) return 0;
+ cfg->val_date_override = (uint32_t)atoi(val);
+ }
+- } else if(strcmp(opt, "local-data-ptr:") == 0) {
++ } else if(strcmp(opt, "local-data-ptr:") == 0) {
+ char* ptr = cfg_ptr_reverse((char*)opt);
+ return cfg_strlist_insert(&cfg->local_data, ptr);
+ } else if(strcmp(opt, "logfile:") == 0) {
+@@ -540,6 +556,7 @@
+ else S_NUMBER_NONZERO("tcp-reuse-timeout:", tcp_reuse_timeout)
+ else S_YNO("edns-tcp-keepalive:", do_tcp_keepalive)
+ else S_NUMBER_NONZERO("edns-tcp-keepalive-timeout:", tcp_keepalive_timeout)
++ else S_NUMBER_OR_ZERO("sock-queue-timeout:", sock_queue_timeout)
+ else S_YNO("ssl-upstream:", ssl_upstream)
+ else S_YNO("tls-upstream:", ssl_upstream)
+ else S_STR("ssl-service-key:", ssl_service_key)
+@@ -649,6 +666,7 @@
+ else S_YNO("harden-below-nxdomain:", harden_below_nxdomain)
+ else S_YNO("harden-referral-path:", harden_referral_path)
+ else S_YNO("harden-algo-downgrade:", harden_algo_downgrade)
++ else S_YNO("harden-unknown-additional:", harden_unknown_additional)
+ else S_YNO("use-caps-for-id:", use_caps_bits_for_id)
+ else S_STRLIST("caps-whitelist:", caps_whitelist)
+ else S_SIZET_OR_ZERO("unwanted-reply-threshold:", unwanted_threshold)
+@@ -675,6 +693,7 @@
+ else S_YNO("val-permissive-mode:", val_permissive_mode)
+ else S_YNO("aggressive-nsec:", aggressive_nsec)
+ else S_YNO("ignore-cd-flag:", ignore_cd)
++ else S_YNO("disable-edns-do:", disable_edns_do)
+ else if(strcmp(opt, "serve-expired:") == 0)
+ { IS_YES_OR_NO; cfg->serve_expired = (strcmp(val, "yes") == 0);
+ SERVE_EXPIRED = cfg->serve_expired; }
+@@ -684,7 +703,7 @@
+ else if(strcmp(opt, "serve-expired-reply-ttl:") == 0)
+ { IS_NUMBER_OR_ZERO; cfg->serve_expired_reply_ttl = atoi(val); SERVE_EXPIRED_REPLY_TTL=(time_t)cfg->serve_expired_reply_ttl;}
+ else S_NUMBER_OR_ZERO("serve-expired-client-timeout:", serve_expired_client_timeout)
+- else S_YNO("ede:", ede)
++ else S_YNO("ede:", ede)
+ else S_YNO("ede-serve-expired:", ede_serve_expired)
+ else S_YNO("serve-original-ttl:", serve_original_ttl)
+ else S_STR("val-nsec3-keysize-iterations:", val_nsec3_key_iterations)
+@@ -765,6 +784,10 @@
+ else S_POW2("dnscrypt-nonce-cache-slabs:",
+ dnscrypt_nonce_cache_slabs)
+ #endif
++ else if(strcmp(opt, "ip-ratelimit-cookie:") == 0) {
++ IS_NUMBER_OR_ZERO; cfg->ip_ratelimit_cookie = atoi(val);
++ infra_ip_ratelimit_cookie=cfg->ip_ratelimit_cookie;
++ }
+ else if(strcmp(opt, "ip-ratelimit:") == 0) {
+ IS_NUMBER_OR_ZERO; cfg->ip_ratelimit = atoi(val);
+ infra_ip_ratelimit=cfg->ip_ratelimit;
+@@ -800,6 +823,9 @@
+ { IS_NUMBER_OR_ZERO; cfg->ipsecmod_max_ttl = atoi(val); }
+ else S_YNO("ipsecmod-strict:", ipsecmod_strict)
+ #endif
++#ifdef USE_CACHEDB
++ else S_YNO("cachedb-no-store:", cachedb_no_store)
++#endif /* USE_CACHEDB */
+ else if(strcmp(opt, "define-tag:") ==0) {
+ return config_add_tag(cfg, val);
+ /* val_sig_skew_min, max and val_max_restart are copied into val_env
+@@ -812,7 +838,7 @@
+ { IS_NUMBER_OR_ZERO; cfg->val_max_restart = (int32_t)atoi(val); }
+ else if (strcmp(opt, "outgoing-interface:") == 0) {
+ char* d = strdup(val);
+- char** oi =
++ char** oi =
+ (char**)reallocarray(NULL, (size_t)cfg->num_out_ifs+1, sizeof(char*));
+ if(!d || !oi) { free(d); free(oi); return -1; }
+ if(cfg->out_ifs && cfg->num_out_ifs) {
+@@ -907,7 +933,7 @@
+ for(s=list; s; s=s->next)
+ total += strlen(s->str) + 1; /* len + newline */
+ left = total+1; /* one extra for nul at end */
+- r = malloc(left);
++ r = malloc(left);
+ if(!r)
+ return NULL;
+ w = r;
+@@ -986,7 +1012,7 @@
+ }
+
+ int
+-config_get_option(struct config_file* cfg, const char* opt,
++config_get_option(struct config_file* cfg, const char* opt,
+ void (*func)(char*,void*), void* arg)
+ {
+ char buf[1024], nopt[64];
+@@ -1062,6 +1088,7 @@
+ else O_DEC(opt, "tcp-reuse-timeout", tcp_reuse_timeout)
+ else O_YNO(opt, "edns-tcp-keepalive", do_tcp_keepalive)
+ else O_DEC(opt, "edns-tcp-keepalive-timeout", tcp_keepalive_timeout)
++ else O_DEC(opt, "sock-queue-timeout", sock_queue_timeout)
+ else O_YNO(opt, "ssl-upstream", ssl_upstream)
+ else O_YNO(opt, "tls-upstream", ssl_upstream)
+ else O_STR(opt, "ssl-service-key", ssl_service_key)
+@@ -1117,6 +1144,7 @@
+ else O_YNO(opt, "harden-below-nxdomain", harden_below_nxdomain)
+ else O_YNO(opt, "harden-referral-path", harden_referral_path)
+ else O_YNO(opt, "harden-algo-downgrade", harden_algo_downgrade)
++ else O_YNO(opt, "harden-unknown-additional", harden_unknown_additional)
+ else O_YNO(opt, "use-caps-for-id", use_caps_bits_for_id)
+ else O_LST(opt, "caps-whitelist", caps_whitelist)
+ else O_DEC(opt, "unwanted-reply-threshold", unwanted_threshold)
+@@ -1128,6 +1156,7 @@
+ else O_YNO(opt, "val-permissive-mode", val_permissive_mode)
+ else O_YNO(opt, "aggressive-nsec", aggressive_nsec)
+ else O_YNO(opt, "ignore-cd-flag", ignore_cd)
++ else O_YNO(opt, "disable-edns-do", disable_edns_do)
+ else O_YNO(opt, "serve-expired", serve_expired)
+ else O_DEC(opt, "serve-expired-ttl", serve_expired_ttl)
+ else O_YNO(opt, "serve-expired-ttl-reset", serve_expired_ttl_reset)
+@@ -1232,6 +1261,7 @@
+ else O_LST(opt, "python-script", python_script)
+ else O_LST(opt, "dynlib-file", dynlib_file)
+ else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check)
++ else O_DEC(opt, "ip-ratelimit-cookie", ip_ratelimit_cookie)
+ else O_DEC(opt, "ip-ratelimit", ip_ratelimit)
+ else O_DEC(opt, "ratelimit", ratelimit)
+ else O_MEM(opt, "ip-ratelimit-size", ip_ratelimit_size)
+@@ -1284,11 +1314,15 @@
+ #ifdef USE_CACHEDB
+ else O_STR(opt, "backend", cachedb_backend)
+ else O_STR(opt, "secret-seed", cachedb_secret)
++ else O_YNO(opt, "cachedb-no-store", cachedb_no_store)
+ #ifdef USE_REDIS
+ else O_STR(opt, "redis-server-host", redis_server_host)
+ else O_DEC(opt, "redis-server-port", redis_server_port)
++ else O_STR(opt, "redis-server-path", redis_server_path)
++ else O_STR(opt, "redis-server-password", redis_server_password)
+ else O_DEC(opt, "redis-timeout", redis_timeout)
+ else O_YNO(opt, "redis-expire-records", redis_expire_records)
++ else O_DEC(opt, "redis-logical-db", redis_logical_db)
+ #endif /* USE_REDIS */
+ #endif /* USE_CACHEDB */
+ #ifdef USE_IPSET
+@@ -1322,7 +1356,7 @@
+ init_cfg_parse();
+ }
+
+-int
++int
+ config_read(struct config_file* cfg, const char* filename, const char* chroot)
+ {
+ FILE *in;
+@@ -1362,7 +1396,7 @@
+ if(r == GLOB_NOMATCH) {
+ verbose(VERB_QUERY, "include: "
+ "no matches for %s", fname);
+- return 1;
++ return 1;
+ } else if(r == GLOB_NOSPACE) {
+ log_err("include: %s: "
+ "fnametern out of memory", fname);
+@@ -1561,7 +1595,7 @@
+ }
+ }
+
+-void
++void
+ config_delete(struct config_file* cfg)
+ {
+ if(!cfg) return;
+@@ -1638,6 +1672,7 @@
+ free(cfg->server_cert_file);
+ free(cfg->control_key_file);
+ free(cfg->control_cert_file);
++ free(cfg->nat64_prefix);
+ free(cfg->dns64_prefix);
+ config_delstrlist(cfg->dns64_ignore_aaaa);
+ free(cfg->dnstap_socket_path);
+@@ -1663,6 +1698,8 @@
+ free(cfg->cachedb_secret);
+ #ifdef USE_REDIS
+ free(cfg->redis_server_host);
++ free(cfg->redis_server_path);
++ free(cfg->redis_server_password);
+ #endif /* USE_REDIS */
+ #endif /* USE_CACHEDB */
+ #ifdef USE_IPSET
+@@ -1672,7 +1709,21 @@
+ free(cfg);
+ }
+
+-static void
++static void
++init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len)
++{
++ struct ub_randstate *rand = ub_initstate(NULL);
++
++ if (!rand)
++ fatal_exit("could not init random generator");
++ while (cookie_secret_len) {
++ *cookie_secret++ = (uint8_t)ub_random(rand);
++ cookie_secret_len--;
++ }
++ ub_randfree(rand);
++}
++
++static void
+ init_outgoing_availports(int* a, int num)
+ {
+ /* generated with make iana_update */
+@@ -1685,7 +1736,7 @@
+ for(i=1024; i<num; i++) {
+ a[i] = i;
+ }
+- /* create empty spot at 49152 to keep ephemeral ports available
++ /* create empty spot at 49152 to keep ephemeral ports available
+ * to other programs */
+ for(i=49152; i<49152+256; i++)
+ a[i] = 0;
+@@ -1696,7 +1747,7 @@
+ }
+ }
+
+-int
++int
+ cfg_mark_ports(const char* str, int allow, int* avail, int num)
+ {
+ char* mid = strchr(str, '-');
+@@ -1741,7 +1792,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_scan_ports(int* avail, int num)
+ {
+ int i;
+@@ -1858,7 +1909,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_region_strlist_insert(struct regional* region,
+ struct config_strlist** head, char* item)
+ {
+@@ -1891,7 +1942,7 @@
+ return NULL;
+ }
+
+-int
++int
+ cfg_strlist_insert(struct config_strlist** head, char* item)
+ {
+ struct config_strlist *s;
+@@ -1921,7 +1972,7 @@
+ return 0;
+ s->str = item;
+ s->next = NULL;
+-
++
+ if (*head==NULL) {
+ *head = s;
+ } else {
+@@ -1931,11 +1982,11 @@
+ }
+ last->next = s;
+ }
+-
+- return 1;
++
++ return 1;
+ }
+
+-int
++int
+ cfg_str2list_insert(struct config_str2list** head, char* item, char* i2)
+ {
+ struct config_str2list *s;
+@@ -1957,7 +2008,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_str3list_insert(struct config_str3list** head, char* item, char* i2,
+ char* i3)
+ {
+@@ -1993,7 +2044,7 @@
+ return 1;
+ }
+
+-time_t
++time_t
+ cfg_convert_timeval(const char* str)
+ {
+ time_t t;
+@@ -2001,7 +2052,7 @@
+ memset(&tm, 0, sizeof(tm));
+ if(strlen(str) < 14)
+ return 0;
+- if(sscanf(str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon,
++ if(sscanf(str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon,
+ &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
+ return 0;
+ tm.tm_year -= 1900;
+@@ -2018,7 +2069,7 @@
+ return t;
+ }
+
+-int
++int
+ cfg_count_numbers(const char* s)
+ {
+ /* format ::= (sp num)+ sp */
+@@ -2053,7 +2104,7 @@
+ return 1;
+ }
+
+-int
++int
+ cfg_parse_memsize(const char* str, size_t* res)
+ {
+ size_t len;
+@@ -2069,11 +2120,11 @@
+ /* check appended num */
+ while(len>0 && str[len-1]==' ')
+ len--;
+- if(len > 1 && str[len-1] == 'b')
++ if(len > 1 && str[len-1] == 'b')
+ len--;
+- else if(len > 1 && str[len-1] == 'B')
++ else if(len > 1 && str[len-1] == 'B')
+ len--;
+-
++
+ if(len > 1 && tolower((unsigned char)str[len-1]) == 'g')
+ mult = 1024*1024*1024;
+ else if(len > 1 && tolower((unsigned char)str[len-1]) == 'm')
+@@ -2160,7 +2211,7 @@
+ log_err("out of memory");
+ return 0;
+ }
+-
++
+ /* parse */
+ s = str;
+ while((p=strsep(&s, " \t\n")) != NULL) {
+@@ -2246,7 +2297,7 @@
+ return 0;
+ }
+
+-void
++void
+ config_apply(struct config_file* config)
+ {
+ MAX_TTL = (time_t)config->max_ttl;
+@@ -2288,7 +2339,7 @@
+ #endif
+ }
+
+-/**
++/**
+ * Calculate string length of full pathname in original filesys
+ * @param fname: the path name to convert.
+ * Must not be null or empty.
+@@ -2302,7 +2353,7 @@
+ {
+ size_t len = 0;
+ int slashit = 0;
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
+ strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
+ /* already full pathname, return it */
+ return strlen(fname);
+@@ -2325,8 +2376,8 @@
+ /* prepend chdir */
+ if(slashit && cfg->directory[0] != '/')
+ len++;
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
+- strncmp(cfg->chrootdir, cfg->directory,
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
++ strncmp(cfg->chrootdir, cfg->directory,
+ strlen(cfg->chrootdir)) == 0)
+ len += strlen(cfg->directory)-strlen(cfg->chrootdir);
+ else len += strlen(cfg->directory);
+@@ -2349,7 +2400,7 @@
+ return NULL;
+ buf[0] = 0;
+ /* is fname already in chroot ? */
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
+ strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
+ /* already full pathname, return it */
+ (void)strlcpy(buf, fname, len);
+@@ -2375,10 +2426,10 @@
+ if(slashit && cfg->directory[0] != '/')
+ (void)strlcat(buf, "/", len);
+ /* is the directory already in the chroot? */
+- if(cfg->chrootdir && cfg->chrootdir[0] &&
+- strncmp(cfg->chrootdir, cfg->directory,
++ if(cfg->chrootdir && cfg->chrootdir[0] &&
++ strncmp(cfg->chrootdir, cfg->directory,
+ strlen(cfg->chrootdir)) == 0)
+- (void)strlcat(buf, cfg->directory+strlen(cfg->chrootdir),
++ (void)strlcat(buf, cfg->directory+strlen(cfg->chrootdir),
+ len);
+ else (void)strlcat(buf, cfg->directory, len);
+ slashit = 1;
+@@ -2415,7 +2466,7 @@
+ return (sp>tab)?sp:tab;
+ }
+
+-int
++int
+ cfg_parse_local_zone(struct config_file* cfg, const char* val)
+ {
+ const char *type, *name_end, *name;
+@@ -2450,11 +2501,11 @@
+ }
+
+ if(strcmp(type, "nodefault")==0) {
+- return cfg_strlist_insert(&cfg->local_zones_nodefault,
++ return cfg_strlist_insert(&cfg->local_zones_nodefault,
+ strdup(name));
+ #ifdef USE_IPSET
+ } else if(strcmp(type, "ipset")==0) {
+- return cfg_strlist_insert(&cfg->local_zones_ipset,
++ return cfg_strlist_insert(&cfg->local_zones_ipset,
+ strdup(name));
+ #endif
+ } else {
+@@ -2509,7 +2560,7 @@
+ const char* hex = "0123456789abcdef";
+ char *p = buf;
+ int i;
+- memmove(ad, &((struct sockaddr_in6*)&addr)->sin6_addr,
++ memmove(ad, &((struct sockaddr_in6*)&addr)->sin6_addr,
+ sizeof(ad));
+ for(i=15; i>=0; i--) {
+ uint8_t b = ad[i];
+@@ -2521,7 +2572,7 @@
+ snprintf(buf+16*4, sizeof(buf)-16*4, "ip6.arpa. ");
+ } else {
+ uint8_t ad[4];
+- memmove(ad, &((struct sockaddr_in*)&addr)->sin_addr,
++ memmove(ad, &((struct sockaddr_in*)&addr)->sin_addr,
+ sizeof(ad));
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u.in-addr.arpa. ",
+ (unsigned)ad[3], (unsigned)ad[2],
+--- contrib/unbound/util/config_file.h.orig
++++ contrib/unbound/util/config_file.h
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -88,6 +88,8 @@
+ int do_ip4;
+ /** do ip6 query support. */
+ int do_ip6;
++ /** do nat64 on queries */
++ int do_nat64;
+ /** prefer ip4 upstream queries. */
+ int prefer_ip4;
+ /** prefer ip6 upstream queries. */
+@@ -116,6 +118,8 @@
+ int do_tcp_keepalive;
+ /** tcp keepalive timeout, in msec */
+ int tcp_keepalive_timeout;
++ /** timeout of packets sitting in the socket queue */
++ int sock_queue_timeout;
+ /** proxy protocol ports */
+ struct config_strlist* proxy_protocol_port;
+
+@@ -232,7 +236,7 @@
+ /** interface description strings (IP addresses) */
+ char **ifs;
+
+- /** number of outgoing interfaces to open.
++ /** number of outgoing interfaces to open.
+ * If 0 default all interfaces. */
+ int num_out_ifs;
+ /** outgoing interface description strings (IP addresses) */
+@@ -251,7 +255,7 @@
+ /** list of donotquery addresses, linked list */
+ struct config_strlist* donotqueryaddrs;
+ #ifdef CLIENT_SUBNET
+- /** list of servers we send edns-client-subnet option to and
++ /** list of servers we send edns-client-subnet option to and
+ * accept option from, linked list */
+ struct config_strlist* client_subnet;
+ /** list of zones we send edns-client-subnet option for */
+@@ -292,6 +296,9 @@
+ int harden_referral_path;
+ /** harden against algorithm downgrade */
+ int harden_algo_downgrade;
++ /** harden against unknown records in the authority section and in
++ * the additional section */
++ int harden_unknown_additional;
+ /** use 0x20 bits in query as random ID bits */
+ int use_caps_bits_for_id;
+ /** 0x20 whitelist, domains that do not use capsforid */
+@@ -364,7 +371,7 @@
+
+ /** the module configuration string */
+ char* module_conf;
+-
++
+ /** files with trusted DS and DNSKEYs in zonefile format, list */
+ struct config_strlist* trust_anchor_file_list;
+ /** list of trustanchor keys, linked list */
+@@ -389,7 +396,7 @@
+ /** max number of query restarts, number of IPs to probe */
+ int32_t val_max_restart;
+ /** this value sets the number of seconds before revalidating bogus */
+- int bogus_ttl;
++ int bogus_ttl;
+ /** should validator clean additional section for secure msgs */
+ int val_clean_additional;
+ /** log bogus messages by the validator */
+@@ -402,6 +409,8 @@
+ int aggressive_nsec;
+ /** ignore the CD flag in incoming queries and refuse them bogus data */
+ int ignore_cd;
++ /** disable EDNS DO flag in outgoing requests */
++ int disable_edns_do;
+ /** serve expired entries and prefetch them */
+ int serve_expired;
+ /** serve expired entries until TTL after expiration */
+@@ -535,6 +544,9 @@
+ /** ignore AAAAs for these domain names and use A record anyway */
+ struct config_strlist* dns64_ignore_aaaa;
+
++ /* NAT64 prefix; if unset defaults to dns64_prefix */
++ char* nat64_prefix;
++
+ /** true to enable dnstap support */
+ int dnstap;
+ /** using bidirectional frame streams if true */
+@@ -580,6 +592,9 @@
+
+ /** ratelimit for ip addresses. 0 is off, otherwise qps (unless overridden) */
+ int ip_ratelimit;
++ /** ratelimit for ip addresses with a valid DNS Cookie. 0 is off,
++ * otherwise qps (unless overridden) */
++ int ip_ratelimit_cookie;
+ /** number of slabs for ip_ratelimit cache */
+ size_t ip_ratelimit_slabs;
+ /** memory size in bytes for ip_ratelimit cache */
+@@ -686,17 +701,32 @@
+ char* cachedb_backend;
+ /** secret seed for hash key calculation */
+ char* cachedb_secret;
++ /** cachedb that does not store, but only reads from database, if on */
++ int cachedb_no_store;
+ #ifdef USE_REDIS
+ /** redis server's IP address or host name */
+ char* redis_server_host;
+ /** redis server's TCP port */
+ int redis_server_port;
++ /** redis server's unix path. Or "", NULL if unused */
++ char* redis_server_path;
++ /** redis server's AUTH password. Or "", NULL if unused */
++ char* redis_server_password;
+ /** timeout (in ms) for communication with the redis server */
+ int redis_timeout;
+ /** set timeout on redis records based on DNS response ttl */
+ int redis_expire_records;
++ /** set the redis logical database upon connection */
++ int redis_logical_db;
+ #endif
+ #endif
++ /** Downstream DNS Cookies */
++ /** do answer with server cookie when request contained cookie option */
++ int do_answer_cookie;
++ /** cookie secret */
++ uint8_t cookie_secret[40];
++ /** cookie secret length */
++ size_t cookie_secret_len;
+
+ /* ipset module */
+ #ifdef USE_IPSET
+@@ -809,7 +839,7 @@
+ struct config_strlist* local_zones_ipset;
+ #endif
+ /** Fallback to global local_zones when there is no match in the view
+- * view specific tree. 1 for yes, 0 for no */
++ * view specific tree. 1 for yes, 0 for no */
+ int isfirst;
+ /** predefined actions for particular IP address responses */
+ struct config_str2list* respip_actions;
+@@ -884,7 +914,7 @@
+ * @param config: where options are stored into, must be freshly created.
+ * @param filename: name of configfile. If NULL nothing is done.
+ * @param chroot: if not NULL, the chroot dir currently in use (for include).
+- * @return: false on error. In that case errno is set, ENOENT means
++ * @return: false on error. In that case errno is set, ENOENT means
+ * file not found.
+ */
+ int config_read(struct config_file* config, const char* filename,
+@@ -919,16 +949,16 @@
+ int config_set_option(struct config_file* config, const char* option,
+ const char* value);
+
+-/**
++/**
+ * Call print routine for the given option.
+ * @param cfg: config.
+- * @param opt: option name without trailing :.
++ * @param opt: option name without trailing :.
+ * This is different from config_set_option.
+ * @param func: print func, called as (str, arg) for every data element.
+ * @param arg: user argument for print func.
+ * @return false if the option name is not supported (syntax error).
+ */
+-int config_get_option(struct config_file* cfg, const char* opt,
++int config_get_option(struct config_file* cfg, const char* opt,
+ void (*func)(char*,void*), void* arg);
+
+ /**
+@@ -948,7 +978,7 @@
+ * @param str: string. malloced, caller must free it.
+ * @return 0=OK, 1=syntax error, 2=malloc failed.
+ */
+-int config_get_option_collate(struct config_file* cfg, const char* opt,
++int config_get_option_collate(struct config_file* cfg, const char* opt,
+ char** str);
+
+ /**
+@@ -1143,7 +1173,7 @@
+ * k=1024, m=1024*1024, g=1024*1024*1024.
+ * @param str: string
+ * @param res: result is stored here, size in bytes.
+- * @return: true if parsed correctly, or 0 on a parse error (and an error
++ * @return: true if parsed correctly, or 0 on a parse error (and an error
+ * is logged).
+ */
+ int cfg_parse_memsize(const char* str, size_t* res);
+@@ -1177,7 +1207,7 @@
+ /**
+ * parse taglist from string into bytestring with bitlist.
+ * @param cfg: the config structure (with tagnames)
+- * @param str: the string to parse. Parse puts 0 bytes in string.
++ * @param str: the string to parse. Parse puts 0 bytes in string.
+ * @param listlen: returns length of in bytes.
+ * @return malloced bytes with a bitlist of the tags. or NULL on parse error
+ * or malloc failure.
+@@ -1220,7 +1250,7 @@
+ * @param allow: give true if this range is permitted.
+ * @param avail: the array from cfg.
+ * @param num: size of the array (65536).
+- * @return: true if parsed correctly, or 0 on a parse error (and an error
++ * @return: true if parsed correctly, or 0 on a parse error (and an error
+ * is logged).
+ */
+ int cfg_mark_ports(const char* str, int allow, int* avail, int num);
+@@ -1248,7 +1278,7 @@
+ */
+ int cfg_scan_ports(int* avail, int num);
+
+-/**
++/**
+ * Convert a filename to full pathname in original filesys
+ * @param fname: the path name to convert.
+ * Must not be null or empty.
+@@ -1257,7 +1287,7 @@
+ * @return pointer to malloced buffer which is: [chroot][chdir]fname
+ * or NULL on malloc failure.
+ */
+-char* fname_after_chroot(const char* fname, struct config_file* cfg,
++char* fname_after_chroot(const char* fname, struct config_file* cfg,
+ int use_chdir);
+
+ /**
+@@ -1342,4 +1372,3 @@
+ #endif
+
+ #endif /* UTIL_CONFIG_FILE_H */
+-
+--- contrib/unbound/util/configlexer.lex.orig
++++ contrib/unbound/util/configlexer.lex
+@@ -210,9 +210,9 @@
+ %x quotedstring singlequotedstr include include_quoted val include_toplevel include_toplevel_quoted
+
+ %%
+-<INITIAL,val>{SPACE}* {
++<INITIAL,val>{SPACE}* {
+ LEXOUT(("SP ")); /* ignore */ }
+-<INITIAL,val>{SPACE}*{COMMENT}.* {
++<INITIAL,val>{SPACE}*{COMMENT}.* {
+ /* note that flex makes the longest match and '.' is any but not nl */
+ LEXOUT(("comment(%s) ", ub_c_text)); /* ignore */ }
+ server{COLON} { YDVAR(0, VAR_SERVER) }
+@@ -228,6 +228,7 @@
+ incoming-num-tcp{COLON} { YDVAR(1, VAR_INCOMING_NUM_TCP) }
+ do-ip4{COLON} { YDVAR(1, VAR_DO_IP4) }
+ do-ip6{COLON} { YDVAR(1, VAR_DO_IP6) }
++do-nat64{COLON} { YDVAR(1, VAR_DO_NAT64) }
+ prefer-ip4{COLON} { YDVAR(1, VAR_PREFER_IP4) }
+ prefer-ip6{COLON} { YDVAR(1, VAR_PREFER_IP6) }
+ do-udp{COLON} { YDVAR(1, VAR_DO_UDP) }
+@@ -241,6 +242,7 @@
+ tcp-auth-query-timeout{COLON} { YDVAR(1, VAR_TCP_AUTH_QUERY_TIMEOUT) }
+ edns-tcp-keepalive{COLON} { YDVAR(1, VAR_EDNS_TCP_KEEPALIVE) }
+ edns-tcp-keepalive-timeout{COLON} { YDVAR(1, VAR_EDNS_TCP_KEEPALIVE_TIMEOUT) }
++sock-queue-timeout{COLON} { YDVAR(1, VAR_SOCK_QUEUE_TIMEOUT) }
+ ssl-upstream{COLON} { YDVAR(1, VAR_SSL_UPSTREAM) }
+ tls-upstream{COLON} { YDVAR(1, VAR_SSL_UPSTREAM) }
+ ssl-service-key{COLON} { YDVAR(1, VAR_SSL_SERVICE_KEY) }
+@@ -317,6 +319,7 @@
+ harden-below-nxdomain{COLON} { YDVAR(1, VAR_HARDEN_BELOW_NXDOMAIN) }
+ harden-referral-path{COLON} { YDVAR(1, VAR_HARDEN_REFERRAL_PATH) }
+ harden-algo-downgrade{COLON} { YDVAR(1, VAR_HARDEN_ALGO_DOWNGRADE) }
++harden-unknown-additional{COLON} { YDVAR(1, VAR_HARDEN_UNKNOWN_ADDITIONAL) }
+ use-caps-for-id{COLON} { YDVAR(1, VAR_USE_CAPS_FOR_ID) }
+ caps-whitelist{COLON} { YDVAR(1, VAR_CAPS_WHITELIST) }
+ caps-exempt{COLON} { YDVAR(1, VAR_CAPS_WHITELIST) }
+@@ -401,6 +404,7 @@
+ val-permissive-mode{COLON} { YDVAR(1, VAR_VAL_PERMISSIVE_MODE) }
+ aggressive-nsec{COLON} { YDVAR(1, VAR_AGGRESSIVE_NSEC) }
+ ignore-cd-flag{COLON} { YDVAR(1, VAR_IGNORE_CD_FLAG) }
++disable-edns-do{COLON} { YDVAR(1, VAR_DISABLE_EDNS_DO) }
+ serve-expired{COLON} { YDVAR(1, VAR_SERVE_EXPIRED) }
+ serve-expired-ttl{COLON} { YDVAR(1, VAR_SERVE_EXPIRED_TTL) }
+ serve-expired-ttl-reset{COLON} { YDVAR(1, VAR_SERVE_EXPIRED_TTL_RESET) }
+@@ -414,7 +418,7 @@
+ key-cache-size{COLON} { YDVAR(1, VAR_KEY_CACHE_SIZE) }
+ key-cache-slabs{COLON} { YDVAR(1, VAR_KEY_CACHE_SLABS) }
+ neg-cache-size{COLON} { YDVAR(1, VAR_NEG_CACHE_SIZE) }
+-val-nsec3-keysize-iterations{COLON} {
++val-nsec3-keysize-iterations{COLON} {
+ YDVAR(1, VAR_VAL_NSEC3_KEYSIZE_ITERATIONS) }
+ zonemd-permissive-mode{COLON} { YDVAR(1, VAR_ZONEMD_PERMISSIVE_MODE) }
+ zonemd-check{COLON} { YDVAR(1, VAR_ZONEMD_CHECK) }
+@@ -463,6 +467,7 @@
+ dns64-prefix{COLON} { YDVAR(1, VAR_DNS64_PREFIX) }
+ dns64-synthall{COLON} { YDVAR(1, VAR_DNS64_SYNTHALL) }
+ dns64-ignore-aaaa{COLON} { YDVAR(1, VAR_DNS64_IGNORE_AAAA) }
++nat64-prefix{COLON} { YDVAR(1, VAR_NAT64_PREFIX) }
+ define-tag{COLON} { YDVAR(1, VAR_DEFINE_TAG) }
+ local-zone-tag{COLON} { YDVAR(2, VAR_LOCAL_ZONE_TAG) }
+ access-control-tag{COLON} { YDVAR(2, VAR_ACCESS_CONTROL_TAG) }
+@@ -504,6 +509,7 @@
+ YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES) }
+ disable-dnssec-lame-check{COLON} { YDVAR(1, VAR_DISABLE_DNSSEC_LAME_CHECK) }
+ ip-ratelimit{COLON} { YDVAR(1, VAR_IP_RATELIMIT) }
++ip-ratelimit-cookie{COLON} { YDVAR(1, VAR_IP_RATELIMIT_COOKIE) }
+ ratelimit{COLON} { YDVAR(1, VAR_RATELIMIT) }
+ ip-ratelimit-slabs{COLON} { YDVAR(1, VAR_IP_RATELIMIT_SLABS) }
+ ratelimit-slabs{COLON} { YDVAR(1, VAR_RATELIMIT_SLABS) }
+@@ -553,15 +559,21 @@
+ cachedb{COLON} { YDVAR(0, VAR_CACHEDB) }
+ backend{COLON} { YDVAR(1, VAR_CACHEDB_BACKEND) }
+ secret-seed{COLON} { YDVAR(1, VAR_CACHEDB_SECRETSEED) }
++cachedb-no-store{COLON} { YDVAR(1, VAR_CACHEDB_NO_STORE) }
+ redis-server-host{COLON} { YDVAR(1, VAR_CACHEDB_REDISHOST) }
+ redis-server-port{COLON} { YDVAR(1, VAR_CACHEDB_REDISPORT) }
++redis-server-path{COLON} { YDVAR(1, VAR_CACHEDB_REDISPATH) }
++redis-server-password{COLON} { YDVAR(1, VAR_CACHEDB_REDISPASSWORD) }
+ redis-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) }
+ redis-expire-records{COLON} { YDVAR(1, VAR_CACHEDB_REDISEXPIRERECORDS) }
++redis-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) }
+ ipset{COLON} { YDVAR(0, VAR_IPSET) }
+ name-v4{COLON} { YDVAR(1, VAR_IPSET_NAME_V4) }
+ name-v6{COLON} { YDVAR(1, VAR_IPSET_NAME_V6) }
+ udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM) }
+ tcp-connection-limit{COLON} { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) }
++answer-cookie{COLON} { YDVAR(1, VAR_ANSWER_COOKIE ) }
++cookie-secret{COLON} { YDVAR(1, VAR_COOKIE_SECRET) }
+ edns-client-string{COLON} { YDVAR(2, VAR_EDNS_CLIENT_STRING) }
+ edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
+ nsid{COLON} { YDVAR(1, VAR_NSID ) }
+--- contrib/unbound/util/configparser.y.orig
++++ contrib/unbound/util/configparser.y
+@@ -47,6 +47,7 @@
+ #include "util/configyyrename.h"
+ #include "util/config_file.h"
+ #include "util/net_help.h"
++#include "sldns/str2wire.h"
+
+ int ub_c_lex(void);
+ void ub_c_error(const char *message);
+@@ -73,9 +74,10 @@
+ %token VAR_FORCE_TOPLEVEL
+ %token VAR_SERVER VAR_VERBOSITY VAR_NUM_THREADS VAR_PORT
+ %token VAR_OUTGOING_RANGE VAR_INTERFACE VAR_PREFER_IP4
+-%token VAR_DO_IP4 VAR_DO_IP6 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP
++%token VAR_DO_IP4 VAR_DO_IP6 VAR_DO_NAT64 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP
+ %token VAR_TCP_MSS VAR_OUTGOING_TCP_MSS VAR_TCP_IDLE_TIMEOUT
+ %token VAR_EDNS_TCP_KEEPALIVE VAR_EDNS_TCP_KEEPALIVE_TIMEOUT
++%token VAR_SOCK_QUEUE_TIMEOUT
+ %token VAR_CHROOT VAR_USERNAME VAR_DIRECTORY VAR_LOGFILE VAR_PIDFILE
+ %token VAR_MSG_CACHE_SIZE VAR_MSG_CACHE_SLABS VAR_NUM_QUERIES_PER_THREAD
+ %token VAR_RRSET_CACHE_SIZE VAR_RRSET_CACHE_SLABS VAR_OUTGOING_NUM_TCP
+@@ -123,6 +125,7 @@
+ %token VAR_UNBLOCK_LAN_ZONES VAR_INSECURE_LAN_ZONES
+ %token VAR_INFRA_CACHE_MIN_RTT VAR_INFRA_CACHE_MAX_RTT VAR_INFRA_KEEP_PROBING
+ %token VAR_DNS64_PREFIX VAR_DNS64_SYNTHALL VAR_DNS64_IGNORE_AAAA
++%token VAR_NAT64_PREFIX
+ %token VAR_DNSTAP VAR_DNSTAP_ENABLE VAR_DNSTAP_SOCKET_PATH VAR_DNSTAP_IP
+ %token VAR_DNSTAP_TLS VAR_DNSTAP_TLS_SERVER_NAME VAR_DNSTAP_TLS_CERT_BUNDLE
+ %token VAR_DNSTAP_TLS_CLIENT_KEY_FILE VAR_DNSTAP_TLS_CLIENT_CERT_FILE
+@@ -175,12 +178,14 @@
+ %token VAR_IPSECMOD_MAX_TTL VAR_IPSECMOD_WHITELIST VAR_IPSECMOD_STRICT
+ %token VAR_CACHEDB VAR_CACHEDB_BACKEND VAR_CACHEDB_SECRETSEED
+ %token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISTIMEOUT
+-%token VAR_CACHEDB_REDISEXPIRERECORDS
++%token VAR_CACHEDB_REDISEXPIRERECORDS VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISPASSWORD
++%token VAR_CACHEDB_REDISLOGICALDB
+ %token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM
+ %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM
+ %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
+ %token VAR_FAST_SERVER_PERMIL VAR_FAST_SERVER_NUM
+ %token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
++%token VAR_ANSWER_COOKIE VAR_COOKIE_SECRET VAR_IP_RATELIMIT_COOKIE
+ %token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
+ %token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
+ %token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES VAR_TLS_USE_SNI
+@@ -194,6 +199,7 @@
+ %token VAR_INTERFACE_ACTION VAR_INTERFACE_VIEW VAR_INTERFACE_TAG
+ %token VAR_INTERFACE_TAG_ACTION VAR_INTERFACE_TAG_DATA
+ %token VAR_PROXY_PROTOCOL_PORT VAR_STATISTICS_INHIBIT_ZERO
++%token VAR_HARDEN_UNKNOWN_ADDITIONAL VAR_DISABLE_EDNS_DO VAR_CACHEDB_NO_STORE
+
+ %%
+ toplevelvars: /* empty */ | toplevelvars toplevelvar ;
+@@ -222,10 +228,11 @@
+ | ;
+ content_server: server_num_threads | server_verbosity | server_port |
+ server_outgoing_range | server_do_ip4 |
+- server_do_ip6 | server_prefer_ip4 | server_prefer_ip6 |
+- server_do_udp | server_do_tcp |
++ server_do_ip6 | server_do_nat64 | server_prefer_ip4 |
++ server_prefer_ip6 | server_do_udp | server_do_tcp |
+ server_tcp_mss | server_outgoing_tcp_mss | server_tcp_idle_timeout |
+ server_tcp_keepalive | server_tcp_keepalive_timeout |
++ server_sock_queue_timeout |
+ server_interface | server_chroot | server_username |
+ server_directory | server_logfile | server_pidfile |
+ server_msg_cache_size | server_msg_cache_slabs |
+@@ -273,6 +280,7 @@
+ server_so_reuseport | server_delay_close | server_udp_connect |
+ server_unblock_lan_zones | server_insecure_lan_zones |
+ server_dns64_prefix | server_dns64_synthall | server_dns64_ignore_aaaa |
++ server_nat64_prefix |
+ server_infra_cache_min_rtt | server_infra_cache_max_rtt | server_harden_algo_downgrade |
+ server_ip_transparent | server_ip_ratelimit | server_ratelimit |
+ server_ip_dscp | server_infra_keep_probing |
+@@ -302,7 +310,7 @@
+ server_serve_expired |
+ server_serve_expired_ttl | server_serve_expired_ttl_reset |
+ server_serve_expired_reply_ttl | server_serve_expired_client_timeout |
+- server_ede_serve_expired | server_serve_original_ttl | server_fake_dsa |
++ server_ede_serve_expired | server_serve_original_ttl | server_fake_dsa |
+ server_log_identity | server_use_systemd |
+ server_response_ip_tag | server_response_ip | server_response_ip_data |
+ server_shm_enable | server_shm_key | server_fake_sha1 |
+@@ -318,12 +326,14 @@
+ server_unknown_server_time_limit | server_log_tag_queryreply |
+ server_stream_wait_size | server_tls_ciphers |
+ server_tls_ciphersuites | server_tls_session_ticket_keys |
++ server_answer_cookie | server_cookie_secret | server_ip_ratelimit_cookie |
+ server_tls_use_sni | server_edns_client_string |
+ server_edns_client_string_opcode | server_nsid |
+ server_zonemd_permissive_mode | server_max_reuse_tcp_queries |
+ server_tcp_reuse_timeout | server_tcp_auth_query_timeout |
+ server_interface_automatic_ports | server_ede |
+- server_proxy_protocol_port | server_statistics_inhibit_zero
++ server_proxy_protocol_port | server_statistics_inhibit_zero |
++ server_harden_unknown_additional | server_disable_edns_do
+ ;
+ stubstart: VAR_STUB_ZONE
+ {
+@@ -486,7 +496,7 @@
+ rpzstart: VAR_RPZ
+ {
+ struct config_auth* s;
+- OUTYY(("\nP(rpz:)\n"));
++ OUTYY(("\nP(rpz:)\n"));
+ cfg_parser->started_toplevel = 1;
+ s = (struct config_auth*)calloc(1, sizeof(struct config_auth));
+ if(s) {
+@@ -502,7 +512,7 @@
+ }
+ }
+ ;
+-contents_rpz: contents_rpz content_rpz
++contents_rpz: contents_rpz content_rpz
+ | ;
+ content_rpz: auth_name | auth_zonefile | rpz_tag | auth_master | auth_url |
+ auth_allow_notify | rpz_action_override | rpz_cname_override |
+@@ -850,6 +860,15 @@
+ free($2);
+ }
+ ;
++server_do_nat64: VAR_DO_NAT64 STRING_ARG
++ {
++ OUTYY(("P(server_do_nat64:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->do_nat64 = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
+ server_do_udp: VAR_DO_UDP STRING_ARG
+ {
+ OUTYY(("P(server_do_udp:%s)\n", $2));
+@@ -972,6 +991,19 @@
+ free($2);
+ }
+ ;
++server_sock_queue_timeout: VAR_SOCK_QUEUE_TIMEOUT STRING_ARG
++ {
++ OUTYY(("P(server_sock_queue_timeout:%s)\n", $2));
++ if(atoi($2) == 0 && strcmp($2, "0") != 0)
++ yyerror("number expected");
++ else if (atoi($2) > 6553500)
++ cfg_parser->cfg->sock_queue_timeout = 6553500;
++ else if (atoi($2) < 1)
++ cfg_parser->cfg->sock_queue_timeout = 0;
++ else cfg_parser->cfg->sock_queue_timeout = atoi($2);
++ free($2);
++ }
++ ;
+ server_tcp_upstream: VAR_TCP_UPSTREAM STRING_ARG
+ {
+ OUTYY(("P(server_tcp_upstream:%s)\n", $2));
+@@ -1132,7 +1164,7 @@
+ yyerror("expected yes or no.");
+ else cfg_parser->cfg->http_nodelay = (strcmp($2, "yes")==0);
+ free($2);
+- }
++ };
+ server_http_notls_downstream: VAR_HTTP_NOTLS_DOWNSTREAM STRING_ARG
+ {
+ OUTYY(("P(server_http_notls_downstream:%s)\n", $2));
+@@ -1778,6 +1810,16 @@
+ free($2);
+ }
+ ;
++server_harden_unknown_additional: VAR_HARDEN_UNKNOWN_ADDITIONAL STRING_ARG
++ {
++ OUTYY(("P(server_harden_unknown_additional:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->harden_unknown_additional =
++ (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
+ server_use_caps_for_id: VAR_USE_CAPS_FOR_ID STRING_ARG
+ {
+ OUTYY(("P(server_use_caps_for_id:%s)\n", $2));
+@@ -2019,6 +2061,15 @@
+ free($2);
+ }
+ ;
++server_disable_edns_do: VAR_DISABLE_EDNS_DO STRING_ARG
++ {
++ OUTYY(("P(server_disable_edns_do:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->disable_edns_do = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
+ server_serve_expired: VAR_SERVE_EXPIRED STRING_ARG
+ {
+ OUTYY(("P(server_serve_expired:%s)\n", $2));
+@@ -2169,6 +2220,7 @@
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
++ ;
+ server_key_cache_size: VAR_KEY_CACHE_SIZE STRING_ARG
+ {
+ OUTYY(("P(server_key_cache_size:%s)\n", $2));
+@@ -2206,6 +2258,7 @@
+ strcmp($3, "transparent")!=0 && strcmp($3, "nodefault")!=0
+ && strcmp($3, "typetransparent")!=0
+ && strcmp($3, "always_transparent")!=0
++ && strcmp($3, "block_a")!=0
+ && strcmp($3, "always_refuse")!=0
+ && strcmp($3, "always_nxdomain")!=0
+ && strcmp($3, "always_nodata")!=0
+@@ -2218,7 +2271,7 @@
+ yyerror("local-zone type: expected static, deny, "
+ "refuse, redirect, transparent, "
+ "typetransparent, inform, inform_deny, "
+- "inform_redirect, always_transparent, "
++ "inform_redirect, always_transparent, block_a,"
+ "always_refuse, always_nxdomain, "
+ "always_nodata, always_deny, always_null, "
+ "noview, nodefault or ipset");
+@@ -2333,6 +2386,13 @@
+ fatal_exit("out of memory adding dns64-ignore-aaaa");
+ }
+ ;
++server_nat64_prefix: VAR_NAT64_PREFIX STRING_ARG
++ {
++ OUTYY(("P(nat64_prefix:%s)\n", $2));
++ free(cfg_parser->cfg->nat64_prefix);
++ cfg_parser->cfg->nat64_prefix = $2;
++ }
++ ;
+ server_define_tag: VAR_DEFINE_TAG STRING_ARG
+ {
+ char* p, *s = $2;
+@@ -2518,6 +2578,15 @@
+ free($2);
+ }
+ ;
++server_ip_ratelimit_cookie: VAR_IP_RATELIMIT_COOKIE STRING_ARG
++ {
++ OUTYY(("P(server_ip_ratelimit_cookie:%s)\n", $2));
++ if(atoi($2) == 0 && strcmp($2, "0") != 0)
++ yyerror("number expected");
++ else cfg_parser->cfg->ip_ratelimit_cookie = atoi($2);
++ free($2);
++ }
++ ;
+ server_ratelimit: VAR_RATELIMIT STRING_ARG
+ {
+ OUTYY(("P(server_ratelimit:%s)\n", $2));
+@@ -2713,7 +2782,7 @@
+ OUTYY(("P(server_pad_responses:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+- else cfg_parser->cfg->pad_responses =
++ else cfg_parser->cfg->pad_responses =
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
+@@ -2732,7 +2801,7 @@
+ OUTYY(("P(server_pad_queries:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+- else cfg_parser->cfg->pad_queries =
++ else cfg_parser->cfg->pad_queries =
+ (strcmp($2, "yes")==0);
+ free($2);
+ }
+@@ -3471,9 +3540,10 @@
+ if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, $2))
+ yyerror("out of memory");
+ }
++ ;
+ dynlibstart: VAR_DYNLIB
+- {
+- OUTYY(("\nP(dynlib:)\n"));
++ {
++ OUTYY(("\nP(dynlib:)\n"));
+ cfg_parser->started_toplevel = 1;
+ }
+ ;
+@@ -3487,6 +3557,7 @@
+ if(!cfg_strlist_append_ex(&cfg_parser->cfg->dynlib_file, $2))
+ yyerror("out of memory");
+ }
++ ;
+ server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG
+ {
+ OUTYY(("P(disable_dnssec_lame_check:%s)\n", $2));
+@@ -3547,7 +3618,6 @@
+ free($2);
+ }
+ ;
+-
+ dnsc_dnscrypt_port: VAR_DNSCRYPT_PORT STRING_ARG
+ {
+ OUTYY(("P(dnsc_dnscrypt_port:%s)\n", $2));
+@@ -3641,7 +3711,8 @@
+ | ;
+ content_cachedb: cachedb_backend_name | cachedb_secret_seed |
+ redis_server_host | redis_server_port | redis_timeout |
+- redis_expire_records
++ redis_expire_records | redis_server_path | redis_server_password |
++ cachedb_no_store | redis_logical_db
+ ;
+ cachedb_backend_name: VAR_CACHEDB_BACKEND STRING_ARG
+ {
+@@ -3667,6 +3738,19 @@
+ #endif
+ }
+ ;
++cachedb_no_store: VAR_CACHEDB_NO_STORE STRING_ARG
++ {
++ #ifdef USE_CACHEDB
++ OUTYY(("P(cachedb_no_store:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->cachedb_no_store = (strcmp($2, "yes")==0);
++ #else
++ OUTYY(("P(Compiled without cachedb, ignoring)\n"));
++ #endif
++ free($2);
++ }
++ ;
+ redis_server_host: VAR_CACHEDB_REDISHOST STRING_ARG
+ {
+ #if defined(USE_CACHEDB) && defined(USE_REDIS)
+@@ -3694,6 +3778,30 @@
+ free($2);
+ }
+ ;
++redis_server_path: VAR_CACHEDB_REDISPATH STRING_ARG
++ {
++ #if defined(USE_CACHEDB) && defined(USE_REDIS)
++ OUTYY(("P(redis_server_path:%s)\n", $2));
++ free(cfg_parser->cfg->redis_server_path);
++ cfg_parser->cfg->redis_server_path = $2;
++ #else
++ OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
++ free($2);
++ #endif
++ }
++ ;
++redis_server_password: VAR_CACHEDB_REDISPASSWORD STRING_ARG
++ {
++ #if defined(USE_CACHEDB) && defined(USE_REDIS)
++ OUTYY(("P(redis_server_password:%s)\n", $2));
++ free(cfg_parser->cfg->redis_server_password);
++ cfg_parser->cfg->redis_server_password = $2;
++ #else
++ OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
++ free($2);
++ #endif
++ }
++ ;
+ redis_timeout: VAR_CACHEDB_REDISTIMEOUT STRING_ARG
+ {
+ #if defined(USE_CACHEDB) && defined(USE_REDIS)
+@@ -3720,6 +3828,21 @@
+ free($2);
+ }
+ ;
++redis_logical_db: VAR_CACHEDB_REDISLOGICALDB STRING_ARG
++ {
++ #if defined(USE_CACHEDB) && defined(USE_REDIS)
++ int db;
++ OUTYY(("P(redis_logical_db:%s)\n", $2));
++ db = atoi($2);
++ if((db == 0 && strcmp($2, "0") != 0) || db < 0)
++ yyerror("valid redis logical database index expected");
++ else cfg_parser->cfg->redis_logical_db = db;
++ #else
++ OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
++ #endif
++ free($2);
++ }
++ ;
+ server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG
+ {
+ OUTYY(("P(server_tcp_connection_limit:%s %s)\n", $2, $3));
+@@ -3731,6 +3854,31 @@
+ }
+ }
+ ;
++server_answer_cookie: VAR_ANSWER_COOKIE STRING_ARG
++ {
++ OUTYY(("P(server_answer_cookie:%s)\n", $2));
++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
++ yyerror("expected yes or no.");
++ else cfg_parser->cfg->do_answer_cookie = (strcmp($2, "yes")==0);
++ free($2);
++ }
++ ;
++server_cookie_secret: VAR_COOKIE_SECRET STRING_ARG
++ {
++ uint8_t secret[32];
++ size_t secret_len = sizeof(secret);
++
++ OUTYY(("P(server_cookie_secret:%s)\n", $2));
++ if(sldns_str2wire_hex_buf($2, secret, &secret_len)
++ || (secret_len != 16))
++ yyerror("expected 128 bit hex string");
++ else {
++ cfg_parser->cfg->cookie_secret_len = secret_len;
++ memcpy(cfg_parser->cfg->cookie_secret, secret, sizeof(secret));
++ }
++ free($2);
++ }
++ ;
+ ipsetstart: VAR_IPSET
+ {
+ OUTYY(("\nP(ipset:)\n"));
+@@ -3800,10 +3948,11 @@
+ strcmp(action, "refuse_non_local")!=0 &&
+ strcmp(action, "allow_setrd")!=0 &&
+ strcmp(action, "allow")!=0 &&
+- strcmp(action, "allow_snoop")!=0)
++ strcmp(action, "allow_snoop")!=0 &&
++ strcmp(action, "allow_cookie")!=0)
+ {
+ yyerror("expected deny, refuse, deny_non_local, "
+- "refuse_non_local, allow, allow_setrd or "
+- "allow_snoop as access control action");
++ "refuse_non_local, allow, allow_setrd, "
++ "allow_snoop or allow_cookie as access control action");
+ }
+ }
+--- contrib/unbound/util/data/msgencode.c.orig
++++ contrib/unbound/util/data/msgencode.c
+@@ -806,6 +806,95 @@
+ return 1 + 2 + 2 + 4 + 2 + rdatalen;
+ }
+
++uint16_t
++calc_edns_option_size(struct edns_data* edns, uint16_t code)
++{
++ size_t rdatalen = 0;
++ struct edns_option* opt;
++ if(!edns || !edns->edns_present)
++ return 0;
++ for(opt = edns->opt_list_inplace_cb_out; opt; opt = opt->next) {
++ if(opt->opt_code == code)
++ rdatalen += 4 + opt->opt_len;
++ }
++ for(opt = edns->opt_list_out; opt; opt = opt->next) {
++ if(opt->opt_code == code)
++ rdatalen += 4 + opt->opt_len;
++ }
++ return rdatalen;
++}
++
++uint16_t
++calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size)
++{
++ size_t rdatalen = 0;
++ struct edns_option* opt;
++ *txt_size = 0;
++ if(!edns || !edns->edns_present)
++ return 0;
++ for(opt = edns->opt_list_inplace_cb_out; opt; opt = opt->next) {
++ if(opt->opt_code == LDNS_EDNS_EDE) {
++ rdatalen += 4 + opt->opt_len;
++ if(opt->opt_len > 2) *txt_size += opt->opt_len - 2;
++ if(opt->opt_len >= 2 && sldns_read_uint16(
++ opt->opt_data) == LDNS_EDE_OTHER) {
++ *txt_size += 4 + 2;
++ }
++ }
++ }
++ for(opt = edns->opt_list_out; opt; opt = opt->next) {
++ if(opt->opt_code == LDNS_EDNS_EDE) {
++ rdatalen += 4 + opt->opt_len;
++ if(opt->opt_len > 2) *txt_size += opt->opt_len - 2;
++ if(opt->opt_len >= 2 && sldns_read_uint16(
++ opt->opt_data) == LDNS_EDE_OTHER) {
++ *txt_size += 4 + 2;
++ }
++ }
++ }
++ return rdatalen;
++}
++
++/* Trims the EDE OPTION-DATA to not include any EXTRA-TEXT data.
++ * Also removes any LDNS_EDE_OTHER options from the list since they are useless
++ * without the extra text. */
++static void
++ede_trim_text(struct edns_option** list)
++{
++ struct edns_option* curr, *prev = NULL;
++ if(!list || !(*list)) return;
++ /* Unlink and repoint if LDNS_EDE_OTHER are first in list */
++ while(list && *list && (*list)->opt_code == LDNS_EDNS_EDE
++ && (*list)->opt_len >= 2
++ && sldns_read_uint16((*list)->opt_data) == LDNS_EDE_OTHER ) {
++ *list = (*list)->next;
++ }
++ if(!list || !(*list)) return;
++ curr = *list;
++ while(curr) {
++ if(curr->opt_code == LDNS_EDNS_EDE) {
++ if(curr->opt_len >= 2 && sldns_read_uint16(
++ curr->opt_data) == LDNS_EDE_OTHER) {
++ /* LDNS_EDE_OTHER cannot be the first option in
++ * this while, so prev is always initialized at
++ * this point from the other branches;
++ * cut this option off */
++ prev->next = curr->next;
++ curr = curr->next;
++ } else if(curr->opt_len > 2) {
++ /* trim this option's EXTRA-TEXT */
++ curr->opt_len = 2;
++ prev = curr;
++ curr = curr->next;
++ }
++ } else {
++ /* continue */
++ prev = curr;
++ curr = curr->next;
++ }
++ }
++}
++
+ static void
+ attach_edns_record_max_msg_sz(sldns_buffer* pkt, struct edns_data* edns,
+ uint16_t max_msg_sz)
+@@ -894,6 +983,7 @@
+ {
+ uint16_t flags;
+ unsigned int attach_edns = 0;
++ uint16_t edns_field_size, ede_size, ede_txt_size;
+
+ if(!cached || rep->authoritative) {
+ /* original flags, copy RD and CD bits from query. */
+@@ -916,25 +1006,41 @@
+ log_assert(flags & BIT_QR); /* QR bit must be on in our replies */
+ if(udpsize < LDNS_HEADER_SIZE)
+ return 0;
++ /* currently edns does not change during calculations;
++ * calculate sizes once here */
++ edns_field_size = calc_edns_field_size(edns);
++ ede_size = calc_ede_option_size(edns, &ede_txt_size);
+ if(sldns_buffer_capacity(pkt) < udpsize)
+ udpsize = sldns_buffer_capacity(pkt);
+- if(udpsize < LDNS_HEADER_SIZE + calc_edns_field_size(edns)) {
++ if(!edns || !edns->edns_present) {
++ attach_edns = 0;
++ /* EDEs are optional, try to fit anything else before them */
++ } else if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) {
+ /* packet too small to contain edns, omit it. */
+ attach_edns = 0;
+ } else {
+ /* reserve space for edns record */
+- attach_edns = (unsigned int)calc_edns_field_size(edns);
+- udpsize -= attach_edns;
++ attach_edns = (unsigned int)edns_field_size - ede_size;
+ }
+
+ if(!reply_info_encode(qinf, rep, id, flags, pkt, timenow, region,
+- udpsize, dnssec, MINIMAL_RESPONSES)) {
++ udpsize - attach_edns, dnssec, MINIMAL_RESPONSES)) {
+ log_err("reply encode: out of memory");
+ return 0;
+ }
+- if(attach_edns && sldns_buffer_capacity(pkt) >=
+- sldns_buffer_limit(pkt)+attach_edns)
+- attach_edns_record_max_msg_sz(pkt, edns, udpsize+attach_edns);
++ if(attach_edns) {
++ if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size)
++ attach_edns_record_max_msg_sz(pkt, edns, udpsize);
++ else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) {
++ ede_trim_text(&edns->opt_list_inplace_cb_out);
++ ede_trim_text(&edns->opt_list_out);
++ attach_edns_record_max_msg_sz(pkt, edns, udpsize);
++ } else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) {
++ edns_opt_list_remove(&edns->opt_list_inplace_cb_out, LDNS_EDNS_EDE);
++ edns_opt_list_remove(&edns->opt_list_out, LDNS_EDNS_EDE);
++ attach_edns_record_max_msg_sz(pkt, edns, udpsize);
++ }
++ }
+ return 1;
+ }
+
+@@ -958,15 +1064,17 @@
+ sldns_buffer_flip(pkt);
+ }
+
+-void
+-error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
+- uint16_t qid, uint16_t qflags, struct edns_data* edns)
++void
++extended_error_encode(sldns_buffer* buf, uint16_t rcode,
++ struct query_info* qinfo, uint16_t qid, uint16_t qflags,
++ uint16_t xflags, struct edns_data* edns)
+ {
+ uint16_t flags;
+
+ sldns_buffer_clear(buf);
+ sldns_buffer_write(buf, &qid, sizeof(uint16_t));
+- flags = (uint16_t)(BIT_QR | BIT_RA | r); /* QR and retcode*/
++ flags = (uint16_t)(BIT_QR | BIT_RA | (rcode & 0xF)); /* QR and retcode*/
++ flags |= xflags;
+ flags |= (qflags & (BIT_RD|BIT_CD)); /* copy RD and CD bit */
+ sldns_buffer_write_u16(buf, flags);
+ if(qinfo) flags = 1;
+@@ -993,11 +1101,25 @@
+ struct edns_data es = *edns;
+ es.edns_version = EDNS_ADVERTISED_VERSION;
+ es.udp_size = EDNS_ADVERTISED_SIZE;
+- es.ext_rcode = 0;
++ es.ext_rcode = (uint8_t)(rcode >> 4);
+ es.bits &= EDNS_DO;
+ if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
+- edns->udp_size)
+- return;
++ edns->udp_size) {
++ edns_opt_list_remove(&es.opt_list_inplace_cb_out, LDNS_EDNS_EDE);
++ edns_opt_list_remove(&es.opt_list_out, LDNS_EDNS_EDE);
++ if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
++ edns->udp_size) {
++ return;
++ }
++ }
+ attach_edns_record(buf, &es);
+ }
+ }
++
++void
++error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
++ uint16_t qid, uint16_t qflags, struct edns_data* edns)
++{
++ extended_error_encode(buf, (r & 0x000F), qinfo, qid, qflags,
++ (r & 0xFFF0), edns);
++}
+--- contrib/unbound/util/data/msgencode.h.orig
++++ contrib/unbound/util/data/msgencode.h
+@@ -108,6 +108,27 @@
+ */
+ uint16_t calc_edns_field_size(struct edns_data* edns);
+
++/**
++ * Calculate the size of a specific EDNS option in packet.
++ * @param edns: edns data or NULL.
++ * @param code: the opt code to get the size of.
++ * @return octets the option will take up.
++ */
++uint16_t calc_edns_option_size(struct edns_data* edns, uint16_t code);
++
++/**
++ * Calculate the size of the EDE option(s) in packet. Also calculate seperately
++ * the size of the EXTRA-TEXT field(s) in case we can trim them to fit.
++ * In this case include any LDNS_EDE_OTHER options in their entirety since they
++ * are useless without extra text.
++ * @param edns: edns data or NULL.
++ * @param txt_size: the size of the EXTRA-TEXT field(s); this includes
++ * LDNS_EDE_OTHER in their entirety since they are useless without
++ * extra text.
++ * @return octets the option will take up.
++ */
++uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size);
++
+ /**
+ * Attach EDNS record to buffer. Buffer has complete packet. There must
+ * be enough room left for the EDNS record.
+@@ -116,11 +137,11 @@
+ */
+ void attach_edns_record(struct sldns_buffer* pkt, struct edns_data* edns);
+
+-/**
++/**
+ * Encode an error. With QR and RA set.
+ *
+ * @param pkt: where to store the packet.
+- * @param r: RCODE value to encode.
++ * @param r: RCODE value to encode (may contain extra flags).
+ * @param qinfo: if not NULL, the query is included.
+ * @param qid: query ID to set in packet. network order.
+ * @param qflags: original query flags (to copy RD and CD bits). host order.
+@@ -130,4 +151,21 @@
+ void error_encode(struct sldns_buffer* pkt, int r, struct query_info* qinfo,
+ uint16_t qid, uint16_t qflags, struct edns_data* edns);
+
++/**
++ * Encode an extended error. With QR and RA set.
++ *
++ * @param pkt: where to store the packet.
++ * @param rcode: Extended RCODE value to encode.
++ * @param qinfo: if not NULL, the query is included.
++ * @param qid: query ID to set in packet. network order.
++ * @param qflags: original query flags (to copy RD and CD bits). host order.
++ * @param xflags: extra flags to set (such as for example BIT_AA and/or BIT_TC)
++ * @param edns: if not NULL, this is the query edns info,
++ * and an edns reply is attached. Only attached if EDNS record fits reply.
++ * Without edns extended errors (i.e. > 15) will not be conveyed.
++ */
++void extended_error_encode(struct sldns_buffer* pkt, uint16_t rcode,
++ struct query_info* qinfo, uint16_t qid, uint16_t qflags,
++ uint16_t xflags, struct edns_data* edns);
++
+ #endif /* UTIL_DATA_MSGENCODE_H */
+--- contrib/unbound/util/data/msgparse.c.orig
++++ contrib/unbound/util/data/msgparse.c
+@@ -45,6 +45,9 @@
+ #include "util/netevent.h"
+ #include "util/storage/lookup3.h"
+ #include "util/regional.h"
++#include "util/rfc_1982.h"
++#include "util/edns.h"
++#include "util/net_help.h"
+ #include "sldns/rrdef.h"
+ #include "sldns/sbuffer.h"
+ #include "sldns/parseutil.h"
+@@ -940,22 +943,11 @@
+ return 0;
+ }
+
+-static int
+-edns_opt_list_append_keepalive(struct edns_option** list, int msec,
+- struct regional* region)
+-{
+- uint8_t data[2]; /* For keepalive value */
+- data[0] = (uint8_t)((msec >> 8) & 0xff);
+- data[1] = (uint8_t)(msec & 0xff);
+- return edns_opt_list_append(list, LDNS_EDNS_KEEPALIVE, sizeof(data),
+- data, region);
+-}
+-
+ /** parse EDNS options from EDNS wireformat rdata */
+ static int
+ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
+ struct edns_data* edns, struct config_file* cfg, struct comm_point* c,
+- struct regional* region)
++ struct comm_reply* repinfo, uint32_t now, struct regional* region)
+ {
+ /* To respond with a Keepalive option, the client connection must have
+ * received one message with a TCP Keepalive EDNS option, and that
+@@ -979,6 +971,10 @@
+ while(rdata_len >= 4) {
+ uint16_t opt_code = sldns_read_uint16(rdata_ptr);
+ uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
++ uint8_t server_cookie[40];
++ enum edns_cookie_val_status cookie_val_status;
++ int cookie_is_v4 = 1;
++
+ rdata_ptr += 4;
+ rdata_len -= 4;
+ if(opt_len > rdata_len)
+@@ -1041,6 +1037,76 @@
+ edns->padding_block_size = cfg->pad_responses_block_size;
+ break;
+
++ case LDNS_EDNS_COOKIE:
++ if(!cfg || !cfg->do_answer_cookie || !repinfo)
++ break;
++ if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
++ verbose(VERB_ALGO, "worker request: "
++ "badly formatted cookie");
++ return LDNS_RCODE_FORMERR;
++ }
++ edns->cookie_present = 1;
++
++ /* Copy client cookie, version and timestamp for
++ * validation and creation purposes.
++ */
++ if(opt_len >= 16) {
++ memmove(server_cookie, rdata_ptr, 16);
++ } else {
++ memset(server_cookie, 0, 16);
++ memmove(server_cookie, rdata_ptr, opt_len);
++ }
++
++ /* Copy client ip for validation and creation
++ * purposes. It will be overwritten if (re)creation
++ * is needed.
++ */
++ if(repinfo->remote_addr.ss_family == AF_INET) {
++ memcpy(server_cookie + 16,
++ &((struct sockaddr_in*)&repinfo->remote_addr)->sin_addr, 4);
++ } else {
++ cookie_is_v4 = 0;
++ memcpy(server_cookie + 16,
++ &((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
++ }
++
++ cookie_val_status = edns_cookie_server_validate(
++ rdata_ptr, opt_len, cfg->cookie_secret,
++ cfg->cookie_secret_len, cookie_is_v4,
++ server_cookie, now);
++ switch(cookie_val_status) {
++ case COOKIE_STATUS_VALID:
++ case COOKIE_STATUS_VALID_RENEW:
++ edns->cookie_valid = 1;
++ /* Reuse cookie */
++ if(!edns_opt_list_append(
++ &edns->opt_list_out, LDNS_EDNS_COOKIE,
++ opt_len, rdata_ptr, region)) {
++ log_err("out of memory");
++ return LDNS_RCODE_SERVFAIL;
++ }
++ /* Cookie to be reused added to outgoing
++ * options. Done!
++ */
++ break;
++ case COOKIE_STATUS_CLIENT_ONLY:
++ edns->cookie_client = 1;
++ /* fallthrough */
++ case COOKIE_STATUS_FUTURE:
++ case COOKIE_STATUS_EXPIRED:
++ case COOKIE_STATUS_INVALID:
++ default:
++ edns_cookie_server_write(server_cookie,
++ cfg->cookie_secret, cookie_is_v4, now);
++ if(!edns_opt_list_append(&edns->opt_list_out,
++ LDNS_EDNS_COOKIE, 24, server_cookie,
++ region)) {
++ log_err("out of memory");
++ return LDNS_RCODE_SERVFAIL;
++ }
++ break;
++ }
++ break;
+ default:
+ break;
+ }
+@@ -1115,6 +1181,8 @@
+ edns->opt_list_out = NULL;
+ edns->opt_list_inplace_cb_out = NULL;
+ edns->padding_block_size = 0;
++ edns->cookie_present = 0;
++ edns->cookie_valid = 0;
+
+ /* take the options */
+ rdata_len = found->rr_first->size-2;
+@@ -1170,7 +1238,8 @@
+
+ int
+ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
+- struct config_file* cfg, struct comm_point* c, struct regional* region)
++ struct config_file* cfg, struct comm_point* c,
++ struct comm_reply* repinfo, time_t now, struct regional* region)
+ {
+ size_t rdata_len;
+ uint8_t* rdata_ptr;
+@@ -1206,6 +1275,8 @@
+ edns->opt_list_out = NULL;
+ edns->opt_list_inplace_cb_out = NULL;
+ edns->padding_block_size = 0;
++ edns->cookie_present = 0;
++ edns->cookie_valid = 0;
+
+ /* take the options */
+ rdata_len = sldns_buffer_read_u16(pkt);
+@@ -1214,7 +1285,7 @@
+ rdata_ptr = sldns_buffer_current(pkt);
+ /* ignore rrsigs */
+ return parse_edns_options_from_query(rdata_ptr, rdata_len, edns, cfg,
+- c, region);
++ c, repinfo, now, region);
+ }
+
+ void
+@@ -1236,3 +1307,27 @@
+ }
+ }
+
++/** remove RR from msgparse RRset, return true if rrset is entirely bad */
++int
++msgparse_rrset_remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* rrset,
++ struct rr_parse* prev, struct rr_parse* rr, struct sockaddr_storage* addr, socklen_t addrlen)
++{
++ if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN && str) {
++ uint8_t buf[LDNS_MAX_DOMAINLEN+1];
++ dname_pkt_copy(pkt, buf, rrset->dname);
++ if(addr)
++ log_name_addr(VERB_QUERY, str, buf, addr, addrlen);
++ else log_nametypeclass(VERB_QUERY, str, buf,
++ rrset->type, ntohs(rrset->rrset_class));
++ }
++ if(prev)
++ prev->next = rr->next;
++ else rrset->rr_first = rr->next;
++ if(rrset->rr_last == rr)
++ rrset->rr_last = prev;
++ rrset->rr_count --;
++ rrset->size -= rr->size;
++ /* rr struct still exists, but is unlinked, so that in the for loop
++ * the rr->next works fine to continue. */
++ return rrset->rr_count == 0;
++}
+--- contrib/unbound/util/data/msgparse.h.orig
++++ contrib/unbound/util/data/msgparse.h
+@@ -72,6 +72,7 @@
+ struct edns_option;
+ struct config_file;
+ struct comm_point;
++struct comm_reply;
+
+ /** number of buckets in parse rrset hash table. Must be power of 2. */
+ #define PARSE_TABLE_SIZE 32
+@@ -217,8 +218,6 @@
+ * region.
+ */
+ struct edns_data {
+- /** if EDNS OPT record was present */
+- int edns_present;
+ /** Extended RCODE */
+ uint8_t ext_rcode;
+ /** The EDNS version number */
+@@ -238,7 +237,15 @@
+ struct edns_option* opt_list_inplace_cb_out;
+ /** block size to pad */
+ uint16_t padding_block_size;
+-};
++ /** if EDNS OPT record was present */
++ unsigned int edns_present : 1;
++ /** if a cookie was present */
++ unsigned int cookie_present : 1;
++ /** if the cookie validated */
++ unsigned int cookie_valid : 1;
++ /** if the cookie holds only the client part */
++ unsigned int cookie_client : 1;
++};
+
+ /**
+ * EDNS option
+@@ -310,12 +317,15 @@
+ * initialised.
+ * @param cfg: the configuration (with nsid value etc.)
+ * @param c: commpoint to determine transport (if needed)
++ * @param repinfo: commreply to determine the client address
++ * @param now: current time
+ * @param region: region to alloc results in (edns option contents)
+ * @return: 0 on success, or an RCODE on error.
+ * RCODE formerr if OPT is badly formatted and so on.
+ */
+ int parse_edns_from_query_pkt(struct sldns_buffer* pkt, struct edns_data* edns,
+- struct config_file* cfg, struct comm_point* c, struct regional* region);
++ struct config_file* cfg, struct comm_point* c,
++ struct comm_reply* repinfo, time_t now, struct regional* region);
+
+ /**
+ * Calculate hash value for rrset in packet.
+@@ -361,4 +371,22 @@
+ void log_edns_opt_list(enum verbosity_value level, const char* info_str,
+ struct edns_option* list);
+
++/**
++ * Remove RR from msgparse RRset.
++ * @param str: this string is used for logging if verbose. If NULL, there is
++ * no logging of the remove.
++ * @param pkt: packet in buffer that is removed from. Used to log the name
++ * of the item removed.
++ * @param rrset: RRset that the RR is removed from.
++ * @param prev: previous RR in list, or NULL.
++ * @param rr: RR that is removed.
++ * @param addr: address used for logging, if verbose, or NULL then it is not
++ * used.
++ * @param addrlen: length of addr, if that is not NULL.
++ * @return true if rrset is entirely bad, it would then need to be removed.
++ */
++int msgparse_rrset_remove_rr(const char* str, struct sldns_buffer* pkt,
++ struct rrset_parse* rrset, struct rr_parse* prev, struct rr_parse* rr,
++ struct sockaddr_storage* addr, socklen_t addrlen);
++
+ #endif /* UTIL_DATA_MSGPARSE_H */
+--- contrib/unbound/util/data/msgreply.c.orig
++++ contrib/unbound/util/data/msgreply.c
+@@ -94,7 +94,7 @@
+ struct reply_info*
+ construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd,
+ time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns,
+- size_t ar, size_t total, enum sec_status sec)
++ size_t ar, size_t total, enum sec_status sec, sldns_ede_code reason_bogus)
+ {
+ struct reply_info* rep;
+ /* rrset_count-1 because the first ref is part of the struct. */
+@@ -117,7 +117,9 @@
+ rep->ar_numrrsets = ar;
+ rep->rrset_count = total;
+ rep->security = sec;
+- rep->reason_bogus = LDNS_EDE_NONE;
++ rep->reason_bogus = reason_bogus;
++ /* this is only allocated and used for caching on copy */
++ rep->reason_bogus_str = NULL;
+ rep->authoritative = 0;
+ /* array starts after the refs */
+ if(region)
+@@ -137,7 +139,7 @@
+ {
+ *rep = construct_reply_info_base(region, msg->flags, msg->qdcount, 0,
+ 0, 0, msg->an_rrsets, msg->ns_rrsets, msg->ar_rrsets,
+- msg->rrset_count, sec_status_unchecked);
++ msg->rrset_count, sec_status_unchecked, LDNS_EDE_NONE);
+ if(!*rep)
+ return 0;
+ return 1;
+@@ -182,7 +184,7 @@
+ new_rep = construct_reply_info_base(region, rep->flags,
+ rep->qdcount, rep->ttl, rep->prefetch_ttl,
+ rep->serve_expired_ttl, an_numrrsets, 0, 0, an_numrrsets,
+- sec_status_insecure);
++ sec_status_insecure, LDNS_EDE_NONE);
+ if(!new_rep)
+ return NULL;
+ if(!reply_info_alloc_rrset_keys(new_rep, NULL, region))
+@@ -580,6 +582,10 @@
+ for(i=0; i<rep->rrset_count; i++) {
+ ub_packed_rrset_parsedelete(rep->rrsets[i], alloc);
+ }
++ if(rep->reason_bogus_str) {
++ free(rep->reason_bogus_str);
++ rep->reason_bogus_str = NULL;
++ }
+ free(rep);
+ }
+
+@@ -661,6 +667,10 @@
+ reply_info_delete(void* d, void* ATTR_UNUSED(arg))
+ {
+ struct reply_info* r = (struct reply_info*)d;
++ if(r->reason_bogus_str) {
++ free(r->reason_bogus_str);
++ r->reason_bogus_str = NULL;
++ }
+ free(r);
+ }
+
+@@ -737,17 +747,36 @@
+ return 1;
+ }
+
+-struct reply_info*
+-reply_info_copy(struct reply_info* rep, struct alloc_cache* alloc,
++struct reply_info*
++reply_info_copy(struct reply_info* rep, struct alloc_cache* alloc,
+ struct regional* region)
+ {
+ struct reply_info* cp;
+- cp = construct_reply_info_base(region, rep->flags, rep->qdcount,
+- rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl,
++ cp = construct_reply_info_base(region, rep->flags, rep->qdcount,
++ rep->ttl, rep->prefetch_ttl, rep->serve_expired_ttl,
+ rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets,
+- rep->rrset_count, rep->security);
++ rep->rrset_count, rep->security, rep->reason_bogus);
+ if(!cp)
+ return NULL;
++
++ if(rep->reason_bogus_str && *rep->reason_bogus_str != 0) {
++ if(region) {
++ cp->reason_bogus_str = (char*)regional_alloc(region,
++ sizeof(char)
++ * (strlen(rep->reason_bogus_str)+1));
++ } else {
++ cp->reason_bogus_str = malloc(sizeof(char)
++ * (strlen(rep->reason_bogus_str)+1));
++ }
++ if(!cp->reason_bogus_str) {
++ if(!region)
++ reply_info_parsedelete(cp, alloc);
++ return NULL;
++ }
++ memcpy(cp->reason_bogus_str, rep->reason_bogus_str,
++ strlen(rep->reason_bogus_str)+1);
++ }
++
+ /* allocate ub_key structures special or not */
+ if(!reply_info_alloc_rrset_keys(cp, alloc, region)) {
+ if(!region)
+@@ -1020,6 +1049,16 @@
+ return 1;
+ }
+
++int edns_opt_list_append_keepalive(struct edns_option** list, int msec,
++ struct regional* region)
++{
++ uint8_t data[2]; /* For keepalive value */
++ data[0] = (uint8_t)((msec >> 8) & 0xff);
++ data[1] = (uint8_t)(msec & 0xff);
++ return edns_opt_list_append(list, LDNS_EDNS_KEEPALIVE, sizeof(data),
++ data, region);
++}
++
+ int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len,
+ uint8_t* data, struct regional* region)
+ {
+@@ -1195,7 +1234,7 @@
+ }
+
+ struct edns_option* edns_opt_copy_region(struct edns_option* list,
+- struct regional* region)
++ struct regional* region)
+ {
+ struct edns_option* result = NULL, *cur = NULL, *s;
+ while(list) {
+@@ -1224,6 +1263,42 @@
+ return result;
+ }
+
++struct edns_option* edns_opt_copy_filter_region(struct edns_option* list,
++ uint16_t* filter_list, size_t filter_list_len, struct regional* region)
++{
++ struct edns_option* result = NULL, *cur = NULL, *s;
++ size_t i;
++ while(list) {
++ for(i=0; i<filter_list_len; i++)
++ if(filter_list[i] == list->opt_code) goto found;
++ if(i == filter_list_len) goto next;
++found:
++ /* copy edns option structure */
++ s = regional_alloc_init(region, list, sizeof(*list));
++ if(!s) return NULL;
++ s->next = NULL;
++
++ /* copy option data */
++ if(s->opt_data) {
++ s->opt_data = regional_alloc_init(region, s->opt_data,
++ s->opt_len);
++ if(!s->opt_data)
++ return NULL;
++ }
++
++ /* link into list */
++ if(cur)
++ cur->next = s;
++ else result = s;
++ cur = s;
++
++next:
++ /* examine next element */
++ list = list->next;
++ }
++ return result;
++}
++
+ int edns_opt_compare(struct edns_option* p, struct edns_option* q)
+ {
+ if(!p && !q) return 0;
+--- contrib/unbound/util/data/msgreply.h.orig
++++ contrib/unbound/util/data/msgreply.h
+@@ -170,9 +170,17 @@
+
+ /**
+ * EDE (rfc8914) code with reason for DNSSEC bogus status.
++ * Used for caching the EDE.
+ */
+ sldns_ede_code reason_bogus;
+
++ /**
++ * EDE (rfc8914) NULL-terminated string with human-readable reason
++ * for DNSSEC bogus status.
++ * Used for caching the EDE.
++ */
++ char* reason_bogus_str;
++
+ /**
+ * Number of RRsets in each section.
+ * The answer section. Add up the RRs in every RRset to calculate
+@@ -240,13 +248,15 @@
+ * @param ar: ar count
+ * @param total: total rrset count (presumably an+ns+ar).
+ * @param sec: security status of the reply info.
++ * @param reason_bogus: the Extended DNS Error for DNSSEC bogus status
+ * @return the reply_info base struct with the array for putting the rrsets
+ * in. The array has been zeroed. Returns NULL on malloc failure.
+ */
+ struct reply_info*
+ construct_reply_info_base(struct regional* region, uint16_t flags, size_t qd,
+- time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns,
+- size_t ar, size_t total, enum sec_status sec);
++ time_t ttl, time_t prettl, time_t expttl, size_t an, size_t ns,
++ size_t ar, size_t total, enum sec_status sec,
++ sldns_ede_code reason_bogus);
+
+ /**
+ * Parse wire query into a queryinfo structure, return 0 on parse error.
+@@ -567,6 +577,16 @@
+ int edns_opt_list_append_ede(struct edns_option** list, struct regional* region,
+ sldns_ede_code code, const char *txt);
+
++/**
++ * Append edns keep alive option to edns options list
++ * @param list: the edns option list to append the edns option to.
++ * @param msec: the duration in msecs for the keep alive.
++ * @param region: region to allocate the new edns option.
++ * @return false on failure.
++ */
++int edns_opt_list_append_keepalive(struct edns_option** list, int msec,
++ struct regional* region);
++
+ /**
+ * Remove any option found on the edns option list that matches the code.
+ * @param list: the list of edns options.
+@@ -718,6 +738,12 @@
+ struct edns_option* edns_opt_copy_region(struct edns_option* list,
+ struct regional* region);
+
++/**
++ * Copy a filtered edns option list allocated to the new region
++ */
++struct edns_option* edns_opt_copy_filter_region(struct edns_option* list,
++ uint16_t* filter_list, size_t filter_list_len, struct regional* region);
++
+ /**
+ * Copy edns option list allocated with malloc
+ */
+--- contrib/unbound/util/edns.c.orig
++++ contrib/unbound/util/edns.c
+@@ -45,8 +45,11 @@
+ #include "util/netevent.h"
+ #include "util/net_help.h"
+ #include "util/regional.h"
++#include "util/rfc_1982.h"
++#include "util/siphash.h"
+ #include "util/data/msgparse.h"
+ #include "util/data/msgreply.h"
++#include "sldns/sbuffer.h"
+
+ #if 0
+ /* XXX: remove me */
+@@ -133,3 +136,59 @@
+ return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen);
+ }
+
++uint8_t*
++edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4,
++ uint8_t* hash)
++{
++ v4?siphash(in, 20, secret, hash, 8):siphash(in, 32, secret, hash, 8);
++ return hash;
++}
++
++void
++edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
++ uint32_t timestamp)
++{
++ uint8_t hash[8];
++ buf[ 8] = 1; /* Version */
++ buf[ 9] = 0; /* Reserved */
++ buf[10] = 0; /* Reserved */
++ buf[11] = 0; /* Reserved */
++ sldns_write_uint32(buf + 12, timestamp);
++ (void)edns_cookie_server_hash(buf, secret, v4, hash);
++ memcpy(buf + 16, hash, 8);
++}
++
++enum edns_cookie_val_status
++edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
++ const uint8_t* secret, size_t secret_len, int v4,
++ const uint8_t* hash_input, uint32_t now)
++{
++ uint8_t hash[8];
++ uint32_t timestamp;
++ uint32_t subt_1982 = 0; /* Initialize for the compiler; unused value */
++ int comp_1982;
++ if(cookie_len != 24)
++ /* RFC9018 cookies are 24 bytes long */
++ return COOKIE_STATUS_CLIENT_ONLY;
++ if(secret_len != 16 || /* RFC9018 cookies have 16 byte secrets */
++ cookie[8] != 1) /* RFC9018 cookies are cookie version 1 */
++ return COOKIE_STATUS_INVALID;
++ timestamp = sldns_read_uint32(cookie + 12);
++ if((comp_1982 = compare_1982(now, timestamp)) > 0
++ && (subt_1982 = subtract_1982(timestamp, now)) > 3600)
++ /* Cookie is older than 1 hour (see RFC9018 Section 4.3.) */
++ return COOKIE_STATUS_EXPIRED;
++ if(comp_1982 <= 0 && subtract_1982(now, timestamp) > 300)
++ /* Cookie time is more than 5 minutes in the future.
++ * (see RFC9018 Section 4.3.) */
++ return COOKIE_STATUS_FUTURE;
++ if(memcmp(edns_cookie_server_hash(hash_input, secret, v4, hash),
++ cookie + 16, 8) != 0)
++ /* Hashes do not match */
++ return COOKIE_STATUS_INVALID;
++ if(comp_1982 > 0 && subt_1982 > 1800)
++ /* Valid cookie but older than 30 minutes, so create a new one
++ * anyway */
++ return COOKIE_STATUS_VALID_RENEW;
++ return COOKIE_STATUS_VALID;
++}
+--- contrib/unbound/util/edns.h.orig
++++ contrib/unbound/util/edns.h
+@@ -75,6 +75,15 @@
+ size_t string_len;
+ };
+
++enum edns_cookie_val_status {
++ COOKIE_STATUS_CLIENT_ONLY = -3,
++ COOKIE_STATUS_FUTURE = -2,
++ COOKIE_STATUS_EXPIRED = -1,
++ COOKIE_STATUS_INVALID = 0,
++ COOKIE_STATUS_VALID = 1,
++ COOKIE_STATUS_VALID_RENEW = 2,
++};
++
+ /**
+ * Create structure to hold EDNS strings
+ * @return: newly created edns_strings, NULL on alloc failure.
+@@ -106,4 +115,54 @@
+ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
+ socklen_t addrlen);
+
++/**
++ * Compute the interoperable DNS cookie (RFC9018) hash.
++ * @param in: buffer input for the hash generation. It needs to be:
++ * Client Cookie | Version | Reserved | Timestamp | Client-IP
++ * @param secret: the server secret; implicit length of 16 octets.
++ * @param v4: if the client IP is v4 or v6.
++ * @param hash: buffer to write the hash to.
++ * return a pointer to the hash.
++ */
++uint8_t* edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret,
++ int v4, uint8_t* hash);
++
++/**
++ * Write an interoperable DNS server cookie (RFC9018).
++ * @param buf: buffer to write to. It should have a size of at least 32 octets
++ * as it doubles as the output buffer and the hash input buffer.
++ * The first 8 octets are expected to be the Client Cookie and will be
++ * left untouched.
++ * The next 8 octets will be written with Version | Reserved | Timestamp.
++ * The next 4 or 16 octets are expected to be the IPv4 or the IPv6 address
++ * based on the v4 flag.
++ * Thus the first 20 or 32 octets, based on the v4 flag, will be used as
++ * the hash input.
++ * The server hash (8 octets) will be written after the first 16 octets;
++ * overwriting the address information.
++ * The caller expects a complete, 24 octet long cookie in the buffer.
++ * @param secret: the server secret; implicit length of 16 octets.
++ * @param v4: if the client IP is v4 or v6.
++ * @param timestamp: the timestamp to use.
++ */
++void edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
++ uint32_t timestamp);
++
++/**
++ * Validate an interoperable DNS cookie (RFC9018).
++ * @param cookie: pointer to the cookie data.
++ * @param cookie_len: the length of the cookie data.
++ * @param secret: pointer to the server secret.
++ * @param secret_len: the length of the secret.
++ * @param v4: if the client IP is v4 or v6.
++ * @param hash_input: pointer to the hash input for validation. It needs to be:
++ * Client Cookie | Version | Reserved | Timestamp | Client-IP
++ * @param now: the current time.
++ * return edns_cookie_val_status with the cookie validation status i.e.,
++ * <=0 for invalid, else valid.
++ */
++enum edns_cookie_val_status edns_cookie_server_validate(const uint8_t* cookie,
++ size_t cookie_len, const uint8_t* secret, size_t secret_len, int v4,
++ const uint8_t* hash_input, uint32_t now);
++
+ #endif
+--- contrib/unbound/util/fptr_wlist.c.orig
++++ contrib/unbound/util/fptr_wlist.c
+@@ -131,6 +131,7 @@
+ else if(fptr == &pending_udp_timer_delay_cb) return 1;
+ else if(fptr == &worker_stat_timer_cb) return 1;
+ else if(fptr == &worker_probe_timer_cb) return 1;
++ else if(fptr == &validate_suspend_timer_cb) return 1;
+ #ifdef UB_ON_WINDOWS
+ else if(fptr == &wsvc_cron_cb) return 1;
+ #endif
+@@ -168,7 +169,9 @@
+ fptr_whitelist_event(void (*fptr)(int, short, void *))
+ {
+ if(fptr == &comm_point_udp_callback) return 1;
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
+ else if(fptr == &comm_point_udp_ancil_callback) return 1;
++#endif
+ else if(fptr == &comm_point_tcp_accept_callback) return 1;
+ else if(fptr == &comm_point_tcp_handle_callback) return 1;
+ else if(fptr == &comm_timer_callback) return 1;
+@@ -659,6 +662,10 @@
+ #else
+ (void)fptr;
+ #endif
++#ifdef WITH_PYTHONMODULE
++ if(fptr == &python_inplace_cb_edns_back_parsed_call)
++ return 1;
++#endif
+ #ifdef WITH_DYNLIBMODULE
+ if(fptr == &dynlib_inplace_cb_edns_back_parsed)
+ return 1;
+@@ -675,6 +682,10 @@
+ #else
+ (void)fptr;
+ #endif
++#ifdef WITH_PYTHONMODULE
++ if(fptr == &python_inplace_cb_query_response)
++ return 1;
++#endif
+ #ifdef WITH_DYNLIBMODULE
+ if(fptr == &dynlib_inplace_cb_query_response)
+ return 1;
+--- contrib/unbound/util/iana_ports.inc.orig
++++ contrib/unbound/util/iana_ports.inc
+@@ -674,6 +674,8 @@
+ 911,
+ 912,
+ 913,
++914,
++915,
+ 989,
+ 990,
+ 991,
+@@ -1901,6 +1903,7 @@
+ 2256,
+ 2257,
+ 2258,
++2259,
+ 2260,
+ 2261,
+ 2262,
+@@ -2010,6 +2013,7 @@
+ 2366,
+ 2367,
+ 2368,
++2369,
+ 2370,
+ 2372,
+ 2378,
+--- contrib/unbound/util/log.c.orig
++++ contrib/unbound/util/log.c
+@@ -187,7 +187,7 @@
+ default_ident = id;
+ }
+
+-void log_ident_revert_to_default()
++void log_ident_revert_to_default(void)
+ {
+ ident = default_ident;
+ }
+--- contrib/unbound/util/module.c.orig
++++ contrib/unbound/util/module.c
+@@ -84,8 +84,10 @@
+ const char* str, sldns_ede_code reason_bogus)
+ {
+ struct errinf_strlist* p;
+- if((qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) || !str)
++ if(!str || (qstate->env->cfg->val_log_level < 2 &&
++ !qstate->env->cfg->log_servfail)) {
+ return;
++ }
+ p = (struct errinf_strlist*)regional_alloc(qstate->region, sizeof(*p));
+ if(!p) {
+ log_err("malloc failure in validator-error-info string");
+@@ -152,15 +154,19 @@
+ return p;
+ }
+
++/* Try to find the latest (most specific) dnssec failure */
+ sldns_ede_code errinf_to_reason_bogus(struct module_qstate* qstate)
+ {
+ struct errinf_strlist* s;
++ sldns_ede_code ede = LDNS_EDE_NONE;
+ for(s=qstate->errinf; s; s=s->next) {
+- if (s->reason_bogus != LDNS_EDE_NONE) {
+- return s->reason_bogus;
+- }
++ if(s->reason_bogus == LDNS_EDE_NONE) continue;
++ if(ede != LDNS_EDE_NONE
++ && ede != LDNS_EDE_DNSSEC_BOGUS
++ && s->reason_bogus == LDNS_EDE_DNSSEC_BOGUS) continue;
++ ede = s->reason_bogus;
+ }
+- return LDNS_EDE_NONE;
++ return ede;
+ }
+
+ char* errinf_to_str_servfail(struct module_qstate* qstate)
+@@ -188,6 +194,24 @@
+ return p;
+ }
+
++char* errinf_to_str_misc(struct module_qstate* qstate)
++{
++ char buf[20480];
++ char* p = buf;
++ size_t left = sizeof(buf);
++ struct errinf_strlist* s;
++ if(!qstate->errinf)
++ snprintf(p, left, "misc failure");
++ else for(s=qstate->errinf; s; s=s->next) {
++ snprintf(p, left, "%s%s", (s==qstate->errinf?"":" "), s->str);
++ left -= strlen(p); p += strlen(p);
++ }
++ p = strdup(buf);
++ if(!p)
++ log_err("malloc failure in errinf_to_str");
++ return p;
++}
++
+ void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr)
+ {
+ char buf[1024];
+--- contrib/unbound/util/module.h.orig
++++ contrib/unbound/util/module.h
+@@ -619,6 +619,12 @@
+ /** if this is a validation recursion query that does not get
+ * validation itself */
+ int is_valrec;
++#ifdef CLIENT_SUBNET
++ /** the client network address is needed for the client-subnet option
++ * when prefetching, but we can't use reply_list in mesh_info, because
++ * we don't want to send a reply for the internal query. */
++ struct sockaddr_storage client_addr;
++#endif
+
+ /** comm_reply contains server replies */
+ struct comm_reply* reply;
+@@ -671,6 +677,8 @@
+ * those servers. By comparing expiry time with qstarttime for type NS.
+ */
+ time_t qstarttime;
++ /** whether a message from cachedb will be used for the reply */
++ int is_cachedb_answer;
+
+ /**
+ * Attributes of clients that share the qstate that may affect IP-based
+@@ -683,6 +691,8 @@
+ struct respip_action_info* respip_action_info;
+ /** if the query is rpz passthru, no further rpz processing for it */
+ int rpz_passthru;
++ /* Flag tcp required. */
++ int tcp_required;
+
+ /** whether the reply should be dropped */
+ int is_drop;
+@@ -818,11 +828,11 @@
+ * This string is malloced and has to be freed by caller.
+ */
+ char* errinf_to_str_bogus(struct module_qstate* qstate);
++
+ /**
+- * Check the sldns_ede_code of the qstate.
++ * Check the sldns_ede_code of the qstate->errinf.
+ * @param qstate: query state.
+- * @return LDNS_EDE_DNSSEC_BOGUS by default, or the first explicitly set
+- * sldns_ede_code.
++ * @return the latest explicitly set sldns_ede_code or LDNS_EDE_NONE.
+ */
+ sldns_ede_code errinf_to_reason_bogus(struct module_qstate* qstate);
+
+@@ -834,6 +844,14 @@
+ */
+ char* errinf_to_str_servfail(struct module_qstate* qstate);
+
++/**
++ * Create error info in string. For misc failures that are not servfail.
++ * @param qstate: query state.
++ * @return string or NULL on malloc failure (already logged).
++ * This string is malloced and has to be freed by caller.
++ */
++char* errinf_to_str_misc(struct module_qstate* qstate);
++
+ /**
+ * Initialize the edns known options by allocating the required space.
+ * @param env: the module environment.
+--- contrib/unbound/util/net_help.c.orig
++++ contrib/unbound/util/net_help.c
+@@ -779,8 +779,8 @@
+ return match;
+ }
+
+-void
+-addr_to_str(struct sockaddr_storage* addr, socklen_t addrlen,
++void
++addr_to_str(struct sockaddr_storage* addr, socklen_t addrlen,
+ char* buf, size_t len)
+ {
+ int af = (int)((struct sockaddr_in*)addr)->sin_family;
+@@ -792,7 +792,51 @@
+ }
+ }
+
+-int
++int
++prefixnet_is_nat64(int prefixnet)
++{
++ return (prefixnet == 32 || prefixnet == 40 ||
++ prefixnet == 48 || prefixnet == 56 ||
++ prefixnet == 64 || prefixnet == 96);
++}
++
++void
++addr_to_nat64(const struct sockaddr_storage* addr,
++ const struct sockaddr_storage* nat64_prefix,
++ socklen_t nat64_prefixlen, int nat64_prefixnet,
++ struct sockaddr_storage* nat64_addr, socklen_t* nat64_addrlen)
++{
++ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
++ struct sockaddr_in6 *sin6;
++ uint8_t *v4_byte;
++ int i;
++
++ /* This needs to be checked by the caller */
++ log_assert(addr->ss_family == AF_INET);
++ /* Current usage is only from config values; prefix lengths enforced
++ * during config validation */
++ log_assert(prefixnet_is_nat64(nat64_prefixnet));
++
++ *nat64_addr = *nat64_prefix;
++ *nat64_addrlen = nat64_prefixlen;
++
++ sin6 = (struct sockaddr_in6 *)nat64_addr;
++ sin6->sin6_flowinfo = 0;
++ sin6->sin6_port = sin->sin_port;
++
++ nat64_prefixnet = nat64_prefixnet / 8;
++
++ v4_byte = (uint8_t *)&sin->sin_addr.s_addr;
++ for(i = 0; i < 4; i++) {
++ if(nat64_prefixnet == 8) {
++ /* bits 64...71 are MBZ */
++ sin6->sin6_addr.s6_addr[nat64_prefixnet++] = 0;
++ }
++ sin6->sin6_addr.s6_addr[nat64_prefixnet++] = *v4_byte++;
++ }
++}
++
++int
+ addr_is_ip4mapped(struct sockaddr_storage* addr, socklen_t addrlen)
+ {
+ /* prefix for ipv4 into ipv6 mapping is ::ffff:x.x.x.x */
+@@ -908,6 +952,111 @@
+ #endif /* HAVE_SSL */
+ }
+
++#ifdef HAVE_SSL
++/** Print crypt erro with SSL_get_error want code and err_get_error code */
++static void log_crypto_err_io_code_arg(const char* str, int r,
++ unsigned long err, int err_present)
++{
++ int print_errno = 0, print_crypto_err = 0;
++ const char* inf = NULL;
++
++ switch(r) {
++ case SSL_ERROR_NONE:
++ inf = "no error";
++ break;
++ case SSL_ERROR_ZERO_RETURN:
++ inf = "channel closed";
++ break;
++ case SSL_ERROR_WANT_READ:
++ inf = "want read";
++ break;
++ case SSL_ERROR_WANT_WRITE:
++ inf = "want write";
++ break;
++ case SSL_ERROR_WANT_CONNECT:
++ inf = "want connect";
++ break;
++ case SSL_ERROR_WANT_ACCEPT:
++ inf = "want accept";
++ break;
++ case SSL_ERROR_WANT_X509_LOOKUP:
++ inf = "want X509 lookup";
++ break;
++#ifdef SSL_ERROR_WANT_ASYNC
++ case SSL_ERROR_WANT_ASYNC:
++ inf = "want async";
++ break;
++#endif
++#ifdef SSL_ERROR_WANT_ASYNC_JOB
++ case SSL_ERROR_WANT_ASYNC_JOB:
++ inf = "want async job";
++ break;
++#endif
++#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
++ case SSL_ERROR_WANT_CLIENT_HELLO_CB:
++ inf = "want client hello cb";
++ break;
++#endif
++ case SSL_ERROR_SYSCALL:
++ print_errno = 1;
++ inf = "syscall";
++ break;
++ case SSL_ERROR_SSL:
++ print_crypto_err = 1;
++ inf = "SSL, usually protocol, error";
++ break;
++ default:
++ inf = "unknown SSL_get_error result code";
++ print_errno = 1;
++ print_crypto_err = 1;
++ }
++ if(print_crypto_err) {
++ if(print_errno) {
++ char buf[1024];
++ snprintf(buf, sizeof(buf), "%s with errno %s",
++ str, strerror(errno));
++ if(err_present)
++ log_crypto_err_code(buf, err);
++ else log_crypto_err(buf);
++ } else {
++ if(err_present)
++ log_crypto_err_code(str, err);
++ else log_crypto_err(str);
++ }
++ } else {
++ if(print_errno) {
++ if(errno == 0)
++ log_err("str: syscall error with errno %s",
++ strerror(errno));
++ else log_err("str: %s", strerror(errno));
++ } else {
++ log_err("str: %s", inf);
++ }
++ }
++}
++#endif /* HAVE_SSL */
++
++void log_crypto_err_io(const char* str, int r)
++{
++#ifdef HAVE_SSL
++ log_crypto_err_io_code_arg(str, r, 0, 0);
++#else
++ (void)str;
++ (void)r;
++#endif /* HAVE_SSL */
++}
++
++void log_crypto_err_io_code(const char* str, int r, unsigned long err)
++{
++#ifdef HAVE_SSL
++ log_crypto_err_io_code_arg(str, r, err, 1);
++#else
++ (void)str;
++ (void)r;
++ (void)err;
++#endif /* HAVE_SSL */
++}
++
+ #ifdef HAVE_SSL
+ /** log certificate details */
+ void
+@@ -1005,6 +1154,16 @@
+ log_crypto_err("could not set cipher list with SSL_CTX_set_cipher_list");
+ }
+ #endif
++#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF)
++ /* ignore errors when peers do not send the mandatory close_notify
++ * alert on shutdown.
++ * Relevant for openssl >= 3 */
++ if((SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF) &
++ SSL_OP_IGNORE_UNEXPECTED_EOF) != SSL_OP_IGNORE_UNEXPECTED_EOF) {
++ log_crypto_err("could not set SSL_OP_IGNORE_UNEXPECTED_EOF");
++ return 0;
++ }
++#endif
+
+ if((SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE) &
+ SSL_OP_CIPHER_SERVER_PREFERENCE) !=
+@@ -1233,6 +1392,17 @@
+ SSL_CTX_free(ctx);
+ return 0;
+ }
++#endif
++#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF)
++ /* ignore errors when peers do not send the mandatory close_notify
++ * alert on shutdown.
++ * Relevant for openssl >= 3 */
++ if((SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF) &
++ SSL_OP_IGNORE_UNEXPECTED_EOF) != SSL_OP_IGNORE_UNEXPECTED_EOF) {
++ log_crypto_err("could not set SSL_OP_IGNORE_UNEXPECTED_EOF");
++ SSL_CTX_free(ctx);
++ return 0;
++ }
+ #endif
+ if(key && key[0]) {
+ if(!SSL_CTX_use_certificate_chain_file(ctx, pem)) {
+--- contrib/unbound/util/net_help.h.orig
++++ contrib/unbound/util/net_help.h
+@@ -331,6 +331,29 @@
+ void addr_to_str(struct sockaddr_storage* addr, socklen_t addrlen,
+ char* buf, size_t len);
+
++/**
++ * Check if the prefix network length is one of the allowed 32, 40, 48, 56, 64,
++ * or 96.
++ * @param prefixnet: prefix network length to check.
++ * @return 1 on success, 0 on failure.
++ */
++int prefixnet_is_nat64(int prefixnet);
++
++/**
++ * Create a NAT64 address from a given address (needs to be IPv4) and a given
++ * NAT64 prefix. The NAT64 prefix net needs to be one of 32, 40, 48, 56, 64, 96.
++ * @param addr: IPv4 address.
++ * @param nat64_prefix: NAT64 prefix.
++ * @param nat64_prefixlen: NAT64 prefix len.
++ * @param nat64_prefixnet: NAT64 prefix mask.
++ * @param nat64_addr: the resulting NAT64 address.
++ * @param nat64_addrlen: the resulting NAT64 address length.
++ */
++void addr_to_nat64(const struct sockaddr_storage* addr,
++ const struct sockaddr_storage* nat64_prefix,
++ socklen_t nat64_prefixlen, int nat64_prefixnet,
++ struct sockaddr_storage* nat64_addr, socklen_t* nat64_addrlen);
++
+ /**
+ * See if sockaddr is an ipv6 mapped ipv4 address, "::ffff:0.0.0.0"
+ * @param addr: address
+@@ -406,6 +429,24 @@
+ */
+ void log_crypto_err_code(const char* str, unsigned long err);
+
++/**
++ * Log an error from libcrypto that came from SSL_write and so on, with
++ * a value from SSL_get_error, calls log_err. If that fails it logs with
++ * log_crypto_err.
++ * @param str: what failed
++ * @param r: output of SSL_get_error on the I/O operation result.
++ */
++void log_crypto_err_io(const char* str, int r);
++
++/**
++ * Log an error from libcrypt that came from an I/O routine with the
++ * errcode from ERR_get_error. Calls log_err() and log_crypto_err_code.
++ * @param str: what failed
++ * @param r: output of SSL_get_error on the I/O operation result.
++ * @param err: error code from ERR_get_error
++ */
++void log_crypto_err_io_code(const char* str, int r, unsigned long err);
++
+ /**
+ * Log certificate details verbosity, string, of X509 cert
+ * @param level: verbosity level
+--- contrib/unbound/util/netevent.c.orig
++++ contrib/unbound/util/netevent.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -46,6 +46,7 @@
+ #include "util/tcp_conn_limit.h"
+ #include "util/fptr_wlist.h"
+ #include "util/proxy_protocol.h"
++#include "util/timeval_func.h"
+ #include "sldns/pkthdr.h"
+ #include "sldns/sbuffer.h"
+ #include "sldns/str2wire.h"
+@@ -71,7 +72,9 @@
+ #ifdef HAVE_OPENSSL_ERR_H
+ #include <openssl/err.h>
+ #endif
+-
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++#include <linux/net_tstamp.h>
++#endif
+ /* -------- Start of local definitions -------- */
+ /** if CMSG_ALIGN is not defined on this platform, a workaround */
+ #ifndef CMSG_ALIGN
+@@ -113,7 +116,19 @@
+
+ /** timeout in millisec to wait for write to unblock, packets dropped after.*/
+ #define SEND_BLOCKED_WAIT_TIMEOUT 200
++/** max number of times to wait for write to unblock, packets dropped after.*/
++#define SEND_BLOCKED_MAX_RETRY 5
+
++/** Let's make timestamping code cleaner and redefine SO_TIMESTAMP* */
++#ifndef SO_TIMESTAMP
++#define SO_TIMESTAMP 29
++#endif
++#ifndef SO_TIMESTAMPNS
++#define SO_TIMESTAMPNS 35
++#endif
++#ifndef SO_TIMESTAMPING
++#define SO_TIMESTAMPING 37
++#endif
+ /**
+ * The internal event structure for keeping ub_event info for the event.
+ * Possibly other structures (list, tree) this is part of.
+@@ -177,7 +192,7 @@
+
+ /* -------- End of local definitions -------- */
+
+-struct comm_base*
++struct comm_base*
+ comm_base_create(int sigs)
+ {
+ struct comm_base* b = (struct comm_base*)calloc(1,
+@@ -220,7 +235,7 @@
+ return b;
+ }
+
+-void
++void
+ comm_base_delete(struct comm_base* b)
+ {
+ if(!b)
+@@ -237,7 +252,7 @@
+ free(b);
+ }
+
+-void
++void
+ comm_base_delete_no_base(struct comm_base* b)
+ {
+ if(!b)
+@@ -253,14 +268,14 @@
+ free(b);
+ }
+
+-void
++void
+ comm_base_timept(struct comm_base* b, time_t** tt, struct timeval** tv)
+ {
+ *tt = &b->eb->secs;
+ *tv = &b->eb->now;
+ }
+
+-void
++void
+ comm_base_dispatch(struct comm_base* b)
+ {
+ int retval;
+@@ -389,9 +404,10 @@
+ WSAGetLastError() == WSAENOBUFS ||
+ WSAGetLastError() == WSAEWOULDBLOCK) {
+ #endif
++ int retries = 0;
+ /* if we set the fd blocking, other threads suddenly
+ * have a blocking fd that they operate on */
+- while(sent == -1 && (
++ while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
+ #ifndef USE_WINSOCK
+ errno == EAGAIN || errno == EINTR ||
+ # ifdef EWOULDBLOCK
+@@ -406,6 +422,13 @@
+ #endif
+ )) {
+ #if defined(HAVE_POLL) || defined(USE_WINSOCK)
++ int send_nobufs = (
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ );
+ struct pollfd p;
+ int pret;
+ memset(&p, 0, sizeof(p));
+@@ -444,8 +467,48 @@
+ log_err("poll udp out failed: %s",
+ sock_strerror(errno));
+ return 0;
++ } else if((pret < 0 &&
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ ) || (send_nobufs && retries > 0)) {
++ /* ENOBUFS, and poll returned without
++ * a timeout. Or the retried send call
++ * returned ENOBUFS. It is good to
++ * wait a bit for the error to clear. */
++ /* The timeout is 20*(2^(retries+1)),
++ * it increases exponentially, starting
++ * at 40 msec. After 5 tries, 1240 msec
++ * have passed in total, when poll
++ * returned the error, and 1200 msec
++ * when send returned the errors. */
++#ifndef USE_WINSOCK
++ pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#else
++ pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#endif
++ if(pret < 0 &&
++#ifndef USE_WINSOCK
++ errno != EAGAIN && errno != EINTR &&
++# ifdef EWOULDBLOCK
++ errno != EWOULDBLOCK &&
++# endif
++ errno != ENOBUFS
++#else
++ WSAGetLastError() != WSAEINPROGRESS &&
++ WSAGetLastError() != WSAEINTR &&
++ WSAGetLastError() != WSAENOBUFS &&
++ WSAGetLastError() != WSAEWOULDBLOCK
++#endif
++ ) {
++ log_err("poll udp out timer failed: %s",
++ sock_strerror(errno));
++ }
+ }
+ #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
++ retries++;
+ if (!is_connected) {
+ sent = sendto(c->fd, (void*)sldns_buffer_begin(packet),
+ sldns_buffer_remaining(packet), 0,
+@@ -470,7 +533,7 @@
+ (struct sockaddr_storage*)addr, addrlen);
+ return 0;
+ } else if((size_t)sent != sldns_buffer_remaining(packet)) {
+- log_err("sent %d in place of %d bytes",
++ log_err("sent %d in place of %d bytes",
+ (int)sent, (int)sldns_buffer_remaining(packet));
+ return 0;
+ }
+@@ -489,7 +552,7 @@
+ if(r->srctype == 6) {
+ #ifdef IPV6_PKTINFO
+ char buf[1024];
+- if(inet_ntop(AF_INET6, &r->pktinfo.v6info.ipi6_addr,
++ if(inet_ntop(AF_INET6, &r->pktinfo.v6info.ipi6_addr,
+ buf, (socklen_t)sizeof(buf)) == 0) {
+ (void)strlcpy(buf, "(inet_ntop error)", sizeof(buf));
+ }
+@@ -499,13 +562,13 @@
+ } else if(r->srctype == 4) {
+ #ifdef IP_PKTINFO
+ char buf1[1024], buf2[1024];
+- if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_addr,
++ if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_addr,
+ buf1, (socklen_t)sizeof(buf1)) == 0) {
+ (void)strlcpy(buf1, "(inet_ntop error)", sizeof(buf1));
+ }
+ buf1[sizeof(buf1)-1]=0;
+ #ifdef HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST
+- if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_spec_dst,
++ if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_spec_dst,
+ buf2, (socklen_t)sizeof(buf2)) == 0) {
+ (void)strlcpy(buf2, "(inet_ntop error)", sizeof(buf2));
+ }
+@@ -517,7 +580,7 @@
+ buf1, buf2);
+ #elif defined(IP_RECVDSTADDR)
+ char buf1[1024];
+- if(inet_ntop(AF_INET, &r->pktinfo.v4addr,
++ if(inet_ntop(AF_INET, &r->pktinfo.v4addr,
+ buf1, (socklen_t)sizeof(buf1)) == 0) {
+ (void)strlcpy(buf1, "(inet_ntop error)", sizeof(buf1));
+ }
+@@ -531,7 +594,7 @@
+ /** send a UDP reply over specified interface*/
+ static int
+ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
+- struct sockaddr* addr, socklen_t addrlen, struct comm_reply* r)
++ struct sockaddr* addr, socklen_t addrlen, struct comm_reply* r)
+ {
+ #if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_SENDMSG)
+ ssize_t sent;
+@@ -579,6 +642,11 @@
+ cmsg_data = CMSG_DATA(cmsg);
+ ((struct in_pktinfo *) cmsg_data)->ipi_ifindex = 0;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in_pktinfo) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in_pktinfo), 0, cmsg->cmsg_len
++ - sizeof(struct in_pktinfo));
+ #elif defined(IP_SENDSRCADDR)
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
+ log_assert(msg.msg_controllen <= sizeof(control.buf));
+@@ -587,6 +655,11 @@
+ memmove(CMSG_DATA(cmsg), &r->pktinfo.v4addr,
+ sizeof(struct in_addr));
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in_addr) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in_addr), 0, cmsg->cmsg_len
++ - sizeof(struct in_addr));
+ #else
+ verbose(VERB_ALGO, "no IP_PKTINFO or IP_SENDSRCADDR");
+ msg.msg_control = NULL;
+@@ -603,6 +676,11 @@
+ cmsg_data = CMSG_DATA(cmsg);
+ ((struct in6_pktinfo *) cmsg_data)->ipi6_ifindex = 0;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in6_pktinfo) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in6_pktinfo), 0, cmsg->cmsg_len
++ - sizeof(struct in6_pktinfo));
+ } else {
+ /* try to pass all 0 to use default route */
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+@@ -611,9 +689,14 @@
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ memset(CMSG_DATA(cmsg), 0, sizeof(struct in6_pktinfo));
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
++ /* zero the padding bytes inserted by the CMSG_LEN */
++ if(sizeof(struct in6_pktinfo) < cmsg->cmsg_len)
++ memset(((uint8_t*)(CMSG_DATA(cmsg))) +
++ sizeof(struct in6_pktinfo), 0, cmsg->cmsg_len
++ - sizeof(struct in6_pktinfo));
+ }
+ #endif /* S_SPLINT_S */
+- if(verbosity >= VERB_ALGO)
++ if(verbosity >= VERB_ALGO && r->srctype != 0)
+ p_ancil("send_udp over interface", r);
+ sent = sendmsg(c->fd, &msg, 0);
+ if(sent == -1) {
+@@ -632,7 +715,8 @@
+ WSAGetLastError() == WSAENOBUFS ||
+ WSAGetLastError() == WSAEWOULDBLOCK) {
+ #endif
+- while(sent == -1 && (
++ int retries = 0;
++ while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
+ #ifndef USE_WINSOCK
+ errno == EAGAIN || errno == EINTR ||
+ # ifdef EWOULDBLOCK
+@@ -647,6 +731,13 @@
+ #endif
+ )) {
+ #if defined(HAVE_POLL) || defined(USE_WINSOCK)
++ int send_nobufs = (
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ );
+ struct pollfd p;
+ int pret;
+ memset(&p, 0, sizeof(p));
+@@ -685,8 +776,48 @@
+ log_err("poll udp out failed: %s",
+ sock_strerror(errno));
+ return 0;
++ } else if((pret < 0 &&
++#ifndef USE_WINSOCK
++ errno == ENOBUFS
++#else
++ WSAGetLastError() == WSAENOBUFS
++#endif
++ ) || (send_nobufs && retries > 0)) {
++ /* ENOBUFS, and poll returned without
++ * a timeout. Or the retried send call
++ * returned ENOBUFS. It is good to
++ * wait a bit for the error to clear. */
++ /* The timeout is 20*(2^(retries+1)),
++ * it increases exponentially, starting
++ * at 40 msec. After 5 tries, 1240 msec
++ * have passed in total, when poll
++ * returned the error, and 1200 msec
++ * when send returned the errors. */
++#ifndef USE_WINSOCK
++ pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#else
++ pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
++#endif
++ if(pret < 0 &&
++#ifndef USE_WINSOCK
++ errno != EAGAIN && errno != EINTR &&
++# ifdef EWOULDBLOCK
++ errno != EWOULDBLOCK &&
++# endif
++ errno != ENOBUFS
++#else
++ WSAGetLastError() != WSAEINPROGRESS &&
++ WSAGetLastError() != WSAEINTR &&
++ WSAGetLastError() != WSAENOBUFS &&
++ WSAGetLastError() != WSAEWOULDBLOCK
++#endif
++ ) {
++ log_err("poll udp out timer failed: %s",
++ sock_strerror(errno));
++ }
+ }
+ #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
++ retries++;
+ sent = sendmsg(c->fd, &msg, 0);
+ }
+ }
+@@ -695,7 +826,7 @@
+ if(!udp_send_errno_needs_log(addr, addrlen))
+ return 0;
+ verbose(VERB_OPS, "sendmsg failed: %s", strerror(errno));
+- log_addr(VERB_OPS, "remote address is",
++ log_addr(VERB_OPS, "remote address is",
+ (struct sockaddr_storage*)addr, addrlen);
+ #ifdef __NetBSD__
+ /* netbsd 7 has IP_PKTINFO for recv but not send */
+@@ -705,7 +836,7 @@
+ #endif
+ return 0;
+ } else if((size_t)sent != sldns_buffer_remaining(packet)) {
+- log_err("sent %d in place of %d bytes",
++ log_err("sent %d in place of %d bytes",
+ (int)sent, (int)sldns_buffer_remaining(packet));
+ return 0;
+ }
+@@ -761,15 +892,18 @@
+ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
+ int stream) {
+ size_t size;
+- struct pp2_header *header = pp2_read_header(buf);
+- if(header == NULL) return 0;
++ struct pp2_header *header;
++ int err = pp2_read_header(sldns_buffer_begin(buf),
++ sldns_buffer_remaining(buf));
++ if(err) return 0;
++ header = (struct pp2_header*)sldns_buffer_begin(buf);
+ size = PP2_HEADER_SIZE + ntohs(header->len);
+ if((header->ver_cmd & 0xF) == PP2_CMD_LOCAL) {
+ /* A connection from the proxy itself.
+ * No need to do anything with addresses. */
+ goto done;
+ }
+- if(header->fam_prot == 0x00) {
++ if(header->fam_prot == PP2_UNSPEC_UNSPEC) {
+ /* Unspecified family and protocol. This could be used for
+ * health checks by proxies.
+ * No need to do anything with addresses. */
+@@ -777,8 +911,8 @@
+ }
+ /* Read the proxied address */
+ switch(header->fam_prot) {
+- case 0x11: /* AF_INET|STREAM */
+- case 0x12: /* AF_INET|DGRAM */
++ case PP2_INET_STREAM:
++ case PP2_INET_DGRAM:
+ {
+ struct sockaddr_in* addr =
+ (struct sockaddr_in*)&rep->client_addr;
+@@ -789,8 +923,8 @@
+ }
+ /* Ignore the destination address; it should be us. */
+ break;
+- case 0x21: /* AF_INET6|STREAM */
+- case 0x22: /* AF_INET6|DGRAM */
++ case PP2_INET6_STREAM:
++ case PP2_INET6_DGRAM:
+ {
+ struct sockaddr_in6* addr =
+ (struct sockaddr_in6*)&rep->client_addr;
+@@ -803,6 +937,10 @@
+ }
+ /* Ignore the destination address; it should be us. */
+ break;
++ default:
++ log_err("proxy_protocol: unsupported family and "
++ "protocol 0x%x", (int)header->fam_prot);
++ return 0;
+ }
+ rep->is_proxied = 1;
+ done:
+@@ -817,10 +955,10 @@
+ return 1;
+ }
+
+-void
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
++void
+ comm_point_udp_ancil_callback(int fd, short event, void* arg)
+ {
+-#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
+ struct comm_reply rep;
+ struct msghdr msg;
+ struct iovec iov[1];
+@@ -833,6 +971,9 @@
+ #ifndef S_SPLINT_S
+ struct cmsghdr* cmsg;
+ #endif /* S_SPLINT_S */
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++ struct timespec *ts;
++#endif /* HAVE_LINUX_NET_TSTAMP_H */
+
+ rep.c = (struct comm_point*)arg;
+ log_assert(rep.c->type == comm_udp);
+@@ -843,6 +984,7 @@
+ ub_comm_base_now(rep.c->ev->base);
+ for(i=0; i<NUM_UDP_PER_SELECT; i++) {
+ sldns_buffer_clear(rep.c->buffer);
++ timeval_clear(&rep.c->recv_tv);
+ rep.remote_addrlen = (socklen_t)sizeof(rep.remote_addr);
+ log_assert(fd != -1);
+ log_assert(sldns_buffer_remaining(rep.c->buffer) > 0);
+@@ -894,9 +1036,23 @@
+ sizeof(struct in_addr));
+ break;
+ #endif /* IP_PKTINFO or IP_RECVDSTADDR */
++#ifdef HAVE_LINUX_NET_TSTAMP_H
++ } else if( cmsg->cmsg_level == SOL_SOCKET &&
++ cmsg->cmsg_type == SO_TIMESTAMPNS) {
++ ts = (struct timespec *)CMSG_DATA(cmsg);
++ TIMESPEC_TO_TIMEVAL(&rep.c->recv_tv, ts);
++ } else if( cmsg->cmsg_level == SOL_SOCKET &&
++ cmsg->cmsg_type == SO_TIMESTAMPING) {
++ ts = (struct timespec *)CMSG_DATA(cmsg);
++ TIMESPEC_TO_TIMEVAL(&rep.c->recv_tv, ts);
++ } else if( cmsg->cmsg_level == SOL_SOCKET &&
++ cmsg->cmsg_type == SO_TIMESTAMP) {
++ memmove(&rep.c->recv_tv, CMSG_DATA(cmsg), sizeof(struct timeval));
++#endif /* HAVE_LINUX_NET_TSTAMP_H */
+ }
+ }
+- if(verbosity >= VERB_ALGO)
++
++ if(verbosity >= VERB_ALGO && rep.srctype != 0)
+ p_ancil("receive_udp on interface", &rep);
+ #endif /* S_SPLINT_S */
+
+@@ -914,23 +1070,23 @@
+ fptr_ok(fptr_whitelist_comm_point(rep.c->callback));
+ if((*rep.c->callback)(rep.c, rep.c->cb_arg, NETEVENT_NOERROR, &rep)) {
+ /* send back immediate reply */
+- (void)comm_point_send_udp_msg_if(rep.c, rep.c->buffer,
++ struct sldns_buffer *buffer;
++#ifdef USE_DNSCRYPT
++ buffer = rep.c->dnscrypt_buffer;
++#else
++ buffer = rep.c->buffer;
++#endif
++ (void)comm_point_send_udp_msg_if(rep.c, buffer,
+ (struct sockaddr*)&rep.remote_addr,
+ rep.remote_addrlen, &rep);
+ }
+ if(!rep.c || rep.c->fd == -1) /* commpoint closed */
+ break;
+ }
+-#else
+- (void)fd;
+- (void)event;
+- (void)arg;
+- fatal_exit("recvmsg: No support for IPV6_PKTINFO; IP_PKTINFO or IP_RECVDSTADDR. "
+- "Please disable interface-automatic");
+-#endif /* AF_INET6 && IPV6_PKTINFO && HAVE_RECVMSG */
+ }
++#endif /* AF_INET6 && IPV6_PKTINFO && HAVE_RECVMSG */
+
+-void
++void
+ comm_point_udp_callback(int fd, short event, void* arg)
+ {
+ struct comm_reply rep;
+@@ -950,14 +1106,14 @@
+ rep.remote_addrlen = (socklen_t)sizeof(rep.remote_addr);
+ log_assert(fd != -1);
+ log_assert(sldns_buffer_remaining(rep.c->buffer) > 0);
+- rcv = recvfrom(fd, (void*)sldns_buffer_begin(rep.c->buffer),
++ rcv = recvfrom(fd, (void*)sldns_buffer_begin(rep.c->buffer),
+ sldns_buffer_remaining(rep.c->buffer), MSG_DONTWAIT,
+ (struct sockaddr*)&rep.remote_addr, &rep.remote_addrlen);
+ if(rcv == -1) {
+ #ifndef USE_WINSOCK
+ if(errno != EAGAIN && errno != EINTR
+ && udp_recv_needs_log(errno))
+- log_err("recvfrom %d failed: %s",
++ log_err("recvfrom %d failed: %s",
+ fd, strerror(errno));
+ #else
+ if(WSAGetLastError() != WSAEINPROGRESS &&
+@@ -1012,7 +1168,7 @@
+
+ /** Use a new tcp handler for new query fd, set to read query */
+ static void
+-setup_tcp_handler(struct comm_point* c, int fd, int cur, int max)
++setup_tcp_handler(struct comm_point* c, int fd, int cur, int max)
+ {
+ int handler_usage;
+ log_assert(c->type == comm_tcp || c->type == comm_http);
+@@ -1076,10 +1232,10 @@
+ /* EINTR is signal interrupt. others are closed connection. */
+ if( errno == EINTR || errno == EAGAIN
+ #ifdef EWOULDBLOCK
+- || errno == EWOULDBLOCK
++ || errno == EWOULDBLOCK
+ #endif
+ #ifdef ECONNABORTED
+- || errno == ECONNABORTED
++ || errno == ECONNABORTED
+ #endif
+ #ifdef EPROTO
+ || errno == EPROTO
+@@ -1253,7 +1409,7 @@
+ #endif /* HAVE_NGHTTP2 */
+
+
+-void
++void
+ comm_point_tcp_accept_callback(int fd, short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg, *c_hdl;
+@@ -1516,7 +1672,8 @@
+ } else {
+ unsigned long err = ERR_get_error();
+ if(!squelch_err_ssl_handshake(err)) {
+- log_crypto_err_code("ssl handshake failed", err);
++ log_crypto_err_io_code("ssl handshake failed",
++ want, err);
+ log_addr(VERB_OPS, "ssl handshake failed",
+ &c->repinfo.remote_addr,
+ c->repinfo.remote_addrlen);
+@@ -1666,23 +1823,30 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read",
++ want);
+ return 0;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_init;
+ }
+ }
+ if(c->pp2_header_state == pp2_header_init) {
+- header = pp2_read_header(c->buffer);
+- if(!header) {
++ int err;
++ err = pp2_read_header(
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_limit(c->buffer));
++ if(err) {
+ log_err("proxy_protocol: could not parse "
+- "PROXYv2 header");
++ "PROXYv2 header (%s)",
++ pp_lookup_error(err));
+ return 0;
+ }
++ header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
+ want_read_size = ntohs(header->len);
+- if(sldns_buffer_remaining(c->buffer) <
++ if(sldns_buffer_limit(c->buffer) <
+ PP2_HEADER_SIZE + want_read_size) {
+ log_err_addr("proxy_protocol: not enough "
+ "buffer size to read PROXYv2 header", "",
+@@ -1727,10 +1891,12 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read",
++ want);
+ return 0;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_done;
+ }
+@@ -1741,6 +1907,7 @@
+ c->repinfo.remote_addrlen);
+ return 0;
+ }
++ sldns_buffer_flip(c->buffer);
+ if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
+ log_err_addr("proxy_protocol: could not consume "
+ "PROXYv2 header", "", &c->repinfo.remote_addr,
+@@ -1785,7 +1952,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return 0;
+ }
+ c->tcp_byte_count += r;
+@@ -1835,7 +2002,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return 0;
+ }
+ sldns_buffer_skip(c->buffer, (ssize_t)r);
+@@ -1926,7 +2093,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return 0;
+ }
+ if(c->tcp_write_and_read) {
+@@ -1978,7 +2145,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return 0;
+ }
+ if(c->tcp_write_and_read) {
+@@ -2062,19 +2229,25 @@
+ goto recv_error_initial;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_init;
+ }
+ }
+ if(c->pp2_header_state == pp2_header_init) {
+- header = pp2_read_header(c->buffer);
+- if(!header) {
++ int err;
++ err = pp2_read_header(
++ sldns_buffer_begin(c->buffer),
++ sldns_buffer_limit(c->buffer));
++ if(err) {
+ log_err("proxy_protocol: could not parse "
+- "PROXYv2 header");
++ "PROXYv2 header (%s)",
++ pp_lookup_error(err));
+ return 0;
+ }
++ header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
+ want_read_size = ntohs(header->len);
+- if(sldns_buffer_remaining(c->buffer) <
++ if(sldns_buffer_limit(c->buffer) <
+ PP2_HEADER_SIZE + want_read_size) {
+ log_err_addr("proxy_protocol: not enough "
+ "buffer size to read PROXYv2 header", "",
+@@ -2101,6 +2274,7 @@
+ goto recv_error;
+ }
+ c->tcp_byte_count += r;
++ sldns_buffer_skip(c->buffer, r);
+ if(c->tcp_byte_count != current_read_size) return 1;
+ c->pp2_header_state = pp2_header_done;
+ }
+@@ -2111,6 +2285,7 @@
+ c->repinfo.remote_addrlen);
+ return 0;
+ }
++ sldns_buffer_flip(c->buffer);
+ if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
+ log_err_addr("proxy_protocol: could not consume "
+ "PROXYv2 header", "", &c->repinfo.remote_addr,
+@@ -2161,7 +2336,7 @@
+ log_err("in comm_point_tcp_handle_read buffer_remaining is "
+ "not > 0 as expected, continuing with (harmless) 0 "
+ "length recv");
+- r = recv(fd, (void*)sldns_buffer_current(c->buffer),
++ r = recv(fd, (void*)sldns_buffer_current(c->buffer),
+ sldns_buffer_remaining(c->buffer), MSG_DONTWAIT);
+ if(r == 0) {
+ if(c->tcp_req_info)
+@@ -2252,8 +2427,8 @@
+ return 0;
+ }
+
+-/**
+- * Handle tcp writing callback.
++/**
++ * Handle tcp writing callback.
+ * @param fd: file descriptor of socket.
+ * @param c: comm point to write buffer out of.
+ * @return: 0 on error
+@@ -2277,7 +2452,7 @@
+ /* from Stevens, unix network programming, vol1, 3rd ed, p450*/
+ int error = 0;
+ socklen_t len = (socklen_t)sizeof(error);
+- if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
++ if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
+ &len) < 0){
+ #ifndef USE_WINSOCK
+ error = errno; /* on solaris errno is error */
+@@ -2318,7 +2493,7 @@
+ return ssl_handle_it(c, 1);
+
+ #ifdef USE_MSG_FASTOPEN
+- /* Only try this on first use of a connection that uses tfo,
++ /* Only try this on first use of a connection that uses tfo,
+ otherwise fall through to normal write */
+ /* Also, TFO support on WINDOWS not implemented at the moment */
+ if(c->tcp_do_fastopen == 1) {
+@@ -2473,7 +2648,7 @@
+ if(WSAGetLastError() == WSAEWOULDBLOCK) {
+ ub_winsock_tcp_wouldblock(c->ev->ev,
+ UB_EV_WRITE);
+- return 1;
++ return 1;
+ }
+ if(WSAGetLastError() == WSAECONNRESET && verbosity < 2)
+ return 0; /* silence reset by peer */
+@@ -2522,7 +2697,7 @@
+ return 1;
+ if(WSAGetLastError() == WSAEWOULDBLOCK) {
+ ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_WRITE);
+- return 1;
++ return 1;
+ }
+ if(WSAGetLastError() == WSAECONNRESET && verbosity < 2)
+ return 0; /* silence reset by peer */
+@@ -2541,7 +2716,7 @@
+ if((!c->tcp_write_and_read && sldns_buffer_remaining(buffer) == 0) || (c->tcp_write_and_read && c->tcp_write_byte_count == c->tcp_write_pkt_len + 2)) {
+ tcp_callback_writer(c);
+ }
+-
++
+ return 1;
+ }
+
+@@ -2561,7 +2736,7 @@
+ if(!c->tcp_do_close) {
+ fptr_ok(fptr_whitelist_comm_point(
+ c->callback));
+- (void)(*c->callback)(c, c->cb_arg,
++ (void)(*c->callback)(c, c->cb_arg,
+ NETEVENT_CLOSED, NULL);
+ }
+ return 0;
+@@ -2618,7 +2793,7 @@
+ }
+ }
+
+-void
++void
+ comm_point_tcp_handle_callback(int fd, short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg;
+@@ -2764,7 +2939,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return 0;
+ }
+ verbose(VERB_ALGO, "ssl http read more skip to %d + %d",
+@@ -2783,7 +2958,7 @@
+ {
+ ssize_t r;
+ log_assert(sldns_buffer_remaining(c->buffer) > 0);
+- r = recv(fd, (void*)sldns_buffer_current(c->buffer),
++ r = recv(fd, (void*)sldns_buffer_current(c->buffer),
+ sldns_buffer_remaining(c->buffer), MSG_DONTWAIT);
+ if(r == 0) {
+ return 0;
+@@ -3052,7 +3227,7 @@
+ /* return and wait to read more */
+ return 1;
+ }
+-
++
+ /* callback of http reader for a new part of the data */
+ c->http_stored = 0;
+ sldns_buffer_set_position(c->buffer, 0);
+@@ -3215,7 +3390,7 @@
+ strerror(errno));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+- log_crypto_err("could not SSL_read");
++ log_crypto_err_io("could not SSL_read", want);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return r;
+@@ -3402,7 +3577,7 @@
+ /* from Stevens, unix network programming, vol1, 3rd ed, p450*/
+ int error = 0;
+ socklen_t len = (socklen_t)sizeof(error);
+- if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
++ if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&error,
+ &len) < 0){
+ #ifndef USE_WINSOCK
+ error = errno; /* on solaris errno is error */
+@@ -3470,7 +3645,7 @@
+ strerror(errno));
+ return 0;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return 0;
+ }
+ sldns_buffer_skip(c->buffer, (ssize_t)r);
+@@ -3487,7 +3662,7 @@
+ {
+ ssize_t r;
+ log_assert(sldns_buffer_remaining(c->buffer) > 0);
+- r = send(fd, (void*)sldns_buffer_current(c->buffer),
++ r = send(fd, (void*)sldns_buffer_current(c->buffer),
+ sldns_buffer_remaining(c->buffer), 0);
+ if(r == -1) {
+ #ifndef USE_WINSOCK
+@@ -3498,7 +3673,7 @@
+ return 1;
+ if(WSAGetLastError() == WSAEWOULDBLOCK) {
+ ub_winsock_tcp_wouldblock(c->ev->ev, UB_EV_WRITE);
+- return 1;
++ return 1;
+ }
+ #endif
+ log_err_addr("http send r", sock_strerror(errno),
+@@ -3543,7 +3718,7 @@
+ strerror(errno));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+- log_crypto_err("could not SSL_write");
++ log_crypto_err_io("could not SSL_write", want);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return r;
+@@ -3619,8 +3794,8 @@
+ #endif
+ }
+
+-/**
+- * Handle http writing callback.
++/**
++ * Handle http writing callback.
+ * @param fd: file descriptor of socket.
+ * @param c: comm point to write buffer out of.
+ * @return: 0 on error
+@@ -3686,7 +3861,7 @@
+ return 1;
+ }
+
+-void
++void
+ comm_point_http_handle_callback(int fd, short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg;
+@@ -3739,7 +3914,7 @@
+ if(event&UB_EV_READ) {
+ if(!comm_point_tcp_handle_read(fd, c, 1)) {
+ fptr_ok(fptr_whitelist_comm_point(c->callback));
+- (void)(*c->callback)(c, c->cb_arg, NETEVENT_CLOSED,
++ (void)(*c->callback)(c, c->cb_arg, NETEVENT_CLOSED,
+ NULL);
+ }
+ return;
+@@ -3747,21 +3922,21 @@
+ log_err("Ignored event %d for localhdl.", event);
+ }
+
+-void comm_point_raw_handle_callback(int ATTR_UNUSED(fd),
++void comm_point_raw_handle_callback(int ATTR_UNUSED(fd),
+ short event, void* arg)
+ {
+ struct comm_point* c = (struct comm_point*)arg;
+ int err = NETEVENT_NOERROR;
+ log_assert(c->type == comm_raw);
+ ub_comm_base_now(c->ev->base);
+-
++
+ if(event&UB_EV_TIMEOUT)
+ err = NETEVENT_TIMEOUT;
+ fptr_ok(fptr_whitelist_comm_point_raw(c->callback));
+ (void)(*c->callback)(c, c->cb_arg, err, NULL);
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_udp(struct comm_base *base, int fd, sldns_buffer* buffer,
+ int pp2_enabled, comm_point_callback_type* callback,
+ void* callback_arg, struct unbound_socket* socket)
+@@ -3824,7 +3999,8 @@
+ return c;
+ }
+
+-struct comm_point*
++#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
++struct comm_point*
+ comm_point_create_udp_ancil(struct comm_base *base, int fd,
+ sldns_buffer* buffer, int pp2_enabled,
+ comm_point_callback_type* callback, void* callback_arg, struct unbound_socket* socket)
+@@ -3886,9 +4062,10 @@
+ c->event_added = 1;
+ return c;
+ }
++#endif
+
+-static struct comm_point*
+-comm_point_create_tcp_handler(struct comm_base *base,
++static struct comm_point*
++comm_point_create_tcp_handler(struct comm_base *base,
+ struct comm_point* parent, size_t bufsize,
+ struct sldns_buffer* spoolbuf, comm_point_callback_type* callback,
+ void* callback_arg, struct unbound_socket* socket)
+@@ -3985,8 +4162,8 @@
+ return c;
+ }
+
+-static struct comm_point*
+-comm_point_create_http_handler(struct comm_base *base,
++static struct comm_point*
++comm_point_create_http_handler(struct comm_base *base,
+ struct comm_point* parent, size_t bufsize, int harden_large_queries,
+ uint32_t http_max_streams, char* http_endpoint,
+ comm_point_callback_type* callback, void* callback_arg,
+@@ -4083,7 +4260,7 @@
+ return NULL;
+ }
+ #endif
+-
++
+ /* add to parent free list */
+ c->tcp_free = parent->tcp_free;
+ parent->tcp_free = c;
+@@ -4105,7 +4282,7 @@
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_tcp(struct comm_base *base, int fd, int num,
+ int idle_timeout, int harden_large_queries,
+ uint32_t http_max_streams, char* http_endpoint,
+@@ -4203,11 +4380,11 @@
+ return NULL;
+ }
+ }
+-
++
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_tcp_out(struct comm_base *base, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg)
+ {
+@@ -4274,7 +4451,7 @@
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_http_out(struct comm_base *base, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg,
+ sldns_buffer* temp)
+@@ -4345,7 +4522,7 @@
+ return c;
+ }
+
+-struct comm_point*
++struct comm_point*
+ comm_point_create_local(struct comm_base *base, int fd, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg)
+ {
+@@ -4413,8 +4590,8 @@
+ return c;
+ }
+
+-struct comm_point*
+-comm_point_create_raw(struct comm_base* base, int fd, int writing,
++struct comm_point*
++comm_point_create_raw(struct comm_base* base, int fd, int writing,
+ comm_point_callback_type* callback, void* callback_arg)
+ {
+ struct comm_point* c = (struct comm_point*)calloc(1,
+@@ -4478,7 +4655,7 @@
+ return c;
+ }
+
+-void
++void
+ comm_point_close(struct comm_point* c)
+ {
+ if(!c)
+@@ -4518,10 +4695,10 @@
+ c->fd = -1;
+ }
+
+-void
++void
+ comm_point_delete(struct comm_point* c)
+ {
+- if(!c)
++ if(!c)
+ return;
+ if((c->type == comm_tcp || c->type == comm_http) && c->ssl) {
+ #ifdef HAVE_SSL
+@@ -4560,7 +4737,7 @@
+ free(c);
+ }
+
+-void
++void
+ comm_point_send_reply(struct comm_reply *repinfo)
+ {
+ struct sldns_buffer* buffer;
+@@ -4624,7 +4801,7 @@
+ }
+ }
+
+-void
++void
+ comm_point_drop_reply(struct comm_reply* repinfo)
+ {
+ if(!repinfo)
+@@ -4648,7 +4825,7 @@
+ reclaim_tcp_handler(repinfo->c);
+ }
+
+-void
++void
+ comm_point_stop_listening(struct comm_point* c)
+ {
+ verbose(VERB_ALGO, "comm point stop listening %d", c->fd);
+@@ -4660,10 +4837,10 @@
+ }
+ }
+
+-void
++void
+ comm_point_start_listening(struct comm_point* c, int newfd, int msec)
+ {
+- verbose(VERB_ALGO, "comm point start listening %d (%d msec)",
++ verbose(VERB_ALGO, "comm point start listening %d (%d msec)",
+ c->fd==-1?newfd:c->fd, msec);
+ if(c->type == comm_tcp_accept && !c->tcp_free) {
+ /* no use to start listening no free slots. */
+@@ -4747,10 +4924,10 @@
+ size_t comm_point_get_mem(struct comm_point* c)
+ {
+ size_t s;
+- if(!c)
++ if(!c)
+ return 0;
+ s = sizeof(*c) + sizeof(*c->ev);
+- if(c->timeout)
++ if(c->timeout)
+ s += sizeof(*c->timeout);
+ if(c->type == comm_tcp || c->type == comm_local) {
+ s += sizeof(*c->buffer) + sldns_buffer_capacity(c->buffer);
+@@ -4769,7 +4946,7 @@
+ return s;
+ }
+
+-struct comm_timer*
++struct comm_timer*
+ comm_timer_create(struct comm_base* base, void (*cb)(void*), void* cb_arg)
+ {
+ struct internal_timer *tm = (struct internal_timer*)calloc(1,
+@@ -4782,7 +4959,7 @@
+ tm->base = base;
+ tm->super.callback = cb;
+ tm->super.cb_arg = cb_arg;
+- tm->ev = ub_event_new(base->eb->base, -1, UB_EV_TIMEOUT,
++ tm->ev = ub_event_new(base->eb->base, -1, UB_EV_TIMEOUT,
+ comm_timer_callback, &tm->super);
+ if(tm->ev == NULL) {
+ log_err("timer_create: event_base_set failed.");
+@@ -4792,7 +4969,7 @@
+ return &tm->super;
+ }
+
+-void
++void
+ comm_timer_disable(struct comm_timer* timer)
+ {
+ if(!timer)
+@@ -4801,7 +4978,7 @@
+ timer->ev_timer->enabled = 0;
+ }
+
+-void
++void
+ comm_timer_set(struct comm_timer* timer, struct timeval* tv)
+ {
+ log_assert(tv);
+@@ -4813,7 +4990,7 @@
+ timer->ev_timer->enabled = 1;
+ }
+
+-void
++void
+ comm_timer_delete(struct comm_timer* timer)
+ {
+ if(!timer)
+@@ -4826,7 +5003,7 @@
+ free(timer->ev_timer);
+ }
+
+-void
++void
+ comm_timer_callback(int ATTR_UNUSED(fd), short event, void* arg)
+ {
+ struct comm_timer* tm = (struct comm_timer*)arg;
+@@ -4838,19 +5015,19 @@
+ (*tm->callback)(tm->cb_arg);
+ }
+
+-int
++int
+ comm_timer_is_set(struct comm_timer* timer)
+ {
+ return (int)timer->ev_timer->enabled;
+ }
+
+-size_t
++size_t
+ comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer))
+ {
+ return sizeof(struct internal_timer);
+ }
+
+-struct comm_signal*
++struct comm_signal*
+ comm_signal_create(struct comm_base* base,
+ void (*callback)(int, void*), void* cb_arg)
+ {
+@@ -4867,7 +5044,7 @@
+ return com;
+ }
+
+-void
++void
+ comm_signal_callback(int sig, short event, void* arg)
+ {
+ struct comm_signal* comsig = (struct comm_signal*)arg;
+@@ -4878,10 +5055,10 @@
+ (*comsig->callback)(sig, comsig->cb_arg);
+ }
+
+-int
++int
+ comm_signal_bind(struct comm_signal* comsig, int sig)
+ {
+- struct internal_signal* entry = (struct internal_signal*)calloc(1,
++ struct internal_signal* entry = (struct internal_signal*)calloc(1,
+ sizeof(struct internal_signal));
+ if(!entry) {
+ log_err("malloc failed");
+@@ -4908,7 +5085,7 @@
+ return 1;
+ }
+
+-void
++void
+ comm_signal_delete(struct comm_signal* comsig)
+ {
+ struct internal_signal* p, *np;
+--- contrib/unbound/util/netevent.h.orig
++++ contrib/unbound/util/netevent.h
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -60,6 +60,7 @@
+ #ifndef NET_EVENT_H
+ #define NET_EVENT_H
+
++#include <sys/time.h>
+ #include "dnscrypt/dnscrypt.h"
+ #ifdef HAVE_NGHTTP2_NGHTTP2_H
+ #include <nghttp2/nghttp2.h>
+@@ -83,7 +84,7 @@
+ enum listen_type;
+
+ /** callback from communication point function type */
+-typedef int comm_point_callback_type(struct comm_point*, void*, int,
++typedef int comm_point_callback_type(struct comm_point*, void*, int,
+ struct comm_reply*);
+
+ /** to pass no_error to callback function */
+@@ -91,7 +92,7 @@
+ /** to pass closed connection to callback function */
+ #define NETEVENT_CLOSED -1
+ /** to pass timeout happened to callback function */
+-#define NETEVENT_TIMEOUT -2
++#define NETEVENT_TIMEOUT -2
+ /** to pass fallback from capsforID to callback function; 0x20 failed */
+ #define NETEVENT_CAPSFAIL -3
+ /** to pass done transfer to callback function; http file is complete */
+@@ -165,8 +166,8 @@
+ socklen_t client_addrlen;
+ };
+
+-/**
+- * Communication point to the network
++/**
++ * Communication point to the network
+ * These behaviours can be accomplished by setting the flags
+ * and passing return values from the callback.
+ * udp frontside: called after readdone. sendafter.
+@@ -206,7 +207,7 @@
+ int max_tcp_count;
+ /** current number of tcp handler in-use for this accept socket */
+ int cur_tcp_count;
+- /** malloced array of tcp handlers for a tcp-accept,
++ /** malloced array of tcp handlers for a tcp-accept,
+ of size max_tcp_count. */
+ struct comm_point** tcp_handlers;
+ /** linked list of free tcp_handlers to use for new queries.
+@@ -271,9 +272,9 @@
+ /** is this a UDP, TCP-accept or TCP socket. */
+ enum comm_point_type {
+ /** UDP socket - handle datagrams. */
+- comm_udp,
++ comm_udp,
+ /** TCP accept socket - only creates handlers if readable. */
+- comm_tcp_accept,
++ comm_tcp_accept,
+ /** TCP handler socket - handle byteperbyte readwrite. */
+ comm_tcp,
+ /** HTTP handler socket */
+@@ -282,7 +283,7 @@
+ comm_local,
+ /** raw - not DNS format - for pipe readers and writers */
+ comm_raw
+- }
++ }
+ /** variable with type of socket, UDP,TCP-accept,TCP,pipe */
+ type;
+
+@@ -303,7 +304,7 @@
+ /** if set the connection is NOT closed on delete. */
+ int do_not_close;
+
+- /** if set, the connection is closed on error, on timeout,
++ /** if set, the connection is closed on error, on timeout,
+ and after read/write completes. No callback is done. */
+ int tcp_do_close;
+
+@@ -383,15 +384,16 @@
+ /** number of queries outstanding on this socket, used by
+ * outside network for udp ports */
+ int inuse;
+-
++ /** the timestamp when the packet was received by the kernel */
++ struct timeval recv_tv;
+ /** callback when done.
+ tcp_accept does not get called back, is NULL then.
+ If a timeout happens, callback with timeout=1 is called.
+- If an error happens, callback is called with error set
++ If an error happens, callback is called with error set
+ nonzero. If not NETEVENT_NOERROR, it is an errno value.
+ If the connection is closed (by remote end) then the
+ callback is called with error set to NETEVENT_CLOSED=-1.
+- If a timeout happens on the connection, the error is set to
++ If a timeout happens on the connection, the error is set to
+ NETEVENT_TIMEOUT=-2.
+ The reply_info can be copied if the reply needs to happen at a
+ later time. It consists of a struct with commpoint and address.
+@@ -399,7 +401,7 @@
+ Note the reply information is temporary and must be copied.
+ NULL is passed for_reply info, in cases where error happened.
+
+- declare as:
++ declare as:
+ int my_callback(struct comm_point* c, void* my_arg, int error,
+ struct comm_reply *reply_info);
+
+@@ -446,14 +448,14 @@
+
+ /**
+ * Create a new comm base.
+- * @param sigs: if true it attempts to create a default loop for
++ * @param sigs: if true it attempts to create a default loop for
+ * signal handling.
+ * @return: the new comm base. NULL on error.
+ */
+ struct comm_base* comm_base_create(int sigs);
+
+ /**
+- * Create comm base that uses the given ub_event_base (underlying pluggable
++ * Create comm base that uses the given ub_event_base (underlying pluggable
+ * event mechanism pointer).
+ * @param base: underlying pluggable event base.
+ * @return: the new comm base. NULL on error.
+@@ -619,7 +621,7 @@
+ * @return: the commpoint or NULL on error.
+ */
+ struct comm_point* comm_point_create_local(struct comm_base* base,
+- int fd, size_t bufsize,
++ int fd, size_t bufsize,
+ comm_point_callback_type* callback, void* callback_arg);
+
+ /**
+@@ -632,7 +634,7 @@
+ * @return: the commpoint or NULL on error.
+ */
+ struct comm_point* comm_point_create_raw(struct comm_base* base,
+- int fd, int writing,
++ int fd, int writing,
+ comm_point_callback_type* callback, void* callback_arg);
+
+ /**
+@@ -722,7 +724,7 @@
+ * @param cb_arg: user callback argument.
+ * @return: the new timer or NULL on error.
+ */
+-struct comm_timer* comm_timer_create(struct comm_base* base,
++struct comm_timer* comm_timer_create(struct comm_base* base,
+ void (*cb)(void*), void* cb_arg);
+
+ /**
+@@ -792,7 +794,7 @@
+ * if -1, error message has been printed if necessary, simply drop
+ * out of the reading handler.
+ */
+-int comm_point_perform_accept(struct comm_point* c,
++int comm_point_perform_accept(struct comm_point* c,
+ struct sockaddr_storage* addr, socklen_t* addrlen);
+
+ /**** internal routines ****/
+@@ -801,7 +803,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for udp comm point.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -811,7 +813,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for udp ancillary data comm point.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -821,7 +823,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp accept comm point
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -831,7 +833,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp data comm point
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -841,7 +843,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for tcp data comm point
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -955,7 +957,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for timer comm.
+ * @param fd: file descriptor (always -1).
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_timer structure.
+ */
+@@ -965,7 +967,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * handle libevent callback for signal comm.
+ * @param fd: file descriptor (used for the signal number).
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the internal commsignal structure.
+ */
+@@ -975,7 +977,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * libevent callback for AF_UNIX fds
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -985,7 +987,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * libevent callback for raw fd access.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+@@ -995,7 +997,7 @@
+ * This routine is published for checks and tests, and is only used internally.
+ * libevent callback for timeout on slow accept.
+ * @param fd: file descriptor.
+- * @param event: event bits from libevent:
++ * @param event: event bits from libevent:
+ * EV_READ, EV_WRITE, EV_SIGNAL, EV_TIMEOUT.
+ * @param arg: the comm_point structure.
+ */
+--- contrib/unbound/util/proxy_protocol.c.orig
++++ contrib/unbound/util/proxy_protocol.c
+@@ -38,102 +38,162 @@
+ *
+ * This file contains PROXY protocol functions.
+ */
+-#include "config.h"
+-#include "util/log.h"
+ #include "util/proxy_protocol.h"
+
+-int
+-pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
++/**
++ * Internal struct initialized with function pointers for writing uint16 and
++ * uint32.
++ */
++struct proxy_protocol_data {
++ void (*write_uint16)(void* buf, uint16_t data);
++ void (*write_uint32)(void* buf, uint32_t data);
++};
++struct proxy_protocol_data pp_data;
++
++/**
++ * Internal lookup table; could be further generic like sldns_lookup_table
++ * for all the future generic stuff.
++ */
++struct proxy_protocol_lookup_table {
++ int id;
++ const char *text;
++};
++
++/**
++ * Internal parsing error text; could be exposed with pp_lookup_error.
++ */
++static struct proxy_protocol_lookup_table pp_parse_errors_data[] = {
++ { PP_PARSE_NOERROR, "no parse error" },
++ { PP_PARSE_SIZE, "not enough space for header" },
++ { PP_PARSE_WRONG_HEADERv2, "could not match PROXYv2 header" },
++ { PP_PARSE_UNKNOWN_CMD, "unknown command" },
++ { PP_PARSE_UNKNOWN_FAM_PROT, "unknown family and protocol" },
++};
++
++void
++pp_init(void (*write_uint16)(void* buf, uint16_t data),
++ void (*write_uint32)(void* buf, uint32_t data)) {
++ pp_data.write_uint16 = write_uint16;
++ pp_data.write_uint32 = write_uint32;
++}
++
++const char*
++pp_lookup_error(enum pp_parse_errors error) {
++ return pp_parse_errors_data[error].text;
++}
++
++size_t
++pp2_write_to_buf(uint8_t* buf, size_t buflen,
++#ifdef INET6
++ struct sockaddr_storage* src,
++#else
++ struct sockaddr_in* src,
++#endif
+ int stream)
+ {
+ int af;
++ size_t expected_size;
+ if(!src) return 0;
+ af = (int)((struct sockaddr_in*)src)->sin_family;
+- if(sldns_buffer_remaining(buf) <
+- PP2_HEADER_SIZE + (af==AF_INET?12:36)) {
++ expected_size = PP2_HEADER_SIZE + (af==AF_INET?12:36);
++ if(buflen < expected_size) {
+ return 0;
+ }
+ /* sig */
+- sldns_buffer_write(buf, PP2_SIG, PP2_SIG_LEN);
++ memcpy(buf, PP2_SIG, PP2_SIG_LEN);
++ buf += PP2_SIG_LEN;
+ /* version and command */
+- sldns_buffer_write_u8(buf, (PP2_VERSION << 4) | PP2_CMD_PROXY);
+- if(af==AF_INET) {
++ *buf = (PP2_VERSION << 4) | PP2_CMD_PROXY;
++ buf++;
++ switch(af) {
++ case AF_INET:
+ /* family and protocol */
+- sldns_buffer_write_u8(buf,
+- (PP2_AF_INET<<4) |
+- (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
++ *buf = (PP2_AF_INET<<4) |
++ (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
++ buf++;
+ /* length */
+- sldns_buffer_write_u16(buf, 12);
++ (*pp_data.write_uint16)(buf, 12);
++ buf += 2;
+ /* src addr */
+- sldns_buffer_write(buf,
++ memcpy(buf,
+ &((struct sockaddr_in*)src)->sin_addr.s_addr, 4);
++ buf += 4;
+ /* dst addr */
+- sldns_buffer_write_u32(buf, 0);
++ (*pp_data.write_uint32)(buf, 0);
++ buf += 4;
+ /* src port */
+- sldns_buffer_write(buf,
++ memcpy(buf,
+ &((struct sockaddr_in*)src)->sin_port, 2);
++ buf += 2;
++ /* dst addr */
+ /* dst port */
+- sldns_buffer_write_u16(buf, 0);
+- } else {
++ (*pp_data.write_uint16)(buf, 12);
++ break;
++#ifdef INET6
++ case AF_INET6:
+ /* family and protocol */
+- sldns_buffer_write_u8(buf,
+- (PP2_AF_INET6<<4) |
+- (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
++ *buf = (PP2_AF_INET6<<4) |
++ (stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
++ buf++;
+ /* length */
+- sldns_buffer_write_u16(buf, 36);
++ (*pp_data.write_uint16)(buf, 36);
++ buf += 2;
+ /* src addr */
+- sldns_buffer_write(buf,
++ memcpy(buf,
+ &((struct sockaddr_in6*)src)->sin6_addr, 16);
++ buf += 16;
+ /* dst addr */
+- sldns_buffer_set_at(buf,
+- sldns_buffer_position(buf), 0, 16);
+- sldns_buffer_skip(buf, 16);
++ memset(buf, 0, 16);
++ buf += 16;
+ /* src port */
+- sldns_buffer_write(buf,
+- &((struct sockaddr_in6*)src)->sin6_port, 2);
++ memcpy(buf, &((struct sockaddr_in6*)src)->sin6_port, 2);
++ buf += 2;
+ /* dst port */
+- sldns_buffer_write_u16(buf, 0);
++ (*pp_data.write_uint16)(buf, 0);
++ break;
++#endif /* INET6 */
++ case AF_UNIX:
++ /* fallthrough */
++ default:
++ return 0;
+ }
+- return 1;
++ return expected_size;
+ }
+
+-struct pp2_header*
+-pp2_read_header(struct sldns_buffer* buf)
++int
++pp2_read_header(uint8_t* buf, size_t buflen)
+ {
+ size_t size;
+- struct pp2_header* header = (struct pp2_header*)sldns_buffer_begin(buf);
++ struct pp2_header* header = (struct pp2_header*)buf;
+ /* Try to fail all the unsupported cases first. */
+- if(sldns_buffer_remaining(buf) < PP2_HEADER_SIZE) {
+- log_err("proxy_protocol: not enough space for header");
+- return NULL;
++ if(buflen < PP2_HEADER_SIZE) {
++ return PP_PARSE_SIZE;
+ }
+ /* Check for PROXYv2 header */
+ if(memcmp(header, PP2_SIG, PP2_SIG_LEN) != 0 ||
+ ((header->ver_cmd & 0xF0)>>4) != PP2_VERSION) {
+- log_err("proxy_protocol: could not match PROXYv2 header");
+- return NULL;
++ return PP_PARSE_WRONG_HEADERv2;
+ }
+ /* Check the length */
+ size = PP2_HEADER_SIZE + ntohs(header->len);
+- if(sldns_buffer_remaining(buf) < size) {
+- log_err("proxy_protocol: not enough space for header");
+- return NULL;
++ if(buflen < size) {
++ return PP_PARSE_SIZE;
+ }
+ /* Check for supported commands */
+ if((header->ver_cmd & 0xF) != PP2_CMD_LOCAL &&
+ (header->ver_cmd & 0xF) != PP2_CMD_PROXY) {
+- log_err("proxy_protocol: unsupported command");
+- return NULL;
++ return PP_PARSE_UNKNOWN_CMD;
+ }
+ /* Check for supported family and protocol */
+- if(header->fam_prot != 0x00 /* AF_UNSPEC|UNSPEC */ &&
+- header->fam_prot != 0x11 /* AF_INET|STREAM */ &&
+- header->fam_prot != 0x12 /* AF_INET|DGRAM */ &&
+- header->fam_prot != 0x21 /* AF_INET6|STREAM */ &&
+- header->fam_prot != 0x22 /* AF_INET6|DGRAM */) {
+- log_err("proxy_protocol: unsupported family and protocol");
+- return NULL;
++ if(header->fam_prot != PP2_UNSPEC_UNSPEC &&
++ header->fam_prot != PP2_INET_STREAM &&
++ header->fam_prot != PP2_INET_DGRAM &&
++ header->fam_prot != PP2_INET6_STREAM &&
++ header->fam_prot != PP2_INET6_DGRAM &&
++ header->fam_prot != PP2_UNIX_STREAM &&
++ header->fam_prot != PP2_UNIX_DGRAM) {
++ return PP_PARSE_UNKNOWN_FAM_PROT;
+ }
+ /* We have a correct header */
+- return header;
++ return PP_PARSE_NOERROR;
+ }
+--- contrib/unbound/util/proxy_protocol.h.orig
++++ contrib/unbound/util/proxy_protocol.h
+@@ -42,7 +42,7 @@
+ #ifndef PROXY_PROTOCOL_H
+ #define PROXY_PROTOCOL_H
+
+-#include "sldns/sbuffer.h"
++#include "config.h"
+
+ /** PROXYv2 minimum header size */
+ #define PP2_HEADER_SIZE 16
+@@ -51,11 +51,11 @@
+ #define PP2_SIG "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
+ #define PP2_SIG_LEN 12
+
+-/** PROXYv2 version */
++/** PROXYv2 version (protocol value) */
+ #define PP2_VERSION 0x2
+
+ /**
+- * PROXYv2 command.
++ * PROXYv2 command (protocol value).
+ */
+ enum pp2_command {
+ PP2_CMD_LOCAL = 0x0,
+@@ -63,7 +63,7 @@
+ };
+
+ /**
+- * PROXYv2 address family.
++ * PROXYv2 address family (protocol value).
+ */
+ enum pp2_af {
+ PP2_AF_UNSPEC = 0x0,
+@@ -73,7 +73,7 @@
+ };
+
+ /**
+- * PROXYv2 protocol.
++ * PROXYv2 protocol (protocol value).
+ */
+ enum pp2_protocol {
+ PP2_PROT_UNSPEC = 0x0,
+@@ -81,6 +81,19 @@
+ PP2_PROT_DGRAM = 0x2
+ };
+
++/**
++ * Expected combinations of address family and protocol values used in checks.
++ */
++enum pp2_af_protocol_combination {
++ PP2_UNSPEC_UNSPEC = (PP2_AF_UNSPEC<<4)|PP2_PROT_UNSPEC,
++ PP2_INET_STREAM = (PP2_AF_INET<<4)|PP2_PROT_STREAM,
++ PP2_INET_DGRAM = (PP2_AF_INET<<4)|PP2_PROT_DGRAM,
++ PP2_INET6_STREAM = (PP2_AF_INET6<<4)|PP2_PROT_STREAM,
++ PP2_INET6_DGRAM = (PP2_AF_INET6<<4)|PP2_PROT_DGRAM,
++ PP2_UNIX_STREAM = (PP2_AF_UNIX<<4)|PP2_PROT_STREAM,
++ PP2_UNIX_DGRAM = (PP2_AF_UNIX<<4)|PP2_PROT_DGRAM
++};
++
+ /**
+ * PROXYv2 header.
+ */
+@@ -109,23 +122,56 @@
+ } addr;
+ };
+
++/**
++ * PROXY parse errors.
++ */
++enum pp_parse_errors {
++ PP_PARSE_NOERROR = 0,
++ PP_PARSE_SIZE,
++ PP_PARSE_WRONG_HEADERv2,
++ PP_PARSE_UNKNOWN_CMD,
++ PP_PARSE_UNKNOWN_FAM_PROT,
++};
++
++/**
++ * Initialize the internal proxy structure.
++ * @param write_uint16: pointer to a function that can write uint16.
++ * @param write_uint32: pointer to a function that can write uint32.
++ */
++void pp_init(void (*write_uint16)(void* buf, uint16_t data),
++ void (*write_uint32)(void* buf, uint32_t data));
++
++/**
++ * Lookup the parsing error description.
++ * @param error: parsing error from pp2_read_header.
++ * @return the description.
++ */
++const char* pp_lookup_error(enum pp_parse_errors error);
++
+ /**
+ * Write a PROXYv2 header at the current position of the buffer.
+- * @param buf: the buffer to write to.
++ * @param buf: pointer to the buffer to write data to.
++ * @param buflen: available size on the buffer.
+ * @param src: the source address.
+ * @param stream: if the protocol is stream or datagram.
+ * @return 1 on success, 0 on failure.
+ */
+-int pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
++size_t pp2_write_to_buf(uint8_t* buf, size_t buflen,
++#ifdef INET6
++ struct sockaddr_storage* src,
++#else
++ struct sockaddr_in* src,
++#endif
+ int stream);
+
+ /**
+ * Read a PROXYv2 header from the current position of the buffer.
+ * It does initial validation and returns a pointer to the buffer position on
+ * success.
+- * @param buf: the buffer to read from.
+- * @return the pointer to the buffer position on success, NULL on error.
++ * @param buf: pointer to the buffer data to read from.
++ * @param buflen: available size on the buffer.
++ * @return parsing error, 0 on success.
+ */
+-struct pp2_header* pp2_read_header(struct sldns_buffer* buf);
++int pp2_read_header(uint8_t* buf, size_t buflen);
+
+ #endif /* PROXY_PROTOCOL_H */
+--- contrib/unbound/util/regional.c.orig
++++ contrib/unbound/util/regional.c
+@@ -186,7 +186,7 @@
+ {
+ void *s = regional_alloc(r, size);
+ if(!s) return NULL;
+- memcpy(s, init, size);
++ memmove(s, init, size);
+ return s;
+ }
+
+--- /dev/null
++++ contrib/unbound/util/rfc_1982.c
+@@ -0,0 +1,75 @@
++/*
++ * util/rfc_1982.c - RFC 1982 Serial Number Arithmetic
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains functions for RFC 1982 serial number arithmetic.
++ */
++#include "config.h"
++#include "util/rfc_1982.h"
++
++int
++compare_1982(uint32_t a, uint32_t b)
++{
++ /* for 32 bit values */
++ const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
++
++ if (a == b) {
++ return 0;
++ } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
++ return -1;
++ } else {
++ return 1;
++ }
++}
++
++uint32_t
++subtract_1982(uint32_t a, uint32_t b)
++{
++ /* for 32 bit values */
++ const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
++
++ if(a == b)
++ return 0;
++ if(a < b && b - a < cutoff) {
++ return b-a;
++ }
++ if(a > b && a - b > cutoff) {
++ return ((uint32_t)0xffffffff) - (a-b-1);
++ }
++ /* wrong case, b smaller than a */
++ return 0;
++}
+--- /dev/null
++++ contrib/unbound/util/rfc_1982.h
+@@ -0,0 +1,63 @@
++/*
++ * util/rfc_1982.h - RFC 1982 Serial Number Arithmetic
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains functions for RFC 1982 serial number arithmetic.
++ */
++#ifndef RFC_1982_H
++#define RFC_1982_H
++
++/**
++ * RFC 1982 comparison, uses unsigned integers, and tries to avoid
++ * compiler optimization (eg. by avoiding a-b<0 comparisons).
++ * @param a: value to compare.
++ * @param b: value to compare.
++ * @return 0 if equal, 1 if a > b, else -1.
++ */
++int compare_1982(uint32_t a, uint32_t b);
++
++/**
++ * RFC 1982 subtraction, uses unsigned integers, and tries to avoid
++ * compiler optimization (eg. by avoiding a-b<0 comparisons).
++ * @param a: value to subtract from.
++ * @param b: value to subtract.
++ * @return the difference between them if we know that b is larger than a,
++ * that is the distance between them in serial number arithmetic.
++ */
++uint32_t subtract_1982(uint32_t a, uint32_t b);
++
++#endif /* RFC_1982_H */
+--- /dev/null
++++ contrib/unbound/util/siphash.c
+@@ -0,0 +1,192 @@
++/*
++ SipHash reference C implementation
++
++ Copyright (c) 2012-2016 Jean-Philippe Aumasson
++ <jeanphilippe.aumasson@gmail.com>
++ Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
++
++ To the extent possible under law, the author(s) have dedicated all copyright
++ and related and neighboring rights to this software to the public domain
++ worldwide. This software is distributed without any warranty.
++
++ You should have received a copy of the CC0 Public Domain Dedication along
++ with
++ this software. If not, see
++ <http://creativecommons.org/publicdomain/zero/1.0/>.
++ */
++/**
++ * Edited slightly for integration in Unbound. Edits are noted with 'EDIT'.
++ */
++/** EDIT
++ * \#include <assert.h>
++ * \#include <stdint.h>
++ * \#include <stdio.h>
++ * \#include <string.h>
++ * Replaced the above includes with Unbound's config.h
++ */
++#include "config.h"
++
++/** EDIT
++ * prevent warning from -Wmissing-prototypes
++ */
++#include "util/siphash.h"
++
++/* default: SipHash-2-4 */
++#define cROUNDS 2
++#define dROUNDS 4
++
++#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
++
++#define U32TO8_LE(p, v) \
++ (p)[0] = (uint8_t)((v)); \
++ (p)[1] = (uint8_t)((v) >> 8); \
++ (p)[2] = (uint8_t)((v) >> 16); \
++ (p)[3] = (uint8_t)((v) >> 24);
++
++#define U64TO8_LE(p, v) \
++ U32TO8_LE((p), (uint32_t)((v))); \
++ U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
++
++#define U8TO64_LE(p) \
++ (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
++ ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
++ ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
++ ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
++
++#define SIPROUND \
++ do { \
++ v0 += v1; \
++ v1 = ROTL(v1, 13); \
++ v1 ^= v0; \
++ v0 = ROTL(v0, 32); \
++ v2 += v3; \
++ v3 = ROTL(v3, 16); \
++ v3 ^= v2; \
++ v0 += v3; \
++ v3 = ROTL(v3, 21); \
++ v3 ^= v0; \
++ v2 += v1; \
++ v1 = ROTL(v1, 17); \
++ v1 ^= v2; \
++ v2 = ROTL(v2, 32); \
++ } while (0)
++
++#ifdef DEBUG
++#define TRACE \
++ do { \
++ printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \
++ (uint32_t)v0); \
++ printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \
++ (uint32_t)v1); \
++ printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \
++ (uint32_t)v2); \
++ printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \
++ (uint32_t)v3); \
++ } while (0)
++#else
++#define TRACE
++#endif
++
++int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
++ uint8_t *out, const size_t outlen) {
++
++ uint64_t v0 = 0x736f6d6570736575ULL;
++ uint64_t v1 = 0x646f72616e646f6dULL;
++ uint64_t v2 = 0x6c7967656e657261ULL;
++ uint64_t v3 = 0x7465646279746573ULL;
++ uint64_t k0 = U8TO64_LE(k);
++ uint64_t k1 = U8TO64_LE(k + 8);
++ uint64_t m;
++ int i;
++ const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
++ const int left = inlen & 7;
++ uint64_t b = ((uint64_t)inlen) << 56;
++ /** EDIT
++ * The following assert moved here from the top for C90 compliance.
++ */
++ assert((outlen == 8) || (outlen == 16));
++ v3 ^= k1;
++ v2 ^= k0;
++ v1 ^= k1;
++ v0 ^= k0;
++
++ if (outlen == 16)
++ v1 ^= 0xee;
++
++ for (; in != end; in += 8) {
++ m = U8TO64_LE(in);
++ v3 ^= m;
++
++ TRACE;
++ for (i = 0; i < cROUNDS; ++i)
++ SIPROUND;
++
++ v0 ^= m;
++ }
++
++ switch (left) {
++ case 7:
++ b |= ((uint64_t)in[6]) << 48;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 6:
++ b |= ((uint64_t)in[5]) << 40;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 5:
++ b |= ((uint64_t)in[4]) << 32;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 4:
++ b |= ((uint64_t)in[3]) << 24;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 3:
++ b |= ((uint64_t)in[2]) << 16;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 2:
++ b |= ((uint64_t)in[1]) << 8;
++ /** EDIT annotate case statement fallthrough for gcc */
++ /* fallthrough */
++ case 1:
++ b |= ((uint64_t)in[0]);
++ break;
++ case 0:
++ break;
++ }
++
++ v3 ^= b;
++
++ TRACE;
++ for (i = 0; i < cROUNDS; ++i)
++ SIPROUND;
++
++ v0 ^= b;
++
++ if (outlen == 16)
++ v2 ^= 0xee;
++ else
++ v2 ^= 0xff;
++
++ TRACE;
++ for (i = 0; i < dROUNDS; ++i)
++ SIPROUND;
++
++ b = v0 ^ v1 ^ v2 ^ v3;
++ U64TO8_LE(out, b);
++
++ if (outlen == 8)
++ return 0;
++
++ v1 ^= 0xdd;
++
++ TRACE;
++ for (i = 0; i < dROUNDS; ++i)
++ SIPROUND;
++
++ b = v0 ^ v1 ^ v2 ^ v3;
++ U64TO8_LE(out + 8, b);
++
++ return 0;
++}
+--- /dev/null
++++ contrib/unbound/util/siphash.h
+@@ -0,0 +1,43 @@
++/*
++ * util/siphash.h - header for SipHash reference C implementation.
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++/**
++ * \file
++ * Contains the SipHash reference C implementation.
++ */
++#ifndef UTIL_SIPHASH_H
++#define UTIL_SIPHASH_H
++int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
++ uint8_t *out, const size_t outlen);
++#endif /* UTIL_SIPHASH_H */
+--- contrib/unbound/util/storage/lruhash.c.orig
++++ contrib/unbound/util/storage/lruhash.c
+@@ -81,6 +81,7 @@
+ table->num = 0;
+ table->space_used = 0;
+ table->space_max = maxmem;
++ table->max_collisions = 0;
+ table->array = calloc(table->size, sizeof(struct lruhash_bin));
+ if(!table->array) {
+ lock_quick_destroy(&table->lock);
+@@ -216,15 +217,19 @@
+
+ struct lruhash_entry*
+ bin_find_entry(struct lruhash* table,
+- struct lruhash_bin* bin, hashvalue_type hash, void* key)
++ struct lruhash_bin* bin, hashvalue_type hash, void* key, size_t* collisions)
+ {
++ size_t c = 0;
+ struct lruhash_entry* p = bin->overflow_list;
+ while(p) {
+ if(p->hash == hash && table->compfunc(p->key, key) == 0)
+- return p;
++ break;
++ c++;
+ p = p->overflow_next;
+ }
+- return NULL;
++ if (collisions != NULL)
++ *collisions = c;
++ return p;
+ }
+
+ void
+@@ -303,6 +308,7 @@
+ struct lruhash_bin* bin;
+ struct lruhash_entry* found, *reclaimlist=NULL;
+ size_t need_size;
++ size_t collisions;
+ fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
+ fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
+ fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
+@@ -317,12 +323,14 @@
+ lock_quick_lock(&bin->lock);
+
+ /* see if entry exists already */
+- if(!(found=bin_find_entry(table, bin, hash, entry->key))) {
++ if(!(found=bin_find_entry(table, bin, hash, entry->key, &collisions))) {
+ /* if not: add to bin */
+ entry->overflow_next = bin->overflow_list;
+ bin->overflow_list = entry;
+ lru_front(table, entry);
+ table->num++;
++ if (table->max_collisions < collisions)
++ table->max_collisions = collisions;
+ table->space_used += need_size;
+ } else {
+ /* if so: update data - needs a writelock */
+@@ -362,7 +370,7 @@
+ lock_quick_lock(&table->lock);
+ bin = &table->array[hash & table->size_mask];
+ lock_quick_lock(&bin->lock);
+- if((entry=bin_find_entry(table, bin, hash, key)))
++ if((entry=bin_find_entry(table, bin, hash, key, NULL)))
+ lru_touch(table, entry);
+ lock_quick_unlock(&table->lock);
+
+@@ -389,7 +397,7 @@
+ lock_quick_lock(&table->lock);
+ bin = &table->array[hash & table->size_mask];
+ lock_quick_lock(&bin->lock);
+- if((entry=bin_find_entry(table, bin, hash, key))) {
++ if((entry=bin_find_entry(table, bin, hash, key, NULL))) {
+ bin_overflow_remove(bin, entry);
+ lru_remove(table, entry);
+ } else {
+@@ -579,6 +587,7 @@
+ struct lruhash_bin* bin;
+ struct lruhash_entry* found, *reclaimlist = NULL;
+ size_t need_size;
++ size_t collisions;
+ fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
+ fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
+ fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
+@@ -593,7 +602,7 @@
+ lock_quick_lock(&bin->lock);
+
+ /* see if entry exists already */
+- if ((found = bin_find_entry(table, bin, hash, entry->key)) != NULL) {
++ if ((found = bin_find_entry(table, bin, hash, entry->key, &collisions)) != NULL) {
+ /* if so: keep the existing data - acquire a writelock */
+ lock_rw_wrlock(&found->lock);
+ }
+@@ -604,6 +613,8 @@
+ bin->overflow_list = entry;
+ lru_front(table, entry);
+ table->num++;
++ if (table->max_collisions < collisions)
++ table->max_collisions = collisions;
+ table->space_used += need_size;
+ /* return the entry that was presented, and lock it */
+ found = entry;
+--- contrib/unbound/util/storage/lruhash.h.orig
++++ contrib/unbound/util/storage/lruhash.h
+@@ -178,6 +178,8 @@
+ size_t space_used;
+ /** the amount of space the hash table is maximally allowed to use. */
+ size_t space_max;
++ /** the maximum collisions were detected during the lruhash_insert operations. */
++ size_t max_collisions;
+ };
+
+ /**
+@@ -357,10 +359,11 @@
+ * @param bin: hash bin to look into.
+ * @param hash: hash value to look for.
+ * @param key: key to look for.
++ * @param collisions: how many collisions were found during the search.
+ * @return: the entry or NULL if not found.
+ */
+ struct lruhash_entry* bin_find_entry(struct lruhash* table,
+- struct lruhash_bin* bin, hashvalue_type hash, void* key);
++ struct lruhash_bin* bin, hashvalue_type hash, void* key, size_t* collisions);
+
+ /**
+ * Remove entry from bin overflow chain.
+--- contrib/unbound/util/storage/slabhash.c.orig
++++ contrib/unbound/util/storage/slabhash.c
+@@ -242,3 +242,21 @@
+ }
+ return cnt;
+ }
++
++void get_slabhash_stats(struct slabhash* sh, long long* num, long long* collisions)
++{
++ size_t slab, cnt = 0, max_collisions = 0;
++
++ for(slab=0; slab<sh->size; slab++) {
++ lock_quick_lock(&sh->array[slab]->lock);
++ cnt += sh->array[slab]->num;
++ if (max_collisions < sh->array[slab]->max_collisions) {
++ max_collisions = sh->array[slab]->max_collisions;
++ }
++ lock_quick_unlock(&sh->array[slab]->lock);
++ }
++ if (num != NULL)
++ *num = cnt;
++ if (collisions != NULL)
++ *collisions = max_collisions;
++}
+--- contrib/unbound/util/storage/slabhash.h.orig
++++ contrib/unbound/util/storage/slabhash.h
+@@ -200,6 +200,15 @@
+ */
+ size_t count_slabhash_entries(struct slabhash* table);
+
++/**
++ * Retrieves number of items in slabhash and the current max collision level
++ * @param table: slabbed hash table.
++ * @param entries_count: where to save the current number of elements.
++ * @param max_collisions: where to save the current max collisions level.
++ */
++void get_slabhash_stats(struct slabhash* table,
++ long long* entries_count, long long* max_collisions);
++
+ /* --- test representation --- */
+ /** test structure contains test key */
+ struct slabhash_testkey {
+--- contrib/unbound/util/timehist.c.orig
++++ contrib/unbound/util/timehist.c
+@@ -4,22 +4,22 @@
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+- *
++ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+- *
++ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+- *
++ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+- *
++ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+- *
++ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+@@ -46,6 +46,7 @@
+ #include <sys/types.h>
+ #include "util/timehist.h"
+ #include "util/log.h"
++#include "util/timeval_func.h"
+
+ /** special timestwo operation for time values in histogram setup */
+ static void
+@@ -83,12 +84,12 @@
+
+ struct timehist* timehist_setup(void)
+ {
+- struct timehist* hist = (struct timehist*)calloc(1,
++ struct timehist* hist = (struct timehist*)calloc(1,
+ sizeof(struct timehist));
+ if(!hist)
+ return NULL;
+ hist->num = NUM_BUCKETS_HIST;
+- hist->buckets = (struct th_buck*)calloc(hist->num,
++ hist->buckets = (struct th_buck*)calloc(hist->num,
+ sizeof(struct th_buck));
+ if(!hist->buckets) {
+ free(hist);
+@@ -114,23 +115,6 @@
+ hist->buckets[i].count = 0;
+ }
+
+-/** histogram compare of time values */
+-static int
+-timeval_smaller(const struct timeval* x, const struct timeval* y)
+-{
+-#ifndef S_SPLINT_S
+- if(x->tv_sec < y->tv_sec)
+- return 1;
+- else if(x->tv_sec == y->tv_sec) {
+- if(x->tv_usec <= y->tv_usec)
+- return 1;
+- else return 0;
+- }
+- else return 0;
+-#endif
+-}
+-
+-
+ void timehist_insert(struct timehist* hist, struct timeval* tv)
+ {
+ size_t i;
+@@ -194,7 +178,7 @@
+ return res;
+ }
+
+-double
++double
+ timehist_quartile(struct timehist* hist, double q)
+ {
+ double lookfor, passed, res;
+@@ -209,22 +193,22 @@
+ lookfor *= q;
+ passed = 0;
+ i = 0;
+- while(i+1 < hist->num &&
++ while(i+1 < hist->num &&
+ passed+(double)hist->buckets[i].count < lookfor) {
+ passed += (double)hist->buckets[i++].count;
+ }
+ /* got the right bucket */
+ #ifndef S_SPLINT_S
+- low = (double)hist->buckets[i].lower.tv_sec +
++ low = (double)hist->buckets[i].lower.tv_sec +
+ (double)hist->buckets[i].lower.tv_usec/1000000.;
+- up = (double)hist->buckets[i].upper.tv_sec +
++ up = (double)hist->buckets[i].upper.tv_sec +
+ (double)hist->buckets[i].upper.tv_usec/1000000.;
+ #endif
+ res = (lookfor - passed)*(up-low)/((double)hist->buckets[i].count);
+ return low+res;
+ }
+
+-void
++void
+ timehist_export(struct timehist* hist, long long* array, size_t sz)
+ {
+ size_t i;
+@@ -235,7 +219,7 @@
+ array[i] = (long long)hist->buckets[i].count;
+ }
+
+-void
++void
+ timehist_import(struct timehist* hist, long long* array, size_t sz)
+ {
+ size_t i;
+--- /dev/null
++++ contrib/unbound/util/timeval_func.c
+@@ -0,0 +1,113 @@
++/*
++ * util/timeval_func.c - helpers to work with struct timeval values.
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains helpers to manipulate struct timeval values.
++ */
++
++#include "config.h"
++#include "timeval_func.h"
++
++/** subtract timers and the values do not overflow or become negative */
++void
++timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start)
++{
++#ifndef S_SPLINT_S
++ time_t end_usec = end->tv_usec;
++ d->tv_sec = end->tv_sec - start->tv_sec;
++ if(end_usec < start->tv_usec) {
++ end_usec += 1000000;
++ d->tv_sec--;
++ }
++ d->tv_usec = end_usec - start->tv_usec;
++#endif
++}
++
++/** add timers and the values do not overflow or become negative */
++void
++timeval_add(struct timeval* d, const struct timeval* add)
++{
++#ifndef S_SPLINT_S
++ d->tv_sec += add->tv_sec;
++ d->tv_usec += add->tv_usec;
++ if(d->tv_usec >= 1000000 ) {
++ d->tv_usec -= 1000000;
++ d->tv_sec++;
++ }
++#endif
++}
++
++/** divide sum of timers to get average */
++void
++timeval_divide(struct timeval* avg, const struct timeval* sum, long long d)
++{
++#ifndef S_SPLINT_S
++ long long leftover;
++ if(d <= 0) {
++ avg->tv_sec = 0;
++ avg->tv_usec = 0;
++ return;
++ }
++ avg->tv_sec = sum->tv_sec / d;
++ avg->tv_usec = sum->tv_usec / d;
++ /* handle fraction from seconds divide */
++ leftover = sum->tv_sec - avg->tv_sec*d;
++ if(leftover <= 0)
++ leftover = 0;
++ avg->tv_usec += (((long long)leftover)*((long long)1000000))/d;
++ if(avg->tv_sec < 0)
++ avg->tv_sec = 0;
++ if(avg->tv_usec < 0)
++ avg->tv_usec = 0;
++#endif
++}
++
++/** histogram compare of time values */
++int
++timeval_smaller(const struct timeval* x, const struct timeval* y)
++{
++#ifndef S_SPLINT_S
++ if(x->tv_sec < y->tv_sec)
++ return 1;
++ else if(x->tv_sec == y->tv_sec) {
++ if(x->tv_usec <= y->tv_usec)
++ return 1;
++ else return 0;
++ }
++ else return 0;
++#endif
++}
+--- /dev/null
++++ contrib/unbound/util/timeval_func.h
+@@ -0,0 +1,53 @@
++/*
++ * util/timeval_func.h - definitions of helpers for struct timeval values.
++ *
++ * Copyright (c) 2023, NLnet Labs. All rights reserved.
++ *
++ * This software is open source.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * Redistributions of source code must retain the above copyright notice,
++ * this list of conditions and the following disclaimer.
++ *
++ * Redistributions in binary form must reproduce the above copyright notice,
++ * this list of conditions and the following disclaimer in the documentation
++ * and/or other materials provided with the distribution.
++ *
++ * Neither the name of the NLNET LABS nor the names of its contributors may
++ * be used to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
++ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
++ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
++ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++/**
++ * \file
++ *
++ * This file contains definitions of helpers to manipulate struct timeval
++ * values, implemented in the corresponding C file.
++ */
++#include <sys/time.h>
++
++#ifndef timeval_isset
++#define timeval_isset(tv) ((tv)->tv_sec || (tv)->tv_usec)
++#endif
++#ifndef timeval_clear
++#define timeval_clear(tv) ((tv)->tv_sec = (tv)->tv_usec = 0)
++#endif
++void timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start);
++void timeval_add(struct timeval* d, const struct timeval* add);
++void timeval_divide(struct timeval* avg, const struct timeval* sum, long long d);
++int timeval_smaller(const struct timeval* x, const struct timeval* y);
+--- contrib/unbound/validator/autotrust.c.orig
++++ contrib/unbound/validator/autotrust.c
+@@ -2376,6 +2376,8 @@
+ edns.opt_list_out = NULL;
+ edns.opt_list_inplace_cb_out = NULL;
+ edns.padding_block_size = 0;
++ edns.cookie_present = 0;
++ edns.cookie_valid = 0;
+ if(sldns_buffer_capacity(buf) < 65535)
+ edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
+ else edns.udp_size = 65535;
+--- contrib/unbound/validator/val_anchor.c.orig
++++ contrib/unbound/validator/val_anchor.c
+@@ -1322,3 +1322,24 @@
+ free(taglist);
+ return 0;
+ }
++
++struct trust_anchor*
++anchors_find_any_noninsecure(struct val_anchors* anchors)
++{
++ struct trust_anchor* ta, *next;
++ lock_basic_lock(&anchors->lock);
++ ta=(struct trust_anchor*)rbtree_first(anchors->tree);
++ while((rbnode_type*)ta != RBTREE_NULL) {
++ next = (struct trust_anchor*)rbtree_next(&ta->node);
++ lock_basic_lock(&ta->lock);
++ if(ta->numDS != 0 || ta->numDNSKEY != 0) {
++ /* not an insecurepoint */
++ lock_basic_unlock(&anchors->lock);
++ return ta;
++ }
++ lock_basic_unlock(&ta->lock);
++ ta = next;
++ }
++ lock_basic_unlock(&anchors->lock);
++ return NULL;
++}
+--- contrib/unbound/validator/val_anchor.h.orig
++++ contrib/unbound/validator/val_anchor.h
+@@ -240,4 +240,12 @@
+ int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs,
+ size_t namelen, uint16_t dclass, uint16_t keytag);
+
++/**
++ * Find an anchor that is not an insecure point, if any, or there are no
++ * DNSSEC verification anchors if none.
++ * @param anchors: anchor storage
++ * @return trust anchor or NULL. It is locked.
++ */
++struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors);
++
+ #endif /* VALIDATOR_VAL_ANCHOR_H */
+--- contrib/unbound/validator/val_kcache.c.orig
++++ contrib/unbound/validator/val_kcache.c
+@@ -81,17 +81,11 @@
+
+ void
+ key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey,
+- struct module_qstate* qstate)
++ int copy_reason)
+ {
+- struct key_entry_key* k = key_entry_copy(kkey);
++ struct key_entry_key* k = key_entry_copy(kkey, copy_reason);
+ if(!k)
+ return;
+- if(key_entry_isbad(k) && qstate->errinf &&
+- qstate->env->cfg->val_log_level >= 2) {
+- /* on malloc failure there is simply no reason string */
+- key_entry_set_reason(k, errinf_to_str_bogus(qstate));
+- key_entry_set_reason_bogus(k, errinf_to_reason_bogus(qstate));
+- }
+ key_entry_hash(k);
+ slabhash_insert(kcache->slab, k->entry.hash, &k->entry,
+ k->entry.data, NULL);
+--- contrib/unbound/validator/val_kcache.h.orig
++++ contrib/unbound/validator/val_kcache.h
+@@ -76,10 +76,10 @@
+ * @param kcache: the key cache.
+ * @param kkey: key entry key, assumed malloced in a region, is copied
+ * to perform update or insertion. Its data pointer is also copied.
+- * @param qstate: store errinf reason in case its bad.
++ * @param copy_reason: if the reason string needs to be copied (allocated).
+ */
+ void key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey,
+- struct module_qstate* qstate);
++ int copy_reason);
+
+ /**
+ * Remove an entry from the key cache.
+--- contrib/unbound/validator/val_kentry.c.orig
++++ contrib/unbound/validator/val_kentry.c
+@@ -152,7 +152,7 @@
+ }
+
+ struct key_entry_key*
+-key_entry_copy(struct key_entry_key* kkey)
++key_entry_copy(struct key_entry_key* kkey, int copy_reason)
+ {
+ struct key_entry_key* newk;
+ if(!kkey)
+@@ -190,7 +190,7 @@
+ }
+ packed_rrset_ptr_fixup(newd->rrset_data);
+ }
+- if(d->reason) {
++ if(copy_reason && d->reason && *d->reason != 0) {
+ newd->reason = strdup(d->reason);
+ if(!newd->reason) {
+ free(newd->rrset_data);
+@@ -199,6 +199,8 @@
+ free(newk);
+ return NULL;
+ }
++ } else {
++ newd->reason = NULL;
+ }
+ if(d->algo) {
+ newd->algo = (uint8_t*)strdup((char*)d->algo);
+@@ -237,22 +239,6 @@
+ return (int)(d->isbad);
+ }
+
+-void
+-key_entry_set_reason(struct key_entry_key* kkey, char* reason)
+-{
+- struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data;
+- d->reason = reason;
+-}
+-
+-void
+-key_entry_set_reason_bogus(struct key_entry_key* kkey, sldns_ede_code ede)
+-{
+- struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data;
+- if (ede != LDNS_EDE_NONE) { /* reason_bogus init is LDNS_EDE_NONE already */
+- d->reason_bogus = ede;
+- }
+-}
+-
+ char*
+ key_entry_get_reason(struct key_entry_key* kkey)
+ {
+@@ -294,6 +280,7 @@
+ struct key_entry_key*
+ key_entry_create_null(struct regional* region,
+ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now)
+ {
+ struct key_entry_key* k;
+@@ -302,8 +289,10 @@
+ return NULL;
+ d->ttl = now + ttl;
+ d->isbad = 0;
+- d->reason = NULL;
+- d->reason_bogus = LDNS_EDE_NONE;
++ d->reason = (!reason || *reason == 0)
++ ?NULL :(char*)regional_strdup(region, reason);
++ /* On allocation error we don't store the reason string */
++ d->reason_bogus = reason_bogus;
+ d->rrset_type = LDNS_RR_TYPE_DNSKEY;
+ d->rrset_data = NULL;
+ d->algo = NULL;
+@@ -313,7 +302,9 @@
+ struct key_entry_key*
+ key_entry_create_rrset(struct regional* region,
+ uint8_t* name, size_t namelen, uint16_t dclass,
+- struct ub_packed_rrset_key* rrset, uint8_t* sigalg, time_t now)
++ struct ub_packed_rrset_key* rrset, uint8_t* sigalg,
++ sldns_ede_code reason_bogus, const char* reason,
++ time_t now)
+ {
+ struct key_entry_key* k;
+ struct key_entry_data* d;
+@@ -323,8 +314,10 @@
+ return NULL;
+ d->ttl = rd->ttl + now;
+ d->isbad = 0;
+- d->reason = NULL;
+- d->reason_bogus = LDNS_EDE_NONE;
++ d->reason = (!reason || *reason == 0)
++ ?NULL :(char*)regional_strdup(region, reason);
++ /* On allocation error we don't store the reason string */
++ d->reason_bogus = reason_bogus;
+ d->rrset_type = ntohs(rrset->rk.type);
+ d->rrset_data = (struct packed_rrset_data*)regional_alloc_init(region,
+ rd, packed_rrset_sizeof(rd));
+@@ -341,7 +334,8 @@
+
+ struct key_entry_key*
+ key_entry_create_bad(struct regional* region,
+- uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now)
+ {
+ struct key_entry_key* k;
+@@ -350,8 +344,10 @@
+ return NULL;
+ d->ttl = now + ttl;
+ d->isbad = 1;
+- d->reason = NULL;
+- d->reason_bogus = LDNS_EDE_NONE;
++ d->reason = (!reason || *reason == 0)
++ ?NULL :(char*)regional_strdup(region, reason);
++ /* On allocation error we don't store the reason string */
++ d->reason_bogus = reason_bogus;
+ d->rrset_type = LDNS_RR_TYPE_DNSKEY;
+ d->rrset_data = NULL;
+ d->algo = NULL;
+--- contrib/unbound/validator/val_kentry.h.orig
++++ contrib/unbound/validator/val_kentry.h
+@@ -120,9 +120,11 @@
+ /**
+ * Copy a key entry, malloced.
+ * @param kkey: the key entry key (and data pointer) to copy.
++ * @param copy_reason: if the reason string needs to be copied (allocated).
+ * @return newly allocated entry or NULL on a failure to allocate memory.
+ */
+-struct key_entry_key* key_entry_copy(struct key_entry_key* kkey);
++struct key_entry_key* key_entry_copy(struct key_entry_key* kkey,
++ int copy_reason);
+
+ /**
+ * See if this is a null entry. Does not do locking.
+@@ -145,23 +147,6 @@
+ */
+ int key_entry_isbad(struct key_entry_key* kkey);
+
+-/**
+- * Set reason why a key is bad.
+- * @param kkey: bad key.
+- * @param reason: string to attach, you must allocate it.
+- * Not safe to call twice unless you deallocate it yourself.
+- */
+-void key_entry_set_reason(struct key_entry_key* kkey, char* reason);
+-
+-/**
+- * Set the EDE (RFC8914) code why the key is bad, if it
+- * exists (so not LDNS_EDE_NONE).
+- * @param kkey: bad key.
+- * @param ede: EDE code to attach to this key.
+- */
+-void key_entry_set_reason_bogus(struct key_entry_key* kkey, sldns_ede_code ede);
+-
+-
+ /**
+ * Get reason why a key is bad.
+ * @param kkey: bad key
+@@ -184,11 +169,14 @@
+ * @param namelen: length of name
+ * @param dclass: class of key entry. (host order);
+ * @param ttl: what ttl should the key have. relative.
++ * @param reason_bogus: accompanying EDE code.
++ * @param reason: accompanying NULL-terminated EDE string (or NULL).
+ * @param now: current time (added to ttl).
+ * @return new key entry or NULL on alloc failure
+ */
+ struct key_entry_key* key_entry_create_null(struct regional* region,
+- uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now);
+
+ /**
+@@ -199,12 +187,16 @@
+ * @param dclass: class of key entry. (host order);
+ * @param rrset: data for key entry. This is copied to the region.
+ * @param sigalg: signalled algorithm list (or NULL).
++ * @param reason_bogus: accompanying EDE code (usually LDNS_EDE_NONE).
++ * @param reason: accompanying NULL-terminated EDE string (or NULL).
+ * @param now: current time (added to ttl of rrset)
+ * @return new key entry or NULL on alloc failure
+ */
+ struct key_entry_key* key_entry_create_rrset(struct regional* region,
+- uint8_t* name, size_t namelen, uint16_t dclass,
+- struct ub_packed_rrset_key* rrset, uint8_t* sigalg, time_t now);
++ uint8_t* name, size_t namelen, uint16_t dclass,
++ struct ub_packed_rrset_key* rrset, uint8_t* sigalg,
++ sldns_ede_code reason_bogus, const char* reason,
++ time_t now);
+
+ /**
+ * Create a bad entry, in the given region.
+@@ -213,11 +205,14 @@
+ * @param namelen: length of name
+ * @param dclass: class of key entry. (host order);
+ * @param ttl: what ttl should the key have. relative.
++ * @param reason_bogus: accompanying EDE code.
++ * @param reason: accompanying NULL-terminated EDE string (or NULL).
+ * @param now: current time (added to ttl).
+ * @return new key entry or NULL on alloc failure
+ */
+ struct key_entry_key* key_entry_create_bad(struct regional* region,
+ uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl,
++ sldns_ede_code reason_bogus, const char* reason,
+ time_t now);
+
+ /**
+--- contrib/unbound/validator/val_neg.c.orig
++++ contrib/unbound/validator/val_neg.c
+@@ -43,7 +43,7 @@
+ */
+ #include "config.h"
+ #ifdef HAVE_OPENSSL_SSL_H
+-#include "openssl/ssl.h"
++#include <openssl/ssl.h>
+ #define NSEC3_SHA_LEN SHA_DIGEST_LENGTH
+ #else
+ #define NSEC3_SHA_LEN 20
+@@ -1407,6 +1407,11 @@
+ /* Matching NSEC, use to generate No Data answer. Not creating answers
+ * yet for No Data proven using wildcard. */
+ if(nsec && nsec_proves_nodata(nsec, qinfo, &nodata_wc) && !nodata_wc) {
++ /* do not create nodata answers for qtype ANY, it is a query
++ * type, not an rrtype to disprove. Nameerrors are useful for
++ * qtype ANY, in the else branch. */
++ if(qinfo->qtype == LDNS_RR_TYPE_ANY)
++ return NULL;
+ if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len,
+ qinfo->qtype, qinfo->qclass, region, 2)))
+ return NULL;
+--- contrib/unbound/validator/val_nsec.c.orig
++++ contrib/unbound/validator/val_nsec.c
+@@ -174,12 +174,14 @@
+
+ /** check security status from cache or verify rrset, returns true if secure */
+ static int
+-nsec_verify_rrset(struct module_env* env, struct val_env* ve,
+- struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey,
+- char** reason, struct module_qstate* qstate)
++nsec_verify_rrset(struct module_env* env, struct val_env* ve,
++ struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey,
++ char** reason, sldns_ede_code* reason_bogus,
++ struct module_qstate* qstate)
+ {
+ struct packed_rrset_data* d = (struct packed_rrset_data*)
+ nsec->entry.data;
++ int verified = 0;
+ if(!d) return 0;
+ if(d->security == sec_status_secure)
+ return 1;
+@@ -187,7 +189,7 @@
+ if(d->security == sec_status_secure)
+ return 1;
+ d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason,
+- NULL, LDNS_SECTION_AUTHORITY, qstate);
++ reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified);
+ if(d->security == sec_status_secure) {
+ rrset_update_sec_status(env->rrset_cache, nsec, *env->now);
+ return 1;
+@@ -199,7 +201,7 @@
+ val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve,
+ struct query_info* qinfo, struct reply_info* rep,
+ struct key_entry_key* kkey, time_t* proof_ttl, char** reason,
+- struct module_qstate* qstate)
++ sldns_ede_code* reason_bogus, struct module_qstate* qstate)
+ {
+ struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns(
+ rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC,
+@@ -216,7 +218,8 @@
+ * 1) this is a delegation point and there is no DS
+ * 2) this is not a delegation point */
+ if(nsec) {
+- if(!nsec_verify_rrset(env, ve, nsec, kkey, reason, qstate)) {
++ if(!nsec_verify_rrset(env, ve, nsec, kkey, reason,
++ reason_bogus, qstate)) {
+ verbose(VERB_ALGO, "NSEC RRset for the "
+ "referral did not verify.");
+ return sec_status_bogus;
+@@ -225,6 +228,7 @@
+ if(sec == sec_status_bogus) {
+ /* something was wrong. */
+ *reason = "NSEC does not prove absence of DS";
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ return sec;
+ } else if(sec == sec_status_insecure) {
+ /* this wasn't a delegation point. */
+@@ -246,9 +250,11 @@
+ if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC))
+ continue;
+ if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, reason,
+- qstate)) {
++ reason_bogus, qstate)) {
+ verbose(VERB_ALGO, "NSEC for empty non-terminal "
+ "did not verify.");
++ *reason = "NSEC for empty non-terminal "
++ "did not verify.";
+ return sec_status_bogus;
+ }
+ if(nsec_proves_nodata(rep->rrsets[i], qinfo, &wc)) {
+--- contrib/unbound/validator/val_nsec.h.orig
++++ contrib/unbound/validator/val_nsec.h
+@@ -44,6 +44,7 @@
+ #ifndef VALIDATOR_VAL_NSEC_H
+ #define VALIDATOR_VAL_NSEC_H
+ #include "util/data/packed_rrset.h"
++#include "sldns/rrdef.h"
+ struct val_env;
+ struct module_env;
+ struct module_qstate;
+@@ -65,6 +66,7 @@
+ * @param kkey: key entry to use for verification of signatures.
+ * @param proof_ttl: if secure, the TTL of how long this proof lasts.
+ * @param reason: string explaining why bogus.
++ * @param reason_bogus: relevant EDE code for validation failure.
+ * @param qstate: qstate with region.
+ * @return security status.
+ * SECURE: proved absence of DS.
+@@ -75,7 +77,8 @@
+ enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env,
+ struct val_env* ve, struct query_info* qinfo,
+ struct reply_info* rep, struct key_entry_key* kkey,
+- time_t* proof_ttl, char** reason, struct module_qstate* qstate);
++ time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus,
++ struct module_qstate* qstate);
+
+ /**
+ * nsec typemap check, takes an NSEC-type bitmap as argument, checks for type.
+--- contrib/unbound/validator/val_nsec3.c.orig
++++ contrib/unbound/validator/val_nsec3.c
+@@ -57,6 +57,19 @@
+ /* we include nsec.h for the bitmap_has_type function */
+ #include "validator/val_nsec.h"
+ #include "sldns/sbuffer.h"
++#include "util/config_file.h"
++
++/**
++ * Max number of NSEC3 calculations at once, suspend query for later.
++ * 8 is low enough and allows for cases where multiple proofs are needed.
++ */
++#define MAX_NSEC3_CALCULATIONS 8
++/**
++ * When all allowed NSEC3 calculations at once resulted in error treat as
++ * bogus. NSEC3 hash errors are not cached and this helps breaks loops with
++ * erroneous data.
++ */
++#define MAX_NSEC3_ERRORS -1
+
+ /**
+ * This function we get from ldns-compat or from base system
+@@ -532,6 +545,17 @@
+ return memcmp(s1, s2, s1len);
+ }
+
++int
++nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region)
++{
++ if(ct->ct) return 1;
++ ct->ct = (rbtree_type*)regional_alloc(region, sizeof(*ct->ct));
++ if(!ct->ct) return 0;
++ ct->region = region;
++ rbtree_init(ct->ct, &nsec3_hash_cmp);
++ return 1;
++}
++
+ size_t
+ nsec3_get_hashed(sldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo,
+ size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max)
+@@ -646,7 +670,7 @@
+ c = (struct nsec3_cached_hash*)rbtree_search(table, &looki);
+ if(c) {
+ *hash = c;
+- return 1;
++ return 2;
+ }
+ /* create a new entry */
+ c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c));
+@@ -658,10 +682,10 @@
+ c->dname_len = dname_len;
+ r = nsec3_calc_hash(region, buf, c);
+ if(r != 1)
+- return r;
++ return r; /* returns -1 or 0 */
+ r = nsec3_calc_b32(region, buf, c);
+ if(r != 1)
+- return r;
++ return r; /* returns 0 */
+ #ifdef UNBOUND_DEBUG
+ n =
+ #else
+@@ -704,6 +728,7 @@
+ struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* s)
+ {
+ uint8_t* nm = s->rk.dname;
++ if(!hash) return 0; /* please clang */
+ /* compare, does hash of name based on params in this NSEC3
+ * match the owner name of this NSEC3?
+ * name must be: <hashlength>base32 . zone name
+@@ -730,34 +755,50 @@
+ * @param nmlen: length of name.
+ * @param rrset: nsec3 that matches is returned here.
+ * @param rr: rr number in nsec3 rrset that matches.
++ * @param calculations: current hash calculations.
+ * @return true if a matching NSEC3 is found, false if not.
+ */
+ static int
+ find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, uint8_t* nm, size_t nmlen,
+- struct ub_packed_rrset_key** rrset, int* rr)
++ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen,
++ struct ub_packed_rrset_key** rrset, int* rr,
++ int* calculations)
+ {
+ size_t i_rs;
+ int i_rr;
+ struct ub_packed_rrset_key* s;
+ struct nsec3_cached_hash* hash = NULL;
+ int r;
++ int calc_errors = 0;
+
+ /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
+ for(s=filter_first(flt, &i_rs, &i_rr); s;
+ s=filter_next(flt, &i_rs, &i_rr)) {
++ /* check if we are allowed more calculations */
++ if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ if(calc_errors == *calculations) {
++ *calculations = MAX_NSEC3_ERRORS;
++ }
++ break;
++ }
+ /* get name hashed for this NSEC3 RR */
+- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
++ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer,
+ s, i_rr, nm, nmlen, &hash);
+ if(r == 0) {
+ log_err("nsec3: malloc failure");
+ break; /* alloc failure */
+- } else if(r != 1)
+- continue; /* malformed NSEC3 */
+- else if(nsec3_hash_matches_owner(flt, hash, s)) {
+- *rrset = s; /* rrset with this name */
+- *rr = i_rr; /* matches hash with these parameters */
+- return 1;
++ } else if(r < 0) {
++ /* malformed NSEC3 */
++ calc_errors++;
++ (*calculations)++;
++ continue;
++ } else {
++ if(r == 1) (*calculations)++;
++ if(nsec3_hash_matches_owner(flt, hash, s)) {
++ *rrset = s; /* rrset with this name */
++ *rr = i_rr; /* matches hash with these parameters */
++ return 1;
++ }
+ }
+ }
+ *rrset = NULL;
+@@ -775,6 +816,7 @@
+ if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen))
+ return 0; /* malformed RR proves nothing */
+
++ if(!hash) return 0; /* please clang */
+ /* check the owner name is a hashed value . apex
+ * base32 encoded values must have equal length.
+ * hash_value and next hash value must have equal length. */
+@@ -823,35 +865,51 @@
+ * @param nmlen: length of name.
+ * @param rrset: covering NSEC3 rrset is returned here.
+ * @param rr: rr of cover is returned here.
++ * @param calculations: current hash calculations.
+ * @return true if a covering NSEC3 is found, false if not.
+ */
+ static int
+ find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, uint8_t* nm, size_t nmlen,
+- struct ub_packed_rrset_key** rrset, int* rr)
++ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen,
++ struct ub_packed_rrset_key** rrset, int* rr,
++ int* calculations)
+ {
+ size_t i_rs;
+ int i_rr;
+ struct ub_packed_rrset_key* s;
+ struct nsec3_cached_hash* hash = NULL;
+ int r;
++ int calc_errors = 0;
+
+ /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
+ for(s=filter_first(flt, &i_rs, &i_rr); s;
+ s=filter_next(flt, &i_rs, &i_rr)) {
++ /* check if we are allowed more calculations */
++ if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ if(calc_errors == *calculations) {
++ *calculations = MAX_NSEC3_ERRORS;
++ }
++ break;
++ }
+ /* get name hashed for this NSEC3 RR */
+- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
++ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer,
+ s, i_rr, nm, nmlen, &hash);
+ if(r == 0) {
+ log_err("nsec3: malloc failure");
+ break; /* alloc failure */
+- } else if(r != 1)
+- continue; /* malformed NSEC3 */
+- else if(nsec3_covers(flt->zone, hash, s, i_rr,
+- env->scratch_buffer)) {
+- *rrset = s; /* rrset with this name */
+- *rr = i_rr; /* covers hash with these parameters */
+- return 1;
++ } else if(r < 0) {
++ /* malformed NSEC3 */
++ calc_errors++;
++ (*calculations)++;
++ continue;
++ } else {
++ if(r == 1) (*calculations)++;
++ if(nsec3_covers(flt->zone, hash, s, i_rr,
++ env->scratch_buffer)) {
++ *rrset = s; /* rrset with this name */
++ *rr = i_rr; /* covers hash with these parameters */
++ return 1;
++ }
+ }
+ }
+ *rrset = NULL;
+@@ -869,11 +927,13 @@
+ * @param ct: cached hashes table.
+ * @param qinfo: query that is verified for.
+ * @param ce: closest encloser information is returned in here.
++ * @param calculations: current hash calculations.
+ * @return true if a closest encloser candidate is found, false if not.
+ */
+ static int
+-nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo, struct ce_response* ce)
++nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo,
++ struct ce_response* ce, int* calculations)
+ {
+ uint8_t* nm = qinfo->qname;
+ size_t nmlen = qinfo->qname_len;
+@@ -888,8 +948,12 @@
+ * may be the case. */
+
+ while(dname_subdomain_c(nm, flt->zone)) {
++ if(*calculations >= MAX_NSEC3_CALCULATIONS ||
++ *calculations == MAX_NSEC3_ERRORS) {
++ return 0;
++ }
+ if(find_matching_nsec3(env, flt, ct, nm, nmlen,
+- &ce->ce_rrset, &ce->ce_rr)) {
++ &ce->ce_rrset, &ce->ce_rr, calculations)) {
+ ce->ce = nm;
+ ce->ce_len = nmlen;
+ return 1;
+@@ -933,22 +997,38 @@
+ * If set true, and the return value is true, then you can be
+ * certain that the ce.nc_rrset and ce.nc_rr are set properly.
+ * @param ce: closest encloser information is returned in here.
++ * @param calculations: pointer to the current NSEC3 hash calculations.
+ * @return bogus if no closest encloser could be proven.
+ * secure if a closest encloser could be proven, ce is set.
+ * insecure if the closest-encloser candidate turns out to prove
+ * that an insecure delegation exists above the qname.
++ * unchecked if no more hash calculations are allowed at this point.
+ */
+ static enum sec_status
+-nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo, int prove_does_not_exist,
+- struct ce_response* ce)
++nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo,
++ int prove_does_not_exist, struct ce_response* ce, int* calculations)
+ {
+ uint8_t* nc;
+ size_t nc_len;
+ /* robust: clean out ce, in case it gets abused later */
+ memset(ce, 0, sizeof(*ce));
+
+- if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) {
++ if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce, calculations)) {
++ if(*calculations == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
++ "not find a candidate for the closest "
++ "encloser; all attempted hash calculations "
++ "were erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
++ "not find a candidate for the closest "
++ "encloser; reached MAX_NSEC3_CALCULATIONS "
++ "(%d); unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
+ "not find a candidate for the closest encloser.");
+ return sec_status_bogus;
+@@ -989,9 +1069,23 @@
+ /* Otherwise, we need to show that the next closer name is covered. */
+ next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len);
+ if(!find_covering_nsec3(env, flt, ct, nc, nc_len,
+- &ce->nc_rrset, &ce->nc_rr)) {
++ &ce->nc_rrset, &ce->nc_rr, calculations)) {
++ if(*calculations == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
++ "candidate encloser was the closest encloser; "
++ "all attempted hash calculations were "
++ "erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
++ "candidate encloser was the closest encloser; "
++ "reached MAX_NSEC3_CALCULATIONS (%d); "
++ "unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
+- "candidate encloser was the closest encloser");
++ "candidate encloser was the closest encloser");
+ return sec_status_bogus;
+ }
+ return sec_status_secure;
+@@ -1019,8 +1113,8 @@
+
+ /** Do the name error proof */
+ static enum sec_status
+-nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo)
++nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo, int* calc)
+ {
+ struct ce_response ce;
+ uint8_t* wc;
+@@ -1032,11 +1126,15 @@
+ /* First locate and prove the closest encloser to qname. We will
+ * use the variant that fails if the closest encloser turns out
+ * to be qname. */
+- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce);
++ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc);
+ if(sec != sec_status_secure) {
+ if(sec == sec_status_bogus)
+ verbose(VERB_ALGO, "nsec3 nameerror proof: failed "
+ "to prove a closest encloser");
++ else if(sec == sec_status_unchecked)
++ verbose(VERB_ALGO, "nsec3 nameerror proof: will "
++ "continue proving closest encloser after "
++ "suspend");
+ else verbose(VERB_ALGO, "nsec3 nameerror proof: closest "
+ "nsec3 is an insecure delegation");
+ return sec;
+@@ -1046,9 +1144,27 @@
+ /* At this point, we know that qname does not exist. Now we need
+ * to prove that the wildcard does not exist. */
+ log_assert(ce.ce);
+- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
+- if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen,
+- &wc_rrset, &wc_rr)) {
++ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen);
++ if(!wc) {
++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
++ "that the applicable wildcard did not exist.");
++ return sec_status_bogus;
++ }
++ if(!find_covering_nsec3(env, flt, ct, wc, wclen, &wc_rrset, &wc_rr, calc)) {
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
++ "that the applicable wildcard did not exist; "
++ "all attempted hash calculations were "
++ "erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
++ "that the applicable wildcard did not exist; "
++ "reached MAX_NSEC3_CALCULATIONS (%d); "
++ "unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
+ "that the applicable wildcard did not exist.");
+ return sec_status_bogus;
+@@ -1064,14 +1180,13 @@
+ enum sec_status
+ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey)
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+@@ -1079,7 +1194,7 @@
+ return sec_status_insecure; /* iteration count too high */
+ log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone",
+ flt.zone, 0, 0);
+- return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
++ return nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc);
+ }
+
+ /*
+@@ -1089,8 +1204,9 @@
+
+ /** Do the nodata proof */
+ static enum sec_status
+-nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt,
+- rbtree_type* ct, struct query_info* qinfo)
++nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt,
++ struct nsec3_cache_table* ct, struct query_info* qinfo,
++ int* calc)
+ {
+ struct ce_response ce;
+ uint8_t* wc;
+@@ -1100,7 +1216,7 @@
+ enum sec_status sec;
+
+ if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len,
+- &rrset, &rr)) {
++ &rrset, &rr, calc)) {
+ /* cases 1 and 2 */
+ if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
+ verbose(VERB_ALGO, "proveNodata: Matching NSEC3 "
+@@ -1144,11 +1260,23 @@
+ }
+ return sec_status_secure;
+ }
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "proveNodata: all attempted hash "
++ "calculations were erroneous while finding a matching "
++ "NSEC3, bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "proveNodata: reached "
++ "MAX_NSEC3_CALCULATIONS (%d) while finding a "
++ "matching NSEC3; unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+
+ /* For cases 3 - 5, we need the proven closest encloser, and it
+ * can't match qname. Although, at this point, we know that it
+ * won't since we just checked that. */
+- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce);
++ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc);
+ if(sec == sec_status_bogus) {
+ verbose(VERB_ALGO, "proveNodata: did not match qname, "
+ "nor found a proven closest encloser.");
+@@ -1157,14 +1285,17 @@
+ verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure "
+ "delegation.");
+ return sec_status_insecure;
++ } else if(sec==sec_status_unchecked) {
++ return sec_status_unchecked;
+ }
+
+ /* Case 3: removed */
+
+ /* Case 4: */
+ log_assert(ce.ce);
+- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
+- if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) {
++ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen);
++ if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr,
++ calc)) {
+ /* found wildcard */
+ if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
+ verbose(VERB_ALGO, "nsec3 nodata proof: matching "
+@@ -1195,6 +1326,18 @@
+ }
+ return sec_status_secure;
+ }
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 nodata proof: all attempted hash "
++ "calculations were erroneous while matching "
++ "wildcard, bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 nodata proof: reached "
++ "MAX_NSEC3_CALCULATIONS (%d) while matching "
++ "wildcard, unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+
+ /* Case 5: */
+ /* Due to forwarders, cnames, and other collating effects, we
+@@ -1223,28 +1366,27 @@
+ enum sec_status
+ nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey)
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+ if(nsec3_iteration_count_high(ve, &flt, kkey))
+ return sec_status_insecure; /* iteration count too high */
+- return nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
++ return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
+ }
+
+ enum sec_status
+ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc)
++ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+ struct ce_response ce;
+ uint8_t* nc;
+@@ -1254,7 +1396,6 @@
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+@@ -1272,8 +1413,22 @@
+ /* Now we still need to prove that the original data did not exist.
+ * Otherwise, we need to show that the next closer name is covered. */
+ next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len);
+- if(!find_covering_nsec3(env, &flt, &ct, nc, nc_len,
+- &ce.nc_rrset, &ce.nc_rr)) {
++ if(!find_covering_nsec3(env, &flt, ct, nc, nc_len,
++ &ce.nc_rrset, &ce.nc_rr, calc)) {
++ if(*calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "proveWildcard: did not find a "
++ "covering NSEC3 that covered the next closer "
++ "name; all attempted hash calculations were "
++ "erroneous; bogus");
++ return sec_status_bogus;
++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "proveWildcard: did not find a "
++ "covering NSEC3 that covered the next closer "
++ "name; reached MAX_NSEC3_CALCULATIONS "
++ "(%d); unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+ verbose(VERB_ALGO, "proveWildcard: did not find a covering "
+ "NSEC3 that covered the next closer name.");
+ return sec_status_bogus;
+@@ -1294,6 +1449,7 @@
+ {
+ struct packed_rrset_data* d;
+ size_t i;
++ int verified = 0;
+ for(i=0; i<num; i++) {
+ d = (struct packed_rrset_data*)list[i]->entry.data;
+ if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3))
+@@ -1304,7 +1460,8 @@
+ if(d->security == sec_status_secure)
+ continue;
+ d->security = val_verify_rrset_entry(env, ve, list[i], kkey,
+- reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate);
++ reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate,
++ &verified);
+ if(d->security != sec_status_secure) {
+ verbose(VERB_ALGO, "NSEC3 did not verify");
+ return 0;
+@@ -1318,13 +1475,16 @@
+ nsec3_prove_nods(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+ struct query_info* qinfo, struct key_entry_key* kkey, char** reason,
+- sldns_ede_code* reason_bogus, struct module_qstate* qstate)
++ sldns_ede_code* reason_bogus, struct module_qstate* qstate,
++ struct nsec3_cache_table* ct)
+ {
+- rbtree_type ct;
+ struct nsec3_filter flt;
+ struct ce_response ce;
+ struct ub_packed_rrset_key* rrset;
+ int rr;
++ int calc = 0;
++ enum sec_status sec;
++
+ log_assert(qinfo->qtype == LDNS_RR_TYPE_DS);
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) {
+@@ -1335,7 +1495,6 @@
+ *reason = "not all NSEC3 records secure";
+ return sec_status_bogus; /* not all NSEC3 records secure */
+ }
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone) {
+ *reason = "no NSEC3 records";
+@@ -1346,8 +1505,8 @@
+
+ /* Look for a matching NSEC3 to qname -- this is the normal
+ * NODATA case. */
+- if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len,
+- &rrset, &rr)) {
++ if(find_matching_nsec3(env, &flt, ct, qinfo->qname, qinfo->qname_len,
++ &rrset, &rr, &calc)) {
+ /* If the matching NSEC3 has the SOA bit set, it is from
+ * the wrong zone (the child instead of the parent). If
+ * it has the DS bit set, then we were lied to. */
+@@ -1370,10 +1529,24 @@
+ /* Otherwise, this proves no DS. */
+ return sec_status_secure;
+ }
++ if(calc == MAX_NSEC3_ERRORS) {
++ verbose(VERB_ALGO, "nsec3 provenods: all attempted hash "
++ "calculations were erroneous while finding a matching "
++ "NSEC3, bogus");
++ return sec_status_bogus;
++ } else if(calc >= MAX_NSEC3_CALCULATIONS) {
++ verbose(VERB_ALGO, "nsec3 provenods: reached "
++ "MAX_NSEC3_CALCULATIONS (%d) while finding a "
++ "matching NSEC3, unchecked still",
++ MAX_NSEC3_CALCULATIONS);
++ return sec_status_unchecked;
++ }
+
+ /* Otherwise, we are probably in the opt-out case. */
+- if(nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce)
+- != sec_status_secure) {
++ sec = nsec3_prove_closest_encloser(env, &flt, ct, qinfo, 1, &ce, &calc);
++ if(sec == sec_status_unchecked) {
++ return sec_status_unchecked;
++ } else if(sec != sec_status_secure) {
+ /* an insecure delegation *above* the qname does not prove
+ * anything about this qname exactly, and bogus is bogus */
+ verbose(VERB_ALGO, "nsec3 provenods: did not match qname, "
+@@ -1407,17 +1580,16 @@
+
+ enum sec_status
+ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
+- struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata)
++ struct ub_packed_rrset_key** list, size_t num,
++ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata,
++ struct nsec3_cache_table* ct, int* calc)
+ {
+ enum sec_status sec, secnx;
+- rbtree_type ct;
+ struct nsec3_filter flt;
+ *nodata = 0;
+
+ if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
+ return sec_status_bogus; /* no valid NSEC3s, bogus */
+- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
+ filter_init(&flt, list, num, qinfo); /* init RR iterator */
+ if(!flt.zone)
+ return sec_status_bogus; /* no RRs */
+@@ -1427,16 +1599,20 @@
+ /* try nxdomain and nodata after another, while keeping the
+ * hash cache intact */
+
+- secnx = nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
++ secnx = nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc);
+ if(secnx==sec_status_secure)
+ return sec_status_secure;
+- sec = nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
++ else if(secnx == sec_status_unchecked)
++ return sec_status_unchecked;
++ sec = nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
+ if(sec==sec_status_secure) {
+ *nodata = 1;
+ } else if(sec == sec_status_insecure) {
+ *nodata = 1;
+ } else if(secnx == sec_status_insecure) {
+ sec = sec_status_insecure;
++ } else if(sec == sec_status_unchecked) {
++ return sec_status_unchecked;
+ }
+ return sec;
+ }
+--- contrib/unbound/validator/val_nsec3.h.orig
++++ contrib/unbound/validator/val_nsec3.h
+@@ -98,6 +98,15 @@
+ /** The SHA1 hash algorithm for NSEC3 */
+ #define NSEC3_HASH_SHA1 0x01
+
++/**
++* Cache table for NSEC3 hashes.
++* It keeps a *pointer* to the region its items are allocated.
++*/
++struct nsec3_cache_table {
++ rbtree_type* ct;
++ struct regional* region;
++};
++
+ /**
+ * Determine if the set of NSEC3 records provided with a response prove NAME
+ * ERROR. This means that the NSEC3s prove a) the closest encloser exists,
+@@ -110,14 +119,18 @@
+ * @param num: number of RRsets in the array to examine.
+ * @param qinfo: query that is verified for.
+ * @param kkey: key entry that signed the NSEC3s.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the Name Error is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey);
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA
+@@ -144,15 +157,18 @@
+ * @param num: number of RRsets in the array to examine.
+ * @param qinfo: query that is verified for.
+ * @param kkey: key entry that signed the NSEC3s.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey);
+-
++ struct query_info* qinfo, struct key_entry_key* kkey,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * Prove that a positive wildcard match was appropriate (no direct match
+@@ -166,14 +182,18 @@
+ * @param kkey: key entry that signed the NSEC3s.
+ * @param wc: The purported wildcard that matched. This is the wildcard name
+ * as *.wildcard.name., with the *. label already removed.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc);
++ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * Prove that a DS response either had no DS, or wasn't a delegation point.
+@@ -189,17 +209,20 @@
+ * @param reason: string for bogus result.
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param qstate: qstate with region.
++ * @param ct: cached hashes table.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
+ * or if there was no DS in an insecure (i.e., opt-in) way,
+- * INDETERMINATE if it was clear that this wasn't a delegation point.
++ * INDETERMINATE if it was clear that this wasn't a delegation point,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nods(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+ struct query_info* qinfo, struct key_entry_key* kkey, char** reason,
+- sldns_ede_code* reason_bogus, struct module_qstate* qstate);
++ sldns_ede_code* reason_bogus, struct module_qstate* qstate,
++ struct nsec3_cache_table* ct);
+
+ /**
+ * Prove NXDOMAIN or NODATA.
+@@ -212,14 +235,18 @@
+ * @param kkey: key entry that signed the NSEC3s.
+ * @param nodata: if return value is secure, this indicates if nodata or
+ * nxdomain was proven.
++ * @param ct: cached hashes table.
++ * @param calc: current hash calculations.
+ * @return:
+ * sec_status SECURE of the proposition is proven by the NSEC3 RRs,
+- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
++ * UNCHECKED if no more hash calculations are allowed at this point.
+ */
+ enum sec_status
+ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key** list, size_t num,
+- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata);
++ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata,
++ struct nsec3_cache_table* ct, int* calc);
+
+ /**
+ * The NSEC3 hash result storage.
+@@ -256,6 +283,14 @@
+ */
+ int nsec3_hash_cmp(const void* c1, const void* c2);
+
++/**
++ * Initialise the NSEC3 cache table.
++ * @param ct: the nsec3 cache table.
++ * @param region: the region where allocations for the table will happen.
++ * @return true on success, false on malloc error.
++ */
++int nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region);
++
+ /**
+ * Obtain the hash of an owner name.
+ * Used internally by the nsec3 proof functions in this file.
+@@ -272,7 +307,8 @@
+ * @param dname_len: the length of the name.
+ * @param hash: the hash node is returned on success.
+ * @return:
+- * 1 on success, either from cache or newly hashed hash is returned.
++ * 2 on success, hash from cache is returned.
++ * 1 on success, newly computed hash is returned.
+ * 0 on a malloc failure.
+ * -1 if the NSEC3 rr was badly formatted (i.e. formerr).
+ */
+--- contrib/unbound/validator/val_sigcrypt.c.orig
++++ contrib/unbound/validator/val_sigcrypt.c
+@@ -48,6 +48,7 @@
+ #include "util/data/msgparse.h"
+ #include "util/data/dname.h"
+ #include "util/rbtree.h"
++#include "util/rfc_1982.h"
+ #include "util/module.h"
+ #include "util/net_help.h"
+ #include "util/regional.h"
+@@ -78,6 +79,9 @@
+ #include <openssl/engine.h>
+ #endif
+
++/** Maximum number of RRSIG validations for an RRset. */
++#define MAX_VALIDATE_RRSIGS 8
++
+ /** return number of rrs in an rrset */
+ static size_t
+ rrset_get_count(struct ub_packed_rrset_key* rrset)
+@@ -541,6 +545,8 @@
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param section: section of packet where this rrset comes from.
+ * @param qstate: qstate with region.
++ * @param numverified: incremented when the number of RRSIG validations
++ * increases.
+ * @return secure if any key signs *this* signature. bogus if no key signs it,
+ * unchecked on error, or indeterminate if all keys are not supported by
+ * the crypto library (openssl3+ only).
+@@ -551,7 +557,8 @@
+ struct ub_packed_rrset_key* dnskey, size_t sig_idx,
+ struct rbtree_type** sortree,
+ char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int* numverified)
+ {
+ /* find matching keys and check them */
+ enum sec_status sec = sec_status_bogus;
+@@ -575,6 +582,7 @@
+ tag != dnskey_calc_keytag(dnskey, i))
+ continue;
+ numchecked ++;
++ (*numverified)++;
+
+ /* see if key verifies */
+ sec = dnskey_verify_rrset_sig(env->scratch,
+@@ -585,6 +593,13 @@
+ return sec;
+ else if(sec == sec_status_indeterminate)
+ numindeterminate ++;
++ if(*numverified > MAX_VALIDATE_RRSIGS) {
++ *reason = "too many RRSIG validations";
++ if(reason_bogus)
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ verbose(VERB_ALGO, "verify sig: too many RRSIG validations");
++ return sec_status_bogus;
++ }
+ }
+ if(numchecked == 0) {
+ *reason = "signatures from unknown keys";
+@@ -608,7 +623,7 @@
+ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey,
+ uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate, int* verified)
+ {
+ enum sec_status sec;
+ size_t i, num;
+@@ -616,6 +631,7 @@
+ /* make sure that for all DNSKEY algorithms there are valid sigs */
+ struct algo_needs needs;
+ int alg;
++ *verified = 0;
+
+ num = rrset_get_sigcount(rrset);
+ if(num == 0) {
+@@ -640,7 +656,7 @@
+ for(i=0; i<num; i++) {
+ sec = dnskeyset_verify_rrset_sig(env, ve, *env->now, rrset,
+ dnskey, i, &sortree, reason, reason_bogus,
+- section, qstate);
++ section, qstate, verified);
+ /* see which algorithm has been fixed up */
+ if(sec == sec_status_secure) {
+ if(!sigalg)
+@@ -652,6 +668,13 @@
+ algo_needs_set_bogus(&needs,
+ (uint8_t)rrset_get_sig_algo(rrset, i));
+ }
++ if(*verified > MAX_VALIDATE_RRSIGS) {
++ verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations");
++ *reason = "too many RRSIG validations";
++ if(reason_bogus)
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ return sec_status_bogus;
++ }
+ }
+ if(sigalg && (alg=algo_needs_missing(&needs)) != 0) {
+ verbose(VERB_ALGO, "rrset failed to verify: "
+@@ -690,6 +713,7 @@
+ int buf_canon = 0;
+ uint16_t tag = dnskey_calc_keytag(dnskey, dnskey_idx);
+ int algo = dnskey_get_algo(dnskey, dnskey_idx);
++ int numverified = 0;
+
+ num = rrset_get_sigcount(rrset);
+ if(num == 0) {
+@@ -713,14 +737,22 @@
+ if(sec == sec_status_secure)
+ return sec;
+ numchecked ++;
++ numverified ++;
+ if(sec == sec_status_indeterminate)
+ numindeterminate ++;
++ if(numverified > MAX_VALIDATE_RRSIGS) {
++ verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations");
++ *reason = "too many RRSIG validations";
++ if(reason_bogus)
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ return sec_status_bogus;
++ }
+ }
+ verbose(VERB_ALGO, "rrset failed to verify: all signatures are bogus");
+ if(!numchecked) {
+- *reason = "signature missing";
++ *reason = "signature for expected key and algorithm missing";
+ if(reason_bogus)
+- *reason_bogus = LDNS_EDE_RRSIGS_MISSING;
++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ } else if(numchecked == numindeterminate) {
+ verbose(VERB_ALGO, "rrset failed to verify due to algorithm "
+ "refusal by cryptolib");
+@@ -1378,44 +1410,6 @@
+ (unsigned)incep, (unsigned)now);
+ }
+
+-/** RFC 1982 comparison, uses unsigned integers, and tries to avoid
+- * compiler optimization (eg. by avoiding a-b<0 comparisons),
+- * this routine matches compare_serial(), for SOA serial number checks */
+-static int
+-compare_1982(uint32_t a, uint32_t b)
+-{
+- /* for 32 bit values */
+- const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
+-
+- if (a == b) {
+- return 0;
+- } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
+- return -1;
+- } else {
+- return 1;
+- }
+-}
+-
+-/** if we know that b is larger than a, return the difference between them,
+- * that is the distance between them. in RFC1982 arith */
+-static uint32_t
+-subtract_1982(uint32_t a, uint32_t b)
+-{
+- /* for 32 bit values */
+- const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
+-
+- if(a == b)
+- return 0;
+- if(a < b && b - a < cutoff) {
+- return b-a;
+- }
+- if(a > b && a - b > cutoff) {
+- return ((uint32_t)0xffffffff) - (a-b-1);
+- }
+- /* wrong case, b smaller than a */
+- return 0;
+-}
+-
+ /** check rrsig dates */
+ static int
+ check_dates(struct val_env* ve, uint32_t unow, uint8_t* expi_p,
+--- contrib/unbound/validator/val_sigcrypt.h.orig
++++ contrib/unbound/validator/val_sigcrypt.h
+@@ -260,6 +260,7 @@
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param section: section of packet where this rrset comes from.
+ * @param qstate: qstate with region.
++ * @param verified: if not NULL the number of RRSIG validations is returned.
+ * @return SECURE if one key in the set verifies one rrsig.
+ * UNCHECKED on allocation errors, unsupported algorithms, malformed data,
+ * and BOGUS on verification failures (no keys match any signatures).
+@@ -268,7 +269,7 @@
+ struct val_env* ve, struct ub_packed_rrset_key* rrset,
+ struct ub_packed_rrset_key* dnskey, uint8_t* sigalg,
+ char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate);
++ sldns_pkt_section section, struct module_qstate* qstate, int* verified);
+
+
+ /**
+--- contrib/unbound/validator/val_utils.c.orig
++++ contrib/unbound/validator/val_utils.c
+@@ -58,6 +58,10 @@
+ #include "sldns/wire2str.h"
+ #include "sldns/parseutil.h"
+
++/** Maximum allowed digest match failures per DS, for DNSKEYs with the same
++ * properties */
++#define MAX_DS_MATCH_FAILURES 4
++
+ enum val_classification
+ val_classify_response(uint16_t query_flags, struct query_info* origqinf,
+ struct query_info* qinf, struct reply_info* rep, size_t skip)
+@@ -336,7 +340,8 @@
+ val_verify_rrset(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys,
+ uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int *verified)
+ {
+ enum sec_status sec;
+ struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
+@@ -346,6 +351,7 @@
+ log_nametypeclass(VERB_ALGO, "verify rrset cached",
+ rrset->rk.dname, ntohs(rrset->rk.type),
+ ntohs(rrset->rk.rrset_class));
++ *verified = 0;
+ return d->security;
+ }
+ /* check in the cache if verification has already been done */
+@@ -354,12 +360,13 @@
+ log_nametypeclass(VERB_ALGO, "verify rrset from cache",
+ rrset->rk.dname, ntohs(rrset->rk.type),
+ ntohs(rrset->rk.rrset_class));
++ *verified = 0;
+ return d->security;
+ }
+ log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname,
+ ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class));
+ sec = dnskeyset_verify_rrset(env, ve, rrset, keys, sigalg, reason,
+- reason_bogus, section, qstate);
++ reason_bogus, section, qstate, verified);
+ verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec));
+ regional_free_all(env->scratch);
+
+@@ -393,7 +400,8 @@
+ val_verify_rrset_entry(struct module_env* env, struct val_env* ve,
+ struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey,
+ char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate)
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int* verified)
+ {
+ /* temporary dnskey rrset-key */
+ struct ub_packed_rrset_key dnskey;
+@@ -407,7 +415,7 @@
+ dnskey.entry.key = &dnskey;
+ dnskey.entry.data = kd->rrset_data;
+ sec = val_verify_rrset(env, ve, rrset, &dnskey, kd->algo, reason,
+- reason_bogus, section, qstate);
++ reason_bogus, section, qstate, verified);
+ return sec;
+ }
+
+@@ -439,6 +447,12 @@
+ if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset,
+ ds_idx)) {
+ verbose(VERB_ALGO, "DS match attempt failed");
++ if(numchecked > numhashok + MAX_DS_MATCH_FAILURES) {
++ verbose(VERB_ALGO, "DS match attempt reached "
++ "MAX_DS_MATCH_FAILURES (%d); bogus",
++ MAX_DS_MATCH_FAILURES);
++ return sec_status_bogus;
++ }
+ continue;
+ }
+ numhashok++;
+@@ -587,16 +601,18 @@
+ return key_entry_create_rrset(region,
+ ds_rrset->rk.dname, ds_rrset->rk.dname_len,
+ ntohs(ds_rrset->rk.rrset_class), dnskey_rrset,
+- downprot?sigalg:NULL, *env->now);
++ downprot?sigalg:NULL, LDNS_EDE_NONE, NULL,
++ *env->now);
+ } else if(sec == sec_status_insecure) {
+ return key_entry_create_null(region, ds_rrset->rk.dname,
+- ds_rrset->rk.dname_len,
++ ds_rrset->rk.dname_len,
+ ntohs(ds_rrset->rk.rrset_class),
+- rrset_get_ttl(ds_rrset), *env->now);
++ rrset_get_ttl(ds_rrset), *reason_bogus, *reason,
++ *env->now);
+ }
+ return key_entry_create_bad(region, ds_rrset->rk.dname,
+ ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class),
+- BOGUS_KEY_TTL, *env->now);
++ BOGUS_KEY_TTL, *reason_bogus, *reason, *env->now);
+ }
+
+ enum sec_status
+@@ -694,7 +710,7 @@
+ has_useful_ta = 1;
+
+ sec = dnskey_verify_rrset(env, ve, dnskey_rrset,
+- ta_dnskey, i, reason, NULL, LDNS_SECTION_ANSWER, qstate);
++ ta_dnskey, i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate);
+ if(sec == sec_status_secure) {
+ if(!sigalg || algo_needs_set_secure(&needs,
+ (uint8_t)dnskey_get_algo(ta_dnskey, i))) {
+@@ -743,16 +759,17 @@
+ return key_entry_create_rrset(region,
+ dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len,
+ ntohs(dnskey_rrset->rk.rrset_class), dnskey_rrset,
+- downprot?sigalg:NULL, *env->now);
++ downprot?sigalg:NULL, LDNS_EDE_NONE, NULL, *env->now);
+ } else if(sec == sec_status_insecure) {
+ return key_entry_create_null(region, dnskey_rrset->rk.dname,
+ dnskey_rrset->rk.dname_len,
+ ntohs(dnskey_rrset->rk.rrset_class),
+- rrset_get_ttl(dnskey_rrset), *env->now);
++ rrset_get_ttl(dnskey_rrset), *reason_bogus, *reason,
++ *env->now);
+ }
+ return key_entry_create_bad(region, dnskey_rrset->rk.dname,
+ dnskey_rrset->rk.dname_len, ntohs(dnskey_rrset->rk.rrset_class),
+- BOGUS_KEY_TTL, *env->now);
++ BOGUS_KEY_TTL, *reason_bogus, *reason, *env->now);
+ }
+
+ int
+--- contrib/unbound/validator/val_utils.h.orig
++++ contrib/unbound/validator/val_utils.h
+@@ -124,12 +124,14 @@
+ * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
+ * @param section: section of packet where this rrset comes from.
+ * @param qstate: qstate with region.
++ * @param verified: if not NULL, the number of RRSIG validations is returned.
+ * @return security status of verification.
+ */
+ enum sec_status val_verify_rrset_entry(struct module_env* env,
+ struct val_env* ve, struct ub_packed_rrset_key* rrset,
+ struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus,
+- sldns_pkt_section section, struct module_qstate* qstate);
++ sldns_pkt_section section, struct module_qstate* qstate,
++ int* verified);
+
+ /**
+ * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but
+--- contrib/unbound/validator/validator.c.orig
++++ contrib/unbound/validator/validator.c
+@@ -64,22 +64,27 @@
+ #include "sldns/wire2str.h"
+ #include "sldns/str2wire.h"
+
++/** Max number of RRSIGs to validate at once, suspend query for later. */
++#define MAX_VALIDATE_AT_ONCE 8
++/** Max number of validation suspends allowed, error out otherwise. */
++#define MAX_VALIDATION_SUSPENDS 16
++
+ /* forward decl for cache response and normal super inform calls of a DS */
+ static void process_ds_response(struct module_qstate* qstate,
+ struct val_qstate* vq, int id, int rcode, struct dns_msg* msg,
+- struct query_info* qinfo, struct sock_list* origin);
++ struct query_info* qinfo, struct sock_list* origin, int* suspend);
+
+
+-/* Updates the suplied EDE (RFC8914) code selectively so we don't loose
+- * a more specific code
+- */
++/* Updates the suplied EDE (RFC8914) code selectively so we don't lose
++ * a more specific code */
+ static void
+ update_reason_bogus(struct reply_info* rep, sldns_ede_code reason_bogus)
+ {
+- if (rep->reason_bogus == LDNS_EDE_DNSSEC_BOGUS ||
+- rep->reason_bogus == LDNS_EDE_NONE) {
+- rep->reason_bogus = reason_bogus;
+- }
++ if(reason_bogus == LDNS_EDE_NONE) return;
++ if(reason_bogus == LDNS_EDE_DNSSEC_BOGUS
++ && rep->reason_bogus != LDNS_EDE_NONE
++ && rep->reason_bogus != LDNS_EDE_DNSSEC_BOGUS) return;
++ rep->reason_bogus = reason_bogus;
+ }
+
+
+@@ -200,6 +205,17 @@
+ log_err("validator: could not apply configuration settings.");
+ return 0;
+ }
++ if(env->cfg->disable_edns_do) {
++ struct trust_anchor* anchor = anchors_find_any_noninsecure(
++ env->anchors);
++ if(anchor) {
++ char b[LDNS_MAX_DOMAINLEN+2];
++ dname_str(anchor->name, b);
++ log_warn("validator: disable-edns-do is enabled, but there is a trust anchor for '%s'. Since DNSSEC could not work, the disable-edns-do setting is turned off. Continuing without it.", b);
++ lock_basic_unlock(&anchor->lock);
++ env->cfg->disable_edns_do = 0;
++ }
++ }
+
+ return 1;
+ }
+@@ -281,6 +297,21 @@
+ return val_new_getmsg(qstate, vq);
+ }
+
++/** reset validator query state for query restart */
++static void
++val_restart(struct val_qstate* vq)
++{
++ struct comm_timer* temp_timer;
++ int restart_count;
++ if(!vq) return;
++ temp_timer = vq->suspend_timer;
++ restart_count = vq->restart_count+1;
++ memset(vq, 0, sizeof(*vq));
++ vq->suspend_timer = temp_timer;
++ vq->restart_count = restart_count;
++ vq->state = VAL_INIT_STATE;
++}
++
+ /**
+ * Exit validation with an error status
+ *
+@@ -587,30 +618,42 @@
+ * completed.
+ *
+ * @param qstate: query state.
++ * @param vq: validator query state.
+ * @param env: module env for verify.
+ * @param ve: validator env for verify.
+ * @param qchase: query that was made.
+ * @param chase_reply: answer to validate.
+ * @param key_entry: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ * @return false if any of the rrsets in the an or ns sections of the message
+ * fail to verify. The message is then set to bogus.
+ */
+ static int
+-validate_msg_signatures(struct module_qstate* qstate, struct module_env* env,
+- struct val_env* ve, struct query_info* qchase,
+- struct reply_info* chase_reply, struct key_entry_key* key_entry)
++validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq,
++ struct module_env* env, struct val_env* ve, struct query_info* qchase,
++ struct reply_info* chase_reply, struct key_entry_key* key_entry,
++ int* suspend)
+ {
+ uint8_t* sname;
+ size_t i, slen;
+ struct ub_packed_rrset_key* s;
+ enum sec_status sec;
+- int dname_seen = 0;
++ int dname_seen = 0, num_verifies = 0, verified, have_state = 0;
+ char* reason = NULL;
+ sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
++ *suspend = 0;
++ if(vq->msg_signatures_state) {
++ /* Pick up the state, and reset it, may not be needed now. */
++ vq->msg_signatures_state = 0;
++ have_state = 1;
++ }
+
+ /* validate the ANSWER section */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
++ if(have_state && i <= vq->msg_signatures_index)
++ continue;
+ s = chase_reply->rrsets[i];
+ /* Skip the CNAME following a (validated) DNAME.
+ * Because of the normalization routines in the iterator,
+@@ -629,7 +672,7 @@
+
+ /* Verify the answer rrset */
+ sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason,
+- &reason_bogus, LDNS_SECTION_ANSWER, qstate);
++ &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified);
+ /* If the (answer) rrset failed to validate, then this
+ * message is BAD. */
+ if(sec != sec_status_secure) {
+@@ -654,14 +697,33 @@
+ ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) {
+ dname_seen = 1;
+ }
++ num_verifies += verified;
++ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
++ i+1 < (env->cfg->val_clean_additional?
++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets:
++ chase_reply->rrset_count)) {
++ /* If the number of RRSIGs exceeds the maximum in
++ * one go, suspend. Only suspend if there is a next
++ * rrset to verify, i+1<loopmax. Store where to
++ * continue later. */
++ *suspend = 1;
++ vq->msg_signatures_state = 1;
++ vq->msg_signatures_index = i;
++ verbose(VERB_ALGO, "msg signature validation "
++ "suspended");
++ return 0;
++ }
+ }
+
+ /* validate the AUTHORITY section */
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
++ if(have_state && i <= vq->msg_signatures_index)
++ continue;
+ s = chase_reply->rrsets[i];
+ sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason,
+- &reason_bogus, LDNS_SECTION_AUTHORITY, qstate);
++ &reason_bogus, LDNS_SECTION_AUTHORITY, qstate,
++ &verified);
+ /* If anything in the authority section fails to be secure,
+ * we have a bad message. */
+ if(sec != sec_status_secure) {
+@@ -675,6 +737,18 @@
+ update_reason_bogus(chase_reply, reason_bogus);
+ return 0;
+ }
++ num_verifies += verified;
++ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
++ i+1 < (env->cfg->val_clean_additional?
++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets:
++ chase_reply->rrset_count)) {
++ *suspend = 1;
++ vq->msg_signatures_state = 1;
++ vq->msg_signatures_index = i;
++ verbose(VERB_ALGO, "msg signature validation "
++ "suspended");
++ return 0;
++ }
+ }
+
+ /* If set, the validator should clean the additional section of
+@@ -684,22 +758,103 @@
+ /* attempt to validate the ADDITIONAL section rrsets */
+ for(i=chase_reply->an_numrrsets+chase_reply->ns_numrrsets;
+ i<chase_reply->rrset_count; i++) {
++ if(have_state && i <= vq->msg_signatures_index)
++ continue;
+ s = chase_reply->rrsets[i];
+ /* only validate rrs that have signatures with the key */
+ /* leave others unchecked, those get removed later on too */
+ val_find_rrset_signer(s, &sname, &slen);
+
++ verified = 0;
+ if(sname && query_dname_compare(sname, key_entry->name)==0)
+ (void)val_verify_rrset_entry(env, ve, s, key_entry,
+- &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate);
++ &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate,
++ &verified);
+ /* the additional section can fail to be secure,
+ * it is optional, check signature in case we need
+ * to clean the additional section later. */
++ num_verifies += verified;
++ if(num_verifies > MAX_VALIDATE_AT_ONCE &&
++ i+1 < chase_reply->rrset_count) {
++ *suspend = 1;
++ vq->msg_signatures_state = 1;
++ vq->msg_signatures_index = i;
++ verbose(VERB_ALGO, "msg signature validation "
++ "suspended");
++ return 0;
++ }
+ }
+
+ return 1;
+ }
+
++void
++validate_suspend_timer_cb(void* arg)
++{
++ struct module_qstate* qstate = (struct module_qstate*)arg;
++ verbose(VERB_ALGO, "validate_suspend timer, continue");
++ mesh_run(qstate->env->mesh, qstate->mesh_info, module_event_pass,
++ NULL);
++}
++
++/** Setup timer to continue validation of msg signatures later */
++static int
++validate_suspend_setup_timer(struct module_qstate* qstate,
++ struct val_qstate* vq, int id, enum val_state resume_state)
++{
++ struct timeval tv;
++ int usec, slack, base;
++ if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) {
++ verbose(VERB_ALGO, "validate_suspend timer: "
++ "reached MAX_VALIDATION_SUSPENDS (%d); error out",
++ MAX_VALIDATION_SUSPENDS);
++ errinf(qstate, "max validation suspends reached, "
++ "too many RRSIG validations");
++ return 0;
++ }
++ verbose(VERB_ALGO, "validate_suspend timer, set for suspend");
++ vq->state = resume_state;
++ qstate->ext_state[id] = module_wait_reply;
++ if(!vq->suspend_timer) {
++ vq->suspend_timer = comm_timer_create(
++ qstate->env->worker_base,
++ validate_suspend_timer_cb, qstate);
++ if(!vq->suspend_timer) {
++ log_err("validate_suspend_setup_timer: "
++ "out of memory for comm_timer_create");
++ return 0;
++ }
++ }
++ /* The timer is activated later, after other events in the event
++ * loop have been processed. The query state can also be deleted,
++ * when the list is full and query states are dropped. */
++ /* Extend wait time if there are a lot of queries or if this one
++ * is taking long, to keep around cpu time for ordinary queries. */
++ usec = 50000; /* 50 msec */
++ slack = 0;
++ if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states)
++ slack += 3;
++ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/2)
++ slack += 2;
++ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/4)
++ slack += 1;
++ if(vq->suspend_count > 3)
++ slack += 3;
++ else if(vq->suspend_count > 0)
++ slack += vq->suspend_count;
++ if(slack != 0 && slack <= 12 /* No numeric overflow. */) {
++ usec = usec << slack;
++ }
++ /* Spread such timeouts within 90%-100% of the original timer. */
++ base = usec * 9/10;
++ usec = base + ub_random_max(qstate->env->rnd, usec-base);
++ tv.tv_usec = (usec % 1000000);
++ tv.tv_sec = (usec / 1000000);
++ vq->suspend_count ++;
++ comm_timer_set(vq->suspend_timer, &tv);
++ return 1;
++}
++
+ /**
+ * Detect wrong truncated response (say from BIND 9.6.1 that is forwarding
+ * and saw the NS record without signatures from a referral).
+@@ -798,11 +953,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_positive_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ uint8_t* wc = NULL;
+ size_t wl;
+@@ -811,6 +972,7 @@
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
++ *suspend = 0;
+
+ /* validate the ANSWER section - this will be the answer itself */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
+@@ -862,17 +1024,23 @@
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+- enum sec_status sec = nsec3_prove_wildcard(env, ve,
++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
++ enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey, wc);
++ chase_reply->ns_numrrsets, qchase, kkey, wc,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "Positive wildcard response is "
+ "insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ wc_NSEC_ok = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ }
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+@@ -904,11 +1072,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_nodata_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ /* Since we are here, there must be nothing in the ANSWER section to
+ * validate. */
+@@ -925,6 +1099,7 @@
+ int nsec3s_seen = 0; /* nsec3s seen */
+ struct ub_packed_rrset_key* s;
+ size_t i;
++ *suspend = 0;
+
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
+@@ -963,16 +1138,23 @@
+ }
+ }
+
+- if(!has_valid_nsec && nsec3s_seen) {
++ if(!has_valid_nsec && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ enum sec_status sec = nsec3_prove_nodata(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey);
++ chase_reply->ns_numrrsets, qchase, kkey,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "NODATA response is insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ has_valid_nsec = 1;
++ } else if(sec == sec_status_unchecked) {
++ /* check is incomplete; suspend */
++ *suspend = 1;
++ return;
++ }
+ }
+
+ if(!has_valid_nsec) {
+@@ -1004,11 +1186,18 @@
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
+ * @param rcode: adjusted RCODE, in case of RCODE/proof mismatch leniency.
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_nameerror_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey, int* rcode)
++ struct key_entry_key* kkey, int* rcode,
++ struct module_qstate* qstate, struct val_qstate* vq,
++ int* nsec3_calculations, int* suspend)
+ {
+ int has_valid_nsec = 0;
+ int has_valid_wnsec = 0;
+@@ -1018,6 +1207,7 @@
+ uint8_t* ce;
+ int ce_labs = 0;
+ int prev_ce_labs = 0;
++ *suspend = 0;
+
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+ chase_reply->ns_numrrsets; i++) {
+@@ -1047,13 +1237,18 @@
+ nsec3s_seen = 1;
+ }
+
+- if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen) {
++ if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ /* use NSEC3 proof, both answer and auth rrsets, in case
+ * NSEC3s end up in the answer (due to qtype=NSEC3 or so) */
+ chase_reply->security = nsec3_prove_nameerror(env, ve,
+ chase_reply->rrsets, chase_reply->an_numrrsets+
+- chase_reply->ns_numrrsets, qchase, kkey);
+- if(chase_reply->security != sec_status_secure) {
++ chase_reply->ns_numrrsets, qchase, kkey,
++ &vq->nsec3_cache_table, nsec3_calculations);
++ if(chase_reply->security == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ } else if(chase_reply->security != sec_status_secure) {
+ verbose(VERB_QUERY, "NameError response failed nsec, "
+ "nsec3 proof was %s", sec_status_to_string(
+ chase_reply->security));
+@@ -1065,26 +1260,34 @@
+
+ /* If the message fails to prove either condition, it is bogus. */
+ if(!has_valid_nsec) {
++ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
++ qstate, vq, nsec3_calculations, suspend);
++ if(*suspend) return;
+ verbose(VERB_QUERY, "NameError response has failed to prove: "
+ "qname does not exist");
+- chase_reply->security = sec_status_bogus;
+- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ /* Be lenient with RCODE in NSEC NameError responses */
+- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
+- if (chase_reply->security == sec_status_secure)
++ if(chase_reply->security == sec_status_secure) {
+ *rcode = LDNS_RCODE_NOERROR;
++ } else {
++ chase_reply->security = sec_status_bogus;
++ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ }
+ return;
+ }
+
+ if(!has_valid_wnsec) {
++ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
++ qstate, vq, nsec3_calculations, suspend);
++ if(*suspend) return;
+ verbose(VERB_QUERY, "NameError response has failed to prove: "
+ "covering wildcard does not exist");
+- chase_reply->security = sec_status_bogus;
+- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ /* Be lenient with RCODE in NSEC NameError responses */
+- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
+- if (chase_reply->security == sec_status_secure)
++ if (chase_reply->security == sec_status_secure) {
+ *rcode = LDNS_RCODE_NOERROR;
++ } else {
++ chase_reply->security = sec_status_bogus;
++ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
++ }
+ return;
+ }
+
+@@ -1144,11 +1347,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_any_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ /* all answer and auth rrsets already verified */
+ /* but check if a wildcard response is given, then check NSEC/NSEC3
+@@ -1159,6 +1368,7 @@
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
++ *suspend = 0;
+
+ if(qchase->qtype != LDNS_RR_TYPE_ANY) {
+ log_err("internal error: ANY validation called for non-ANY");
+@@ -1213,19 +1423,25 @@
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ /* look both in answer and auth section for NSEC3s */
+- enum sec_status sec = nsec3_prove_wildcard(env, ve,
++ enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ chase_reply->rrsets,
+- chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
+- qchase, kkey, wc);
++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
++ qchase, kkey, wc, &vq->nsec3_cache_table,
++ nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "Positive ANY wildcard response is "
+ "insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ wc_NSEC_ok = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ }
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+@@ -1258,11 +1474,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_cname_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ uint8_t* wc = NULL;
+ size_t wl;
+@@ -1270,6 +1492,7 @@
+ int nsec3s_seen = 0;
+ size_t i;
+ struct ub_packed_rrset_key* s;
++ *suspend = 0;
+
+ /* validate the ANSWER section - this will be the CNAME (+DNAME) */
+ for(i=0; i<chase_reply->an_numrrsets; i++) {
+@@ -1334,17 +1557,23 @@
+ /* If this was a positive wildcard response that we haven't already
+ * proven, and we have NSEC3 records, try to prove it using the NSEC3
+ * records. */
+- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+- enum sec_status sec = nsec3_prove_wildcard(env, ve,
++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
++ enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey, wc);
++ chase_reply->ns_numrrsets, qchase, kkey, wc,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "wildcard CNAME response is "
+ "insecure");
+ chase_reply->security = sec_status_insecure;
+ return;
+- } else if(sec == sec_status_secure)
++ } else if(sec == sec_status_secure) {
+ wc_NSEC_ok = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
++ }
+ }
+
+ /* If after all this, we still haven't proven the positive wildcard
+@@ -1375,11 +1604,17 @@
+ * @param chase_reply: answer to that query to validate.
+ * @param kkey: the key entry, which is trusted, and which matches
+ * the signer of the answer. The key entry isgood().
++ * @param qstate: query state for the region.
++ * @param vq: validator state for the nsec3 cache table.
++ * @param nsec3_calculations: current nsec3 hash calculations.
++ * @param suspend: returned true if the task takes too long and needs to
++ * suspend to continue the effort later.
+ */
+ static void
+ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
+ struct query_info* qchase, struct reply_info* chase_reply,
+- struct key_entry_key* kkey)
++ struct key_entry_key* kkey, struct module_qstate* qstate,
++ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
+ {
+ int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/
+ uint8_t* ce = NULL; /* for wildcard nodata responses. This is the
+@@ -1393,6 +1628,7 @@
+ uint8_t* nsec_ce; /* Used to find the NSEC with the longest ce */
+ int ce_labs = 0;
+ int prev_ce_labs = 0;
++ *suspend = 0;
+
+ /* the AUTHORITY section */
+ for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
+@@ -1458,11 +1694,13 @@
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ return;
+ }
+- if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) {
++ if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen &&
++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ int nodata;
+ enum sec_status sec = nsec3_prove_nxornodata(env, ve,
+ chase_reply->rrsets+chase_reply->an_numrrsets,
+- chase_reply->ns_numrrsets, qchase, kkey, &nodata);
++ chase_reply->ns_numrrsets, qchase, kkey, &nodata,
++ &vq->nsec3_cache_table, nsec3_calculations);
+ if(sec == sec_status_insecure) {
+ verbose(VERB_ALGO, "CNAMEchain to noanswer response "
+ "is insecure");
+@@ -1472,6 +1710,9 @@
+ if(nodata)
+ nodata_valid_nsec = 1;
+ else nxdomain_valid_nsec = 1;
++ } else if(sec == sec_status_unchecked) {
++ *suspend = 1;
++ return;
+ }
+ }
+
+@@ -1672,20 +1913,13 @@
+ vq->state = VAL_FINISHED_STATE;
+ return 1;
+ } else if(key_entry_isbad(vq->key_entry)) {
+- sldns_ede_code ede = LDNS_EDE_DNSSEC_BOGUS;
+-
+- /* the key could have a more spefic EDE than just bogus */
+- if(key_entry_get_reason_bogus(vq->key_entry) != LDNS_EDE_NONE) {
+- ede = key_entry_get_reason_bogus(vq->key_entry);
+- }
+-
++ /* Bad keys should have the relevant EDE code and text */
++ sldns_ede_code ede = key_entry_get_reason_bogus(vq->key_entry);
+ /* key is bad, chain is bad, reply is bogus */
+ errinf_dname(qstate, "key for validation", vq->key_entry->name);
+ errinf_ede(qstate, "is marked as invalid", ede);
+- if(key_entry_get_reason(vq->key_entry)) {
+- errinf(qstate, "because of a previous");
+- errinf(qstate, key_entry_get_reason(vq->key_entry));
+- }
++ errinf(qstate, "because of a previous");
++ errinf(qstate, key_entry_get_reason(vq->key_entry));
+
+ /* no retries, stop bothering the authority until timeout */
+ vq->restart_count = ve->max_restart;
+@@ -1822,13 +2056,37 @@
+ * Uses negative cache for NSEC3 lookup of DS responses. */
+ /* only if cache not blacklisted, of course */
+ struct dns_msg* msg;
+- if(!qstate->blacklist && !vq->chain_blacklist &&
++ int suspend;
++ if(vq->sub_ds_msg) {
++ /* We have a suspended DS reply from a sub-query;
++ * process it. */
++ verbose(VERB_ALGO, "Process suspended sub DS response");
++ msg = vq->sub_ds_msg;
++ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
++ msg, &msg->qinfo, NULL, &suspend);
++ if(suspend) {
++ /* we'll come back here later to continue */
++ if(!validate_suspend_setup_timer(qstate, vq,
++ id, VAL_FINDKEY_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
++ vq->sub_ds_msg = NULL;
++ return 1; /* continue processing ds-response results */
++ } else if(!qstate->blacklist && !vq->chain_blacklist &&
+ (msg=val_find_DS(qstate->env, target_key_name,
+ target_key_len, vq->qchase.qclass, qstate->region,
+ vq->key_entry->name)) ) {
+ verbose(VERB_ALGO, "Process cached DS response");
+ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
+- msg, &msg->qinfo, NULL);
++ msg, &msg->qinfo, NULL, &suspend);
++ if(suspend) {
++ /* we'll come back here later to continue */
++ if(!validate_suspend_setup_timer(qstate, vq,
++ id, VAL_FINDKEY_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ return 1; /* continue processing ds-response results */
+ }
+ if(!generate_request(qstate, id, target_key_name,
+@@ -1871,7 +2129,7 @@
+ struct val_env* ve, int id)
+ {
+ enum val_classification subtype;
+- int rcode;
++ int rcode, suspend, nsec3_calculations = 0;
+
+ if(!vq->key_entry) {
+ verbose(VERB_ALGO, "validate: no key entry, failed");
+@@ -1888,7 +2146,8 @@
+ vq->chase_reply->security = sec_status_insecure;
+ val_mark_insecure(vq->chase_reply, vq->key_entry->name,
+ qstate->env->rrset_cache, qstate->env);
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+ return 1;
+ }
+
+@@ -1897,12 +2156,13 @@
+ "of trust to keys for", vq->key_entry->name,
+ LDNS_RR_TYPE_DNSKEY, vq->key_entry->key_class);
+ vq->chase_reply->security = sec_status_bogus;
+-
+- update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSKEY_MISSING);
++ update_reason_bogus(vq->chase_reply,
++ key_entry_get_reason_bogus(vq->key_entry));
+ errinf_ede(qstate, "while building chain of trust",
+- LDNS_EDE_DNSKEY_MISSING);
++ key_entry_get_reason_bogus(vq->key_entry));
+ if(vq->restart_count >= ve->max_restart)
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+ return 1;
+ }
+
+@@ -1926,8 +2186,14 @@
+
+ /* check signatures in the message;
+ * answer and authority must be valid, additional is only checked. */
+- if(!validate_msg_signatures(qstate, qstate->env, ve, &vq->qchase,
+- vq->chase_reply, vq->key_entry)) {
++ if(!validate_msg_signatures(qstate, vq, qstate->env, ve, &vq->qchase,
++ vq->chase_reply, vq->key_entry, &suspend)) {
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate, vq,
++ id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ /* workaround bad recursor out there that truncates (even
+ * with EDNS4k) to 512 by removing RRSIG from auth section
+ * for positive replies*/
+@@ -1956,7 +2222,14 @@
+ case VAL_CLASS_POSITIVE:
+ verbose(VERB_ALGO, "Validating a positive response");
+ validate_positive_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(positive): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1965,7 +2238,14 @@
+ case VAL_CLASS_NODATA:
+ verbose(VERB_ALGO, "Validating a nodata response");
+ validate_nodata_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(nodata): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1975,7 +2255,14 @@
+ rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags);
+ verbose(VERB_ALGO, "Validating a nxdomain response");
+ validate_nameerror_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry, &rcode);
++ &vq->qchase, vq->chase_reply, vq->key_entry, &rcode,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(nxdomain): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1986,7 +2273,14 @@
+ case VAL_CLASS_CNAME:
+ verbose(VERB_ALGO, "Validating a cname response");
+ validate_cname_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(cname): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -1996,7 +2290,14 @@
+ verbose(VERB_ALGO, "Validating a cname noanswer "
+ "response");
+ validate_cname_noanswer_response(qstate->env, ve,
+- &vq->qchase, vq->chase_reply, vq->key_entry);
++ &vq->qchase, vq->chase_reply, vq->key_entry,
++ qstate, vq, &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(cname_noanswer): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -2013,8 +2314,15 @@
+ case VAL_CLASS_ANY:
+ verbose(VERB_ALGO, "Validating a positive ANY "
+ "response");
+- validate_any_response(qstate->env, ve, &vq->qchase,
+- vq->chase_reply, vq->key_entry);
++ validate_any_response(qstate->env, ve, &vq->qchase,
++ vq->chase_reply, vq->key_entry, qstate, vq,
++ &nsec3_calculations, &suspend);
++ if(suspend) {
++ if(!validate_suspend_setup_timer(qstate,
++ vq, id, VAL_VALIDATE_STATE))
++ return val_error(qstate, id);
++ return 0;
++ }
+ verbose(VERB_DETAIL, "validate(positive_any): %s",
+ sec_status_to_string(
+ vq->chase_reply->security));
+@@ -2123,16 +2431,13 @@
+ if(vq->orig_msg->rep->security == sec_status_bogus) {
+ /* see if we can try again to fetch data */
+ if(vq->restart_count < ve->max_restart) {
+- int restart_count = vq->restart_count+1;
+ verbose(VERB_ALGO, "validation failed, "
+ "blacklist and retry to fetch data");
+ val_blacklist(&qstate->blacklist, qstate->region,
+ qstate->reply_origin, 0);
+ qstate->reply_origin = NULL;
+ qstate->errinf = NULL;
+- memset(vq, 0, sizeof(*vq));
+- vq->restart_count = restart_count;
+- vq->state = VAL_INIT_STATE;
++ val_restart(vq);
+ verbose(VERB_ALGO, "pass back to next module");
+ qstate->ext_state[id] = module_restart_next;
+ return 0;
+@@ -2151,9 +2456,19 @@
+ log_query_info(NO_VERBOSE, "validation failure",
+ &qstate->qinfo);
+ else {
+- char* err = errinf_to_str_bogus(qstate);
+- if(err) log_info("%s", err);
+- free(err);
++ char* err_str = errinf_to_str_bogus(qstate);
++ if(err_str) {
++ size_t err_str_len = strlen(err_str);
++ log_info("%s", err_str);
++ /* allocate space and store the error
++ * string */
++ vq->orig_msg->rep->reason_bogus_str = regional_alloc(
++ qstate->region,
++ sizeof(char) * (err_str_len+1));
++ memcpy(vq->orig_msg->rep->reason_bogus_str,
++ err_str, err_str_len+1);
++ }
++ free(err_str);
+ }
+ }
+ /*
+@@ -2195,6 +2510,9 @@
+ }
+ }
+ }
++
++ /* Update rep->reason_bogus as it is the one being cached */
++ update_reason_bogus(vq->orig_msg->rep, errinf_to_reason_bogus(qstate));
+ /* store results in cache */
+ if(qstate->query_flags&BIT_RD) {
+ /* if secure, this will override cache anyway, no need
+@@ -2370,13 +2688,17 @@
+ log_nametypeclass(VERB_OPS, "failed to prime trust anchor -- "
+ "could not fetch DNSKEY rrset",
+ ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass);
++ reason_bogus = LDNS_EDE_DNSKEY_MISSING;
++ reason = "no DNSKEY rrset";
+ if(qstate->env->cfg->harden_dnssec_stripped) {
+- errinf_ede(qstate, "no DNSKEY rrset", LDNS_EDE_DNSKEY_MISSING);
++ errinf_ede(qstate, reason, reason_bogus);
+ kkey = key_entry_create_bad(qstate->region, ta->name,
+ ta->namelen, ta->dclass, BOGUS_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ } else kkey = key_entry_create_null(qstate->region, ta->name,
+ ta->namelen, ta->dclass, NULL_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ if(!kkey) {
+ log_err("out of memory: allocate fail prime key");
+@@ -2409,9 +2731,11 @@
+ errinf_ede(qstate, reason, reason_bogus);
+ kkey = key_entry_create_bad(qstate->region, ta->name,
+ ta->namelen, ta->dclass, BOGUS_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ } else kkey = key_entry_create_null(qstate->region, ta->name,
+ ta->namelen, ta->dclass, NULL_KEY_TTL,
++ reason_bogus, reason,
+ *qstate->env->now);
+ if(!kkey) {
+ log_err("out of memory: allocate null prime key");
+@@ -2440,7 +2764,10 @@
+ * DS response indicated an end to secure space, is_good if the DS
+ * validated. It returns ke=NULL if the DS response indicated that the
+ * request wasn't a delegation point.
+- * @return 0 on servfail error (malloc failure).
++ * @return
++ * 0 on success,
++ * 1 on servfail error (malloc failure),
++ * 2 on NSEC3 suspend.
+ */
+ static int
+ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
+@@ -2451,6 +2778,7 @@
+ char* reason = NULL;
+ sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ enum val_classification subtype;
++ int verified;
+ if(rcode != LDNS_RCODE_NOERROR) {
+ char rc[16];
+ rc[0]=0;
+@@ -2458,8 +2786,9 @@
+ /* errors here pretty much break validation */
+ verbose(VERB_DETAIL, "DS response was error, thus bogus");
+ errinf(qstate, rc);
+- errinf_ede(qstate, "no DS", LDNS_EDE_NETWORK_ERROR);
+-
++ reason = "no DS";
++ reason_bogus = LDNS_EDE_NETWORK_ERROR;
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+
+@@ -2473,13 +2802,14 @@
+ if(!ds) {
+ log_warn("internal error: POSITIVE DS response was "
+ "missing DS.");
+- errinf_ede(qstate, "no DS record", LDNS_EDE_DNSSEC_BOGUS);
++ reason = "no DS record";
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+ /* Verify only returns BOGUS or SECURE. If the rrset is
+ * bogus, then we are done. */
+ sec = val_verify_rrset_entry(qstate->env, ve, ds,
+- vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate);
++ vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified);
+ if(sec != sec_status_secure) {
+ verbose(VERB_DETAIL, "DS rrset in DS response did "
+ "not verify");
+@@ -2492,22 +2822,20 @@
+ if(!val_dsset_isusable(ds)) {
+ /* If they aren't usable, then we treat it like
+ * there was no DS. */
+-
+- /* TODO add EDE Unsupported DS Digest Type; this needs
+- * EDE to be added on non SERVFAIL answers. */
+-
+- *ke = key_entry_create_null(qstate->region,
+- qinfo->qname, qinfo->qname_len, qinfo->qclass,
+- ub_packed_rrset_ttl(ds), *qstate->env->now);
+- return (*ke) != NULL;
++ *ke = key_entry_create_null(qstate->region,
++ qinfo->qname, qinfo->qname_len, qinfo->qclass,
++ ub_packed_rrset_ttl(ds),
++ LDNS_EDE_UNSUPPORTED_DS_DIGEST, NULL,
++ *qstate->env->now);
++ return (*ke) == NULL;
+ }
+
+ /* Otherwise, we return the positive response. */
+ log_query_info(VERB_DETAIL, "validated DS", qinfo);
+ *ke = key_entry_create_rrset(qstate->region,
+ qinfo->qname, qinfo->qname_len, qinfo->qclass, ds,
+- NULL, *qstate->env->now);
+- return (*ke) != NULL;
++ NULL, LDNS_EDE_NONE, NULL, *qstate->env->now);
++ return (*ke) == NULL;
+ } else if(subtype == VAL_CLASS_NODATA ||
+ subtype == VAL_CLASS_NAMEERROR) {
+ /* NODATA means that the qname exists, but that there was
+@@ -2518,7 +2846,8 @@
+ /* make sure there are NSECs or NSEC3s with signatures */
+ if(!val_has_signed_nsecs(msg->rep, &reason)) {
+ verbose(VERB_ALGO, "no NSECs: %s", reason);
+- errinf_ede(qstate, reason, LDNS_EDE_NSEC_MISSING);
++ reason_bogus = LDNS_EDE_NSEC_MISSING;
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+
+@@ -2530,7 +2859,7 @@
+ /* Try to prove absence of the DS with NSEC */
+ sec = val_nsec_prove_nodata_dsreply(
+ qstate->env, ve, qinfo, msg->rep, vq->key_entry,
+- &proof_ttl, &reason, qstate);
++ &proof_ttl, &reason, &reason_bogus, qstate);
+ switch(sec) {
+ case sec_status_secure:
+ verbose(VERB_DETAIL, "NSEC RRset for the "
+@@ -2538,13 +2867,14 @@
+ *ke = key_entry_create_null(qstate->region,
+ qinfo->qname, qinfo->qname_len,
+ qinfo->qclass, proof_ttl,
++ LDNS_EDE_NONE, NULL,
+ *qstate->env->now);
+- return (*ke) != NULL;
++ return (*ke) == NULL;
+ case sec_status_insecure:
+ verbose(VERB_DETAIL, "NSEC RRset for the "
+ "referral proved not a delegation point");
+ *ke = NULL;
+- return 1;
++ return 0;
+ case sec_status_bogus:
+ verbose(VERB_DETAIL, "NSEC RRset for the "
+ "referral did not prove no DS.");
+@@ -2556,10 +2886,17 @@
+ break;
+ }
+
++ if(!nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
++ log_err("malloc failure in ds_response_to_ke for "
++ "NSEC3 cache");
++ reason = "malloc failure";
++ errinf_ede(qstate, reason, 0);
++ goto return_bogus;
++ }
+ sec = nsec3_prove_nods(qstate->env, ve,
+ msg->rep->rrsets + msg->rep->an_numrrsets,
+ msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason,
+- &reason_bogus, qstate);
++ &reason_bogus, qstate, &vq->nsec3_cache_table);
+ switch(sec) {
+ case sec_status_insecure:
+ /* case insecure also continues to unsigned
+@@ -2571,19 +2908,21 @@
+ *ke = key_entry_create_null(qstate->region,
+ qinfo->qname, qinfo->qname_len,
+ qinfo->qclass, proof_ttl,
++ LDNS_EDE_NONE, NULL,
+ *qstate->env->now);
+- return (*ke) != NULL;
++ return (*ke) == NULL;
+ case sec_status_indeterminate:
+ verbose(VERB_DETAIL, "NSEC3s for the "
+ "referral proved no delegation");
+ *ke = NULL;
+- return 1;
++ return 0;
+ case sec_status_bogus:
+ verbose(VERB_DETAIL, "NSEC3s for the "
+ "referral did not prove no DS.");
+ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ case sec_status_unchecked:
++ return 2;
+ default:
+ /* NSEC3 proof did not work */
+ break;
+@@ -2593,7 +2932,8 @@
+ * this is BOGUS. */
+ verbose(VERB_DETAIL, "DS %s ran out of options, so return "
+ "bogus", val_classification_to_string(subtype));
+- errinf(qstate, "no DS but also no proof of that");
++ reason = "no DS but also no proof of that";
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ } else if(subtype == VAL_CLASS_CNAME ||
+ subtype == VAL_CLASS_CNAMENOANSWER) {
+@@ -2605,36 +2945,40 @@
+ cname = reply_find_rrset_section_an(msg->rep, qinfo->qname,
+ qinfo->qname_len, LDNS_RR_TYPE_CNAME, qinfo->qclass);
+ if(!cname) {
+- errinf(qstate, "validator classified CNAME but no "
+- "CNAME of the queried name for DS");
++ reason = "validator classified CNAME but no "
++ "CNAME of the queried name for DS";
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+ if(((struct packed_rrset_data*)cname->entry.data)->rrsig_count
+ == 0) {
+ if(msg->rep->an_numrrsets != 0 && ntohs(msg->rep->
+ rrsets[0]->rk.type)==LDNS_RR_TYPE_DNAME) {
+- errinf(qstate, "DS got DNAME answer");
++ reason = "DS got DNAME answer";
+ } else {
+- errinf(qstate, "DS got unsigned CNAME answer");
++ reason = "DS got unsigned CNAME answer";
+ }
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ }
+- sec = val_verify_rrset_entry(qstate->env, ve, cname,
+- vq->key_entry, &reason, NULL, LDNS_SECTION_ANSWER, qstate);
++ sec = val_verify_rrset_entry(qstate->env, ve, cname,
++ vq->key_entry, &reason, &reason_bogus,
++ LDNS_SECTION_ANSWER, qstate, &verified);
+ if(sec == sec_status_secure) {
+ verbose(VERB_ALGO, "CNAME validated, "
+ "proof that DS does not exist");
+ /* and that it is not a referral point */
+ *ke = NULL;
+- return 1;
++ return 0;
+ }
+ errinf(qstate, "CNAME in DS response was not secure.");
+- errinf(qstate, reason);
++ errinf_ede(qstate, reason, reason_bogus);
+ goto return_bogus;
+ } else {
+ verbose(VERB_QUERY, "Encountered an unhandled type of "
+ "DS response, thus bogus.");
+ errinf(qstate, "no DS and");
++ reason = "no DS";
+ if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR) {
+ char rc[16];
+ rc[0]=0;
+@@ -2647,9 +2991,9 @@
+ }
+ return_bogus:
+ *ke = key_entry_create_bad(qstate->region, qinfo->qname,
+- qinfo->qname_len, qinfo->qclass,
+- BOGUS_KEY_TTL, *qstate->env->now);
+- return (*ke) != NULL;
++ qinfo->qname_len, qinfo->qclass, BOGUS_KEY_TTL,
++ reason_bogus, reason, *qstate->env->now);
++ return (*ke) == NULL;
+ }
+
+ /**
+@@ -2670,17 +3014,31 @@
+ static void
+ process_ds_response(struct module_qstate* qstate, struct val_qstate* vq,
+ int id, int rcode, struct dns_msg* msg, struct query_info* qinfo,
+- struct sock_list* origin)
++ struct sock_list* origin, int* suspend)
+ {
+ struct val_env* ve = (struct val_env*)qstate->env->modinfo[id];
+ struct key_entry_key* dske = NULL;
+ uint8_t* olds = vq->empty_DS_name;
++ int ret;
++ *suspend = 0;
+ vq->empty_DS_name = NULL;
+- if(!ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske)) {
++ ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske);
++ if(ret != 0) {
++ switch(ret) {
++ case 1:
+ log_err("malloc failure in process_ds_response");
+ vq->key_entry = NULL; /* make it error */
+ vq->state = VAL_VALIDATE_STATE;
+ return;
++ case 2:
++ *suspend = 1;
++ return;
++ default:
++ log_err("unhandled error value for ds_response_to_ke");
++ vq->key_entry = NULL; /* make it error */
++ vq->state = VAL_VALIDATE_STATE;
++ return;
++ }
+ }
+ if(dske == NULL) {
+ vq->empty_DS_name = regional_alloc_init(qstate->region,
+@@ -2768,14 +3126,17 @@
+ vq->restart_count++;
+ return;
+ }
+- vq->key_entry = key_entry_create_bad(qstate->region,
++ reason = "No DNSKEY record";
++ reason_bogus = LDNS_EDE_DNSKEY_MISSING;
++ vq->key_entry = key_entry_create_bad(qstate->region,
+ qinfo->qname, qinfo->qname_len, qinfo->qclass,
+- BOGUS_KEY_TTL, *qstate->env->now);
++ BOGUS_KEY_TTL, reason_bogus, reason,
++ *qstate->env->now);
+ if(!vq->key_entry) {
+ log_err("alloc failure in missing dnskey response");
+ /* key_entry is NULL for failure in Validate */
+ }
+- errinf_ede(qstate, "No DNSKEY record", LDNS_EDE_DNSKEY_MISSING);
++ errinf_ede(qstate, reason, reason_bogus);
+ errinf_origin(qstate, origin);
+ errinf_dname(qstate, "for key", qinfo->qname);
+ vq->state = VAL_VALIDATE_STATE;
+@@ -2822,7 +3183,8 @@
+ qstate->errinf = NULL;
+
+ /* The DNSKEY validated, so cache it as a trusted key rrset. */
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+
+ /* If good, we stay in the FINDKEY state. */
+ log_query_info(VERB_DETAIL, "validated DNSKEY", qinfo);
+@@ -2890,7 +3252,8 @@
+ errinf_origin(qstate, origin);
+ errinf_dname(qstate, "for trust anchor", ta->name);
+ /* store the freshly primed entry in the cache */
+- key_cache_insert(ve->kcache, vq->key_entry, qstate);
++ key_cache_insert(ve->kcache, vq->key_entry,
++ qstate->env->cfg->val_log_level >= 2);
+ }
+
+ /* If the result of the prime is a null key, skip the FINDKEY state.*/
+@@ -2927,9 +3290,26 @@
+ return;
+ }
+ if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) {
++ int suspend;
+ process_ds_response(super, vq, id, qstate->return_rcode,
+- qstate->return_msg, &qstate->qinfo,
+- qstate->reply_origin);
++ qstate->return_msg, &qstate->qinfo,
++ qstate->reply_origin, &suspend);
++ /* If NSEC3 was needed during validation, NULL the NSEC3 cache;
++ * it will be re-initiated if needed later on.
++ * Validation (and the cache table) are happening/allocated in
++ * the super qstate whilst the RRs are allocated (and pointed
++ * to) in this sub qstate. */
++ if(vq->nsec3_cache_table.ct) {
++ vq->nsec3_cache_table.ct = NULL;
++ }
++ if(suspend) {
++ /* deep copy the return_msg to vq->sub_ds_msg; it will
++ * be resumed later in the super state with the caveat
++ * that the initial calculations will be re-caclulated
++ * and re-suspended there before continuing. */
++ vq->sub_ds_msg = dns_msg_deepcopy_region(
++ qstate->return_msg, super->region);
++ }
+ return;
+ } else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) {
+ process_dnskey_response(super, vq, id, qstate->return_rcode,
+@@ -2943,8 +3323,15 @@
+ void
+ val_clear(struct module_qstate* qstate, int id)
+ {
++ struct val_qstate* vq;
+ if(!qstate)
+ return;
++ vq = (struct val_qstate*)qstate->minfo[id];
++ if(vq) {
++ if(vq->suspend_timer) {
++ comm_timer_delete(vq->suspend_timer);
++ }
++ }
+ /* everything is allocated in the region, so assign NULL */
+ qstate->minfo[id] = NULL;
+ }
+--- contrib/unbound/validator/validator.h.orig
++++ contrib/unbound/validator/validator.h
+@@ -45,11 +45,13 @@
+ #include "util/module.h"
+ #include "util/data/msgreply.h"
+ #include "validator/val_utils.h"
++#include "validator/val_nsec3.h"
+ struct val_anchors;
+ struct key_cache;
+ struct key_entry_key;
+ struct val_neg_cache;
+ struct config_strlist;
++struct comm_timer;
+
+ /**
+ * This is the TTL to use when a trust anchor fails to prime. A trust anchor
+@@ -215,6 +217,19 @@
+
+ /** true if this state is waiting to prime a trust anchor */
+ int wait_prime_ta;
++
++ /** State to continue with RRSIG validation in a message later */
++ int msg_signatures_state;
++ /** The rrset index for the msg signatures to continue from */
++ size_t msg_signatures_index;
++ /** Cache table for NSEC3 hashes */
++ struct nsec3_cache_table nsec3_cache_table;
++ /** DS message from sub if it got suspended from NSEC3 calculations */
++ struct dns_msg* sub_ds_msg;
++ /** The timer to resume processing msg signatures */
++ struct comm_timer* suspend_timer;
++ /** Number of suspends */
++ int suspend_count;
+ };
+
+ /**
+@@ -262,4 +277,7 @@
+ */
+ size_t val_get_mem(struct module_env* env, int id);
+
++/** Timer callback for msg signatures continue timer */
++void validate_suspend_timer_cb(void* arg);
++
+ #endif /* VALIDATOR_VALIDATOR_H */
+--- lib/libunbound/Makefile.orig
++++ lib/libunbound/Makefile
+@@ -24,8 +24,10 @@
+ msgreply.c net_help.c netevent.c outbound_list.c outside_network.c \
+ packed_rrset.c parse.c parseutil.c proxy_protocol.c \
+ random.c rbtree.c redis.c \
+- regional.c respip.c rpz.c rrdef.c rrset.c rtt.c sbuffer.c slabhash.c \
+- str2wire.c tcp_conn_limit.c timehist.c tube.c ub_event_pluggable.c \
++ regional.c respip.c rfc_1982.c rpz.c rrdef.c rrset.c rtt.c sbuffer.c \
++ siphash.c slabhash.c \
++ str2wire.c tcp_conn_limit.c timehist.c timeval_func.c \
++ tube.c ub_event_pluggable.c \
+ val_anchor.c val_kcache.c val_kentry.c val_neg.c val_nsec.c \
+ val_nsec3.c val_secalgo.c val_sigcrypt.c val_utils.c validator.c \
+ view.c winsock_event.c wire2str.c
+--- lib/libunbound/config.h.orig
++++ lib/libunbound/config.h
+@@ -793,7 +793,7 @@
+ #define PACKAGE_NAME "unbound"
+
+ /* Define to the full name and version of this package. */
+-#define PACKAGE_STRING "unbound 1.17.1"
++#define PACKAGE_STRING "unbound 1.19.1"
+
+ /* Define to the one symbol short name of this package. */
+ #define PACKAGE_TARNAME "unbound"
+@@ -802,7 +802,7 @@
+ #define PACKAGE_URL ""
+
+ /* Define to the version of this package. */
+-#define PACKAGE_VERSION "1.17.1"
++#define PACKAGE_VERSION "1.19.1"
+
+ /* default pidfile location */
+ #define PIDFILE "/var/unbound/unbound.pid"
+@@ -825,7 +825,7 @@
+ #define ROOT_CERT_FILE "/var/unbound/icannbundle.pem"
+
+ /* version number for resource files */
+-#define RSRC_PACKAGE_VERSION 1,17,1,0
++#define RSRC_PACKAGE_VERSION 1,19,0,0
+
+ /* Directory to chdir to */
+ #define RUN_DIR "/var/unbound"
diff --git a/website/static/security/patches/SA-24:03/unbound-14.patch.asc b/website/static/security/patches/SA-24:03/unbound-14.patch.asc
new file mode 100644
index 0000000000..a283999b4a
--- /dev/null
+++ b/website/static/security/patches/SA-24:03/unbound-14.patch.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEthUnfoEIffdcgYM7bljekB8AGu8FAmYFGbAACgkQbljekB8A
+Gu9LYg//QAH2ro07VsC7l+fkMLwnv+bDzWTnoBhJDWE8hnQxyzhCTIiTkDo5ZiJy
+f/RUlp6KwsKV2BdhabYvJD2b8uo+Y91EIFXGR35IM1O0fRUEVsH1RycnT90Xr8zk
+uNuP7dJPL8wcwuPpRwuDXVCxt9weXfB+1Zaz1G1+FiXKyw8UdYNQt4DlLcqAH4lo
+TIaVsRgScBx9N8+PKLh9mbJDtIowJndxvNPiOzcqvuQnHVFTgHYu8gqE2yAAF1fY
+R8go7qxaNeuV30Cxeg2RySpYc++SMw4wceDPXbxTFow9RqqLgMv5yuatLucahQ0B
+WO/Gl+qU5kHVutBkOf98+vp6XXh2OCFqNX85CrAatC7XA/QxokDfvPceYgPFvNO2
+KNRTpk+KNBRzJ2Jz7Pqkd+i9QR+miOA0zulaZWMi6Gib+ZHVOli+1QW/iaUeJVG5
+Wm4WBGo9gar6gXaIE7nS2LK99ESIEigUfkCoXVg0kxNwTpR/+LwTDPm6nlVh34jg
+LPCrhtZBzYDxV3cQOO446mSuinrtn1qe2CJuumpUrW8kxKVlAp1cqfNboCz2MOHy
+mS6rrE3vFSxnCkaTyeFJWv8MPCYzS4FLM7BKKRf6we6Kf/OpVG2yhvK1H0DAFfqo
+LQxyKL/oXBvwyavfYm00zo60c4os99/mNKVGz7qjn9xvFTGuyEo=
+=r3yP
+-----END PGP SIGNATURE-----