]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #5185 from rgacogne/dnsdist-nmg-add-masks
authorbert hubert <bert.hubert@netherlabs.nl>
Tue, 18 Apr 2017 19:03:02 +0000 (21:03 +0200)
committerGitHub <noreply@github.com>
Tue, 18 Apr 2017 19:03:02 +0000 (21:03 +0200)
dnsdist: Add `NetmaskGroup::addMasks()` to fill a NMG from `exceeds*` results

152 files changed:
build-scripts/build-auth-rpm
build-scripts/build-recursor-rpm
build-scripts/debian-authoritative/control.in
build-scripts/debian-authoritative/rules
build-scripts/debian-dnsdist/control.in
build-scripts/debian-recursor/control.in
build-scripts/debian-recursor/rules
configure.ac
docs/Dockerfile
docs/markdown/authoritative/backend-generic-postgresql.md
docs/markdown/authoritative/backend-ldap.md
docs/markdown/authoritative/howtos.md
docs/markdown/authoritative/performance.md
docs/markdown/authoritative/settings.md
docs/markdown/authoritative/upgrading.md
docs/markdown/recursor/scripting.md
docs/markdown/recursor/settings.md
ext/luawrapper/include/LuaContext.hpp
m4/boost.m4
m4/pdns_check_ldap.m4
m4/pdns_with_postgresql.m4
modules/gpgsqlbackend/Makefile.am
modules/gpgsqlbackend/OBJECTLIBS
modules/ldapbackend/Makefile.am
modules/ldapbackend/OBJECTFILES
modules/ldapbackend/OBJECTLIBS
modules/ldapbackend/exceptions.hh [new file with mode: 0644]
modules/ldapbackend/ldapauthenticator.cc [new file with mode: 0644]
modules/ldapbackend/ldapauthenticator.hh [new file with mode: 0644]
modules/ldapbackend/ldapauthenticator_p.hh [new file with mode: 0644]
modules/ldapbackend/ldapbackend.cc
modules/ldapbackend/ldapbackend.hh
modules/ldapbackend/ldaputils.cc [new file with mode: 0644]
modules/ldapbackend/ldaputils.hh [new file with mode: 0644]
modules/ldapbackend/powerldap.cc
modules/ldapbackend/powerldap.hh
modules/ldapbackend/utils.hh
modules/remotebackend/Makefile.am
modules/remotebackend/test-remotebackend-http.cc
modules/remotebackend/test-remotebackend-json.cc
modules/remotebackend/test-remotebackend-pipe.cc
modules/remotebackend/test-remotebackend-post.cc
modules/remotebackend/test-remotebackend-unix.cc
modules/remotebackend/test-remotebackend-zeromq.cc
modules/remotebackend/test-remotebackend.cc
modules/tinydnsbackend/data
modules/tinydnsbackend/data.cdb
modules/tinydnsbackend/generate-data.sh
pdns/Makefile.am
pdns/README-dnsdist.md
pdns/ascii.hh [new file with mode: 0644]
pdns/auth-caches.cc [new file with mode: 0644]
pdns/auth-caches.hh [new file with mode: 0644]
pdns/auth-packetcache.cc [new file with mode: 0644]
pdns/auth-packetcache.hh [new file with mode: 0644]
pdns/auth-querycache.cc [new file with mode: 0644]
pdns/auth-querycache.hh [new file with mode: 0644]
pdns/cachecleaner.hh
pdns/common_startup.cc
pdns/common_startup.hh
pdns/dnsdist-cache.cc
pdns/dnsdist-cache.hh
pdns/dnsdist-console.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-lua2.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/ascii.hh [new symlink]
pdns/dnsdistdist/m4/dnsdist_enable_dnscrypt.m4
pdns/dnsname.cc
pdns/dnsname.hh
pdns/dnspacket.cc
pdns/dnspacket.hh
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnsproxy.cc
pdns/dnsrecords.cc
pdns/dnswriter.cc
pdns/dnswriter.hh
pdns/dynhandler.cc
pdns/ednsoptions.cc
pdns/ednsoptions.hh
pdns/filterpo.cc
pdns/iputils.cc
pdns/iputils.hh
pdns/lock.hh
pdns/lua-recursor4.cc
pdns/lua-recursor4.hh
pdns/mastercommunicator.cc
pdns/misc.hh
pdns/packetcache.cc [deleted file]
pdns/packetcache.hh
pdns/packethandler.cc
pdns/packethandler.hh
pdns/pdns_recursor.cc
pdns/pdnsutil.cc
pdns/rec_channel_rec.cc
pdns/recpacketcache.cc
pdns/recpacketcache.hh
pdns/recursordist/.gitignore
pdns/recursordist/Makefile.am
pdns/recursordist/ascii.hh [new symlink]
pdns/recursordist/configure.ac
pdns/recursordist/contrib/syncres.dot [new file with mode: 0644]
pdns/recursordist/ecs.cc
pdns/recursordist/ednscookies.cc [new symlink]
pdns/recursordist/ednscookies.hh [new symlink]
pdns/recursordist/negcache.cc [new file with mode: 0644]
pdns/recursordist/negcache.hh [new file with mode: 0644]
pdns/recursordist/packetcache.hh [new symlink]
pdns/recursordist/test-ednsoptions_cc.cc [new file with mode: 0644]
pdns/recursordist/test-negcache_cc.cc [new file with mode: 0644]
pdns/recursordist/test-syncres_cc.cc [new file with mode: 0644]
pdns/remote_logger.hh
pdns/rfc2136handler.cc
pdns/root-addresses.hh
pdns/root-dnssec.hh
pdns/secpoll-recursor.cc
pdns/signingpipe.cc
pdns/snmp-agent.hh
pdns/stubresolver.cc
pdns/stubresolver.hh
pdns/syncres.cc
pdns/syncres.hh
pdns/tcpreceiver.cc
pdns/test-dnsname_cc.cc
pdns/test-lock_hh.cc [new file with mode: 0644]
pdns/test-packetcache_cc.cc
pdns/test-recpacketcache_cc.cc
pdns/test-zoneparser_tng_cc.cc
pdns/testrunner.cc
pdns/ueberbackend.cc
pdns/validate-recursor.cc
pdns/ws-auth.cc
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Caching.py
regression-tests.dnsdist/test_DynBlocks.py
regression-tests.nobackend/counters/expected_result
regression-tests.nobackend/tinydns-data-check/expected_result
regression-tests/backends/gsql-common
regression-tests/tests/1dyndns-check-soa-update/description
regression-tests/tests/1dyndns-update-replace-a-host/description
regression-tests/tests/cname-to-apex/description
regression-tests/tests/ent-axfr/expected_result
regression-tests/tests/ent-axfr/expected_result.nsec3
regression-tests/tests/ent-axfr/expected_result.nsec3-optout
regression-tests/tests/secure-delegation/description
regression-tests/zones/test.com

index 7bfcaed91a3faf5e865d999c5e8f072ef85b9f8c..0971ad3f27898ae8dfe39c92ac039330ac2cdad2 100755 (executable)
@@ -285,6 +285,7 @@ Requires(postun): /sbin/service
 
 BuildRequires: boost-devel
 BuildRequires: lua-devel
+BuildRequires: libsodium-devel
 BuildRequires: bison
 Provides: powerdns = %{version}-%{release}
 
@@ -394,6 +395,7 @@ export CPPFLAGS="-DLDAP_DEPRECATED"
        --with-lua \
        --with-dynmodules='bind %{backends} random' \
        --enable-tools \
+       --enable-libsodium \
        --without-protobuf \
        --enable-remotebackend-http \
        --enable-unit-tests
@@ -543,6 +545,7 @@ BuildRequires: systemd-units
 BuildRequires: systemd-devel
 BuildRequires: boost-devel
 BuildRequires: lua-devel
+BuildRequires: libsodium-devel
 BuildRequires: bison
 BuildRequires: openssl-devel
 BuildRequires: protobuf-devel
@@ -687,6 +690,7 @@ export CPPFLAGS="-DLDAP_DEPRECATED"
        --with-lua \
        --with-dynmodules='%{backends} random' \
        --enable-tools \
+       --enable-libsodium \
        --enable-unit-tests \
        --enable-systemd
 
index 8b27f5c5ccf3b3242e783f10ab6f3875a404f548..b1d42d681c9be6312ecc50ffc54582d9c94ee555 100755 (executable)
@@ -122,6 +122,7 @@ Source1: pdns-recursor.init
 Provides: powerdns-recursor = %{version}-%{release}
 BuildRequires: boost148-devel
 BuildRequires: lua-devel
+BuildRequires: libsodium-devel
 #BuildRequires: protobuf-devel
 #BuildRequires: protobuf-compiler
 
@@ -146,6 +147,7 @@ package if you need a dns cache for your network.
        --disable-silent-rules \
        --with-protobuf \
        --enable-unit-tests \
+       --enable-libsodium \
        --with-boost=/usr/include/boost148 LIBRARY_PATH=/usr/lib64/boost148
 
 make %{?_smp_mflags} LIBRARY_PATH=/usr/lib64/boost148
@@ -214,6 +216,7 @@ BuildRequires: boost-devel
 BuildRequires: lua-devel
 BuildRequires: systemd-units
 BuildRequires: systemd-devel
+BuildRequires: libsodium-devel
 BuildRequires: hostname
 BuildRequires: protobuf-devel
 BuildRequires: protobuf-compiler
@@ -240,6 +243,7 @@ package if you need a dns cache for your network.
        --disable-silent-rules \
        --enable-unit-tests \
        --with-protobuf \
+       --enable-libsodium \
        --enable-systemd
 
 make %{?_smp_mflags}
index f9ec15bf0d70805816d02978fd6fd32e123058c2..8f58a18d9afe946d906887709651d6d102b17af9 100644 (file)
@@ -4,7 +4,7 @@ Priority: extra
 Standards-Version: 3.9.8
 Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
 Origin: PowerDNS
-Build-Depends: debhelper (>= 9~), dh-autoreconf, dh-systemd, po-debconf, libtool, flex, bison, libmysqlclient-dev, libpq-dev, libssl-dev, libgdbm-dev, libldap2-dev, libsqlite3-dev, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, libboost-program-options-dev, libboost-test-dev, autotools-dev, automake, autoconf, liblua5.2-dev, pkg-config, ragel, libgmp-dev, libbotan1.10-dev, libcurl4-openssl-dev, libzmq-dev, libyaml-cpp-dev (>= 0.5), libgeoip-dev, libopendbx1-dev, libcdb-dev, unixodbc-dev (>= 2.3.1), libprotobuf-dev, protobuf-compiler @LIBSYSTEMDDEV@
+Build-Depends: debhelper (>= 9~), dh-autoreconf, dh-systemd, po-debconf, libtool, flex, bison, libmysqlclient-dev, libpq-dev, libssl-dev, libgdbm-dev, libldap2-dev, libsqlite3-dev, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, libboost-program-options-dev, libboost-test-dev, autotools-dev, automake, autoconf, liblua5.2-dev, pkg-config, ragel, libgmp-dev, libbotan1.10-dev, libcurl4-openssl-dev, libzmq-dev, libyaml-cpp-dev (>= 0.5), libgeoip-dev, libopendbx1-dev, libcdb-dev, unixodbc-dev (>= 2.3.1), libprotobuf-dev, protobuf-compiler @LIBSYSTEMDDEV@ @LIBSODIUMDEV@
 Homepage: http://www.powerdns.com/
 
 Package: pdns-server
index 4b37fbb7a4503d8b6e65f9b20f1af5a3917e8ea1..70a27d1c19bd7b82e5617254f865c85a3ddfd40a 100755 (executable)
@@ -9,6 +9,9 @@ ENABLE_SYSTEMD := --enable-systemd --with-systemd=/lib/systemd/system
 LIBSYSTEMD_DEV := , libsystemd-dev
 DEBHELPER_WITH_SYSTEMD := --with systemd
 
+ENABLE_LIBSODIUM := --enable-libsodium
+LIBSODIUM_DEV := , libsodium-dev
+
 # $(ID) and $(VERSION_ID) come from the environment, source this from /etc/os-release
 ifeq ($(ID), ubuntu)
   ifeq ($(VERSION_ID), 14.04)
@@ -16,11 +19,16 @@ ifeq ($(ID), ubuntu)
     ENABLE_SYSTEMD=
     LIBSYSTEMD_DEV=
     DEBHELPER_WITH_SYSTEMD=
+
+    # Also disable libsodium
+    ENABLE_LIBSODIUM=
+    LIBSODIUM_DEV=
   endif
 endif
 
 debian/control: debian/control.in
-       sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" $< > $@
+       sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" \
+           -e "s!@LIBSODIUMDEV@!$(LIBSODIUM_DEV)!" $< > $@
 
 # Use new build system
 %:
@@ -42,11 +50,11 @@ override_dh_auto_configure:
                --libexecdir='$${prefix}/lib' \
                --with-dynmodules="$(backends)" \
                --with-modules="" \
-               --with-pgsql-includes=`pg_config --includedir` \
                --enable-botan1.10 \
                --enable-tools \
                --enable-unit-tests \
-               $(ENABLE_SYSTEMD)
+               $(ENABLE_SYSTEMD) \
+               $(ENABLE_LIBSODIUM)
 
 # pdns-server has a debug package
 override_dh_strip:
index 9b015f0dbdcbbcb173ba6afd7b61fe2bf49ad4e9..58986482348804fc835dfa2c8bbab751a479cd3a 100644 (file)
@@ -3,7 +3,7 @@ Section: net
 Priority: optional
 Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
 Origin: PowerDNS
-Build-Depends: debhelper (>= 9), dh-autoreconf, dh-systemd (>= 1.5), libboost-dev, libedit-dev, liblua5.2-dev, protobuf-compiler, libprotobuf-def, pkg-config @LIBRE2DEV@ @LIBSODIUMDEV@ @LIBSYSTEMDDEV@
+Build-Depends: debhelper (>= 9), dh-autoreconf, dh-systemd (>= 1.5), libboost-dev, libedit-dev, liblua5.2-dev, protobuf-compiler, libprotobuf-dev, pkg-config @LIBRE2DEV@ @LIBSODIUMDEV@ @LIBSYSTEMDDEV@
 Standards-Version: 3.9.7
 Homepage: http://dnsdist.org
 
index a7e2b4e443d0929835631df6d9575c7aeaeb25aa..d1dbaee2500ddf6f83a716096a82617e377e250d 100644 (file)
@@ -4,7 +4,7 @@ Priority: extra
 Standards-Version: 3.9.6
 Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
 Origin: PowerDNS
-Build-Depends: debhelper (>= 9~), dh-systemd, quilt, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, liblua5.2-dev, libprotobuf-dev, protobuf-compiler, pkg-config @LIBSYSTEMDDEV@
+Build-Depends: debhelper (>= 9~), dh-systemd, quilt, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, liblua5.2-dev, libprotobuf-dev, protobuf-compiler, pkg-config @LIBSYSTEMDDEV@ @LIBSODIUMDEV@
 Homepage: http://www.powerdns.com/
 
 Package: pdns-recursor
index 983fea9fa0a19b9f535375b7ee0f0ebd20e5adeb..6128fbaa2d9fd17f97a647cf9f11d45e915e90a3 100755 (executable)
@@ -11,6 +11,9 @@ ENABLE_SYSTEMD := --enable-systemd --with-systemd=/lib/systemd/system
 LIBSYSTEMD_DEV := , libsystemd-dev
 DEBHELPER_WITH_SYSTEMD := --with systemd
 
+ENABLE_LIBSODIUM := --enable-libsodium
+LIBSODIUM_DEV := , libsodium-dev
+
 # $(ID) and $(VERSION_ID) come from the environment, source this from /etc/os-release
 ifeq ($(ID), ubuntu)
   ifeq ($(VERSION_ID), 14.04)
@@ -18,11 +21,16 @@ ifeq ($(ID), ubuntu)
     ENABLE_SYSTEMD=
     LIBSYSTEMD_DEV=
     DEBHELPER_WITH_SYSTEMD=
+
+    # Also disable libsodium
+    ENABLE_LIBSODIUM=
+    LIBSODIUM_DEV=
   endif
 endif
 
 debian/control: debian/control.in
-       sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" $< > $@
+       sed -e "s!@LIBSYSTEMDDEV@!$(LIBSYSTEMD_DEV)!" \
+           -e "s!@LIBSODIUMDEV@!$(LIBSODIUM_DEV)!" $< > $@
 
 # Use new build system
 %:
@@ -43,7 +51,8 @@ override_dh_auto_configure:
                --libexecdir='$${prefix}/lib' \
                --with-lua \
                --with-protobuf=yes \
-               $(ENABLE_SYSTEMD)
+               $(ENABLE_SYSTEMD) \
+               $(ENABLE_LIBSODIUM)
 
 override_dh_auto_install:
        ./pdns_recursor --config | sed \
index a43086bbcffd1e39651ebb45ba028abcf0b939a5..0956e2fb1888c1c34ab6f4d9f27fcbdd49a4e82f 100644 (file)
@@ -348,6 +348,10 @@ AS_IF([test "x$libcrypto_ecdsa" == "xyes"],
   [AC_MSG_NOTICE([OpenSSL ecdsa: yes])],
   [AC_MSG_NOTICE([OpenSSL ecdsa: no])]
 )
+AS_IF([test "x$LIBSODIUM_LIBS" != "x"],
+  [AC_MSG_NOTICE([libsodium ed25519: yes])],
+  [AC_MSG_NOTICE([libsodium ed25519: no])]
+)
 AS_IF([test "x$needsqlite3" != "x"],
   [AC_MSG_NOTICE([SQLite3: yes])],
   [AC_MSG_NOTICE([SQLite3: no])]
index e990b2c564d8a1ff099e74656b7bb4955c476210..410e6fbd6d2d24289d7ed54eb494240cc42df590 100644 (file)
@@ -2,8 +2,7 @@
 #
 # example usage:
 # docs/$ docker build -t pdns-mkdocs .
-# docs/$ cd ../.. # to pdns checkout root
-# pdns/$ docker run  -t -i -v $(pwd):/pdns pdns-mkdocs bash
+# docs/$ docker run  -t -i -v $(pwd)/..:/pdns pdns-mkdocs bash
 # root@07d38c7f88a5:/# useradd -u 1000 peter
 # root@07d38c7f88a5:/# su - peter
 # No directory, logging in with HOME=/
index ec9181024a555d871be0a117cb064b709ac90e56..5757eb24c8e2b0a979fbd92dfdb93b997a13c008 100644 (file)
@@ -10,8 +10,8 @@
 |DNSSEC|Yes (set `gpgsql-dnssec`)|
 |Disabled data|Yes (v3.4.0 and up)|
 |Comments|Yes (v3.4.0 and up)|
-|Module name | pgsql|
-|Launch name| pgsql|
+|Module name | gpgsql|
+|Launch name| gpgsql|
 
 This PostgreSQL backend is based on the [generic SQL backend](backend-generic-sql.md).
 The default setup conforms to the schema at the bottom of this page, note that
index 9543fa00bded213114f22592669b78d6a159489b..2bbd005287ee8e50a2ba84cc633b77cabc087c39 100644 (file)
@@ -58,20 +58,31 @@ There can be multiple LDAP URIs specified for load balancing and high availabili
 In case the used LDAP client library doesn't support LDAP URIs as connection parameter, use plain host names or IP addresses instead (both may optionally be followed by a colon and the port).
 
 ## `ldap-starttls`
-(default "no") : Use TLS encrypted connections to the LDAP server.
-This is only allowed if `ldap-host` is a `ldap://` URI or a host name / IP address.
+(default "no") : Use TLS encrypted connections to the LDAP server. This is only allowed if ldap-host is a <ldap://> URI or a host name / IP address.
 
-## `ldap-basedn`
-(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information.
-The retrieval of attributes is limited to this subtree.
-This option must be set to the path according to the layout of the LDAP tree, e.g. `ou=hosts,o=example,c=net` is the DN to my objects containing the DNS information.
+## `ldap-timeout`
+(default: "5") : The number of seconds to wait for LDAP operations to complete.
+
+## `ldap-reconnect-attempts`
+(default: "5") : The number of attempts to make to re-establish a lost connection to the LDAP server.
+
+## `ldap-authmethod`
+(default: "simple") : How to authenticate to the LDAP server. Actually only two methods are supported: "simple", which uses the classical DN / password, or "gssapi", which requires a Kerberos keytab.
 
 ## `ldap-binddn`
-(default "") : Path to the object to authenticate against.
-Should only be used if the LDAP server doesn't support anonymous binds.
+(default "") : Path to the object to authenticate against. Should only be used, if the LDAP server doesn't support anonymous binds and with the "simple" authmethod.
 
 ## `ldap-secret`
-(default "") : Password for authentication against the object specified by `ldap-binddn`.
+(default "") : Password for authentication against the object specified by ldap-binddn. Only used when "authmethod" is "simple".
+
+## `ldap-krb5-keytab`
+(default: "") : Full path to the keytab file to use to authenticate. This is only used when "authmethod" is set to "gssapi". The keytab must, ideally, contain only one principal (or to put it otherwise, only the first principal found in the keytab will be used).
+
+## `ldap-krb5-ccache`
+(default: "") : Full path to the Kerberos credential cache file to use. Actually only files are supported, and the "FILE:" prefix must not be set. The PowerDNS process must be able to write to this file and it *must* be the only one able to read it.
+
+## `ldap-basedn`
+(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information. The retrieval of attributes is limited to this subtree. This option must be set to the path according to the layout of your LDAP tree, e.g. ou=hosts,o=linuxnetworks,c=de is the DN to my objects containing the DNS information.
 
 ## `ldap-method`
 (default "simple") :
@@ -86,6 +97,50 @@ Should only be used if the LDAP server doesn't support anonymous binds.
 ## `ldap-filter-lookup`
 (default "(:target:)" ) : LDAP filter for limiting IP or name lookups, e.g. (&(:target:)(active=yes)) for returning only entries whose attribute "active" is set to "yes".
 
+# Master Mode
+
+Schema update
+-------------
+
+First off adding master support to the LDAP backend needs
+a schema update. This is required as some metadata must
+be stored by PowerDNS, such as the last successful transfer
+to slaves. The new schema is available in
+schema/pdns-domaininfo.schema.
+
+Once the schema is loaded the zones for which you want to
+be a master must be modified. The dn of the SOA record
+*must* have the object class `PdnsDomain`, and thus the
+`PdnsDomainId` attribute. This attribute is an integer
+that *must* be unique across all zones served by the
+backend. Furthermore the `PdnsDomainType` must be equal
+to 'master' (lower case).
+
+Example
+-------
+
+Here is an example LDIF of a zone that's ready for master
+operation (assuming the 'tree' style):
+
+```
+dn: dc=example,dc=com,ou=dns,dc=mycompany,dc=com
+objectClass: top
+objectClass: domainRelatedObject
+objectClass: dNSDomain2
+objectClass: PdnsDomain
+dc: example
+associatedDomain: example.com
+nSRecord: ns1.example.com
+sOARecord: ns1.example.com. hostmaster.example.com. 2013031101 1800 600 1209600 600
+mXRecord: 10 mx1.example.com
+PdnsDomainId: 1
+PdnsDomainType: master
+PdnsDomainMaster: 192.168.0.2
+```
+
+You should have one attribute `PdnsDomainMaster` per
+master serving this zone.
+
 # Example
 ## Tree design
 The DNS LDAP tree should be designed carefully to prevent mistakes, which are hard to correct afterwards.
index 4c2bb973bb3fc10f9b86d96b9317d392c92b182d..e956e846b6fa895c8ceeac7d68ea52141bc5ca4b 100644 (file)
@@ -178,13 +178,15 @@ If you have multiple IP addresses on the internet on one machine, UNIX often sen
 # Using ALIAS records
 The ALIAS record provides a way to have CNAME-like behaviour on the zone apex.
 
-In order to correctly serve ALIAS records, set the [`resolver`](settings.md#resolver)
-setting to an existing resolver: 
+In order to correctly serve ALIAS records, set the [`resolver`](settings.md#resolver) setting to an existing resolver and enable [`expand-alias`](settings.md#expand-alias):
 
 ```
 resolver=[::1]:5300
+expand-alias=yes
 ```
 
+**note**: If `resolver` is unset, ALIAS expension is disabled!
+
 and add the ALIAS record to your zone apex. e.g.:
 
 ```
@@ -213,6 +215,9 @@ Set `outgoing-axfr-expand-alias` to 'yes' if your slaves don't understand ALIAS
 or should not look up the addresses themselves. Note that slaves will not
 automatically follow changes in those A/AAAA records unless you AXFR regularly.
 
+**note:** The `expand-alias` setting does not exist in PowerDNS Authoritative Server 4.0.x.
+Hence, ALIAS records are always expanded on a direct A or AAAA query.
+
 ## ALIAS and DNSSEC
 Starting with the PowerDNS Authoritative Server 4.0.0, DNSSEC 'washing' of ALIAS
 records is supported on AXFR (**not** on live-signing). Set `outgoing-axfr-expand-alias`
index 3284024b3472280e2dedd46b3838043e47d54ec4..8906bb377ff512b951485f2841e44df02b8cbecc 100644 (file)
@@ -12,7 +12,7 @@ Different backends will have different characteristics - some will want to have
 
 This is done with the [`distributor-threads`](settings.md#distributor-threads) setting which says how many distributors will be opened for each receiver thread. Of special importance is the choice between 1 or more backends. In case of only 1 thread, PowerDNS reverts to unthreaded operation which may be a lot faster, depending on your operating system and architecture.
 
-Another very important setting is [`cache-ttl`](settings.md#cache-ttl). PowerDNS caches entire packets it sends out so as to save the time to query backends to assemble all data. The default setting of 20 seconds may be low for high traffic sites, a value of 60 seconds rarely leads to problems. Please be aware that if any TTL in the answer is shorter than this setting, the packet cache will respect the answer's shortest TTL.
+Other very important settings are [`cache-ttl`](settings.md#cache-ttl). PowerDNS caches entire packets it sends out so as to save the time to query backends to assemble all data. The default setting of 20 seconds may be low for high traffic sites, a value of 60 seconds rarely leads to problems. Please be aware that if any TTL in the answer is shorter than this setting, the packet cache will respect the answer's shortest TTL.
 
 Some PowerDNS operators set cache-ttl to many hours or even days, and use [`pdns_control`](running.md#pdns_control)` purge` to selectively or globally notify PowerDNS of changes made in the backend. Also look at the [Query Cache](#query-cache) described in this chapter. It may materially improve your performance.
 
@@ -21,16 +21,16 @@ To determine if PowerDNS is unable to keep up with packets, determine the value
 Logging truly kills performance as answering a question from the cache is an order of magnitude less work than logging a line about it. Busy sites will prefer to turn [`log-dns-details`](settings.md#log-dns-details) off.
 
 # Packet Cache
-PowerDNS by default uses the 'Packet Cache' to recognise identical questions and supply them with identical answers, without any further processing. The default time to live is 10 seconds. It has been observed that the utility of the packet cache increases with the load on your nameserver.
+PowerDNS by default uses the 'Packet Cache' to recognise identical questions and supply them with identical answers, without any further processing. The default time to live is 20 seconds and can be changed by setting `cache-ttl`. It has been observed that the utility of the packet cache increases with the load on your nameserver.
 
-Not all backends may benefit from the packetcache. If your backend is memory based and does not lead to context switches, the packetcache may actually hurt performance.
+Not all backends may benefit from the packet cache. If your backend is memory based and does not lead to context switches, the packet cache may actually hurt performance.
 
-The size of the packetcache can be observed with `/etc/init.d/pdns show packetcache-size`
+The maximum size of the packet cache is controlled by the `max-packet-cache-entries` entries since 4.1. Before that both the query cache and the packet cache used the `max-cache-entries` setting.
 
 # Query Cache
 Besides entire packets, PowerDNS can also cache individual backend queries. Each DNS query leads to a number of backend queries, the most obvious additional backend query is the check for a possible CNAME. So, when a query comes in for the 'A' record for 'www.powerdns.com', PowerDNS must first check for a CNAME for 'www.powerdns.com'.
 
-The Query Cache caches these backend queries, many of which are quite repetitive. PowerDNS only caches queries with no answer, or with exactly one. In the future this may be expanded but this lightweight solution is very simple and therefore fast.
+The Query Cache caches these backend queries, many of which are quite repetitive. The maximum number of entries in the cache is controlled by the `max-cache-entries` setting. Before 4.1 this setting also controls the maximum number of entries in the packet cache.
 
 Most gain is made from caching negative entries, ie, queries that have no answer. As these take little memory to store and are typically not a real problem in terms of speed-of-propagation, the default TTL for negative queries is a rather high 60 seconds.
 
@@ -49,6 +49,8 @@ daemon.
 * `corrupt-packets`: Number of corrupt packets received
 * `deferred-cache-inserts`: Number of cache inserts that were deferred because of maintenance
 * `deferred-cache-lookup`: Number of cache lookups that were deferred because of maintenance
+* `deferred-packetcache-inserts`: Number of packet cache inserts that were deferred because of maintenance
+* `deferred-packetcache-lookup`: Number of packet cache lookups that were deferred because of maintenance
 * `dnsupdate-answers`: Number of DNS update packets successfully answered
 * `dnsupdate-changes`: Total number of changes to records from DNS update
 * `dnsupdate-queries`: Number of DNS update packets received
@@ -64,6 +66,7 @@ daemon.
 * `qsize-q`: Number of packets waiting for database attention
 * `query-cache-hit`: Number of hits on the [query cache](performance.md#query-cache)
 * `query-cache-miss`: Number of misses on the [query cache](performance.md#query-cache)
+* `query-cache-size`: Number of entries in the query cache
 * `rd-queries`: Number of packets sent by clients requesting recursion (regardless of if we'll be providing them with recursion). Since 3.4.0.
 * `recursing-answers`: Number of packets we supplied an answer to after recursive processing
 * `recursing-questions`: Number of packets we performed recursive processing for
index 732f733fd4023bbe09a9fb1783d0127c4e1606d7..a1bb83ce85e022a864fc6c239b336442d28f1199 100644 (file)
@@ -342,6 +342,19 @@ Enables EDNS subnet processing, for backends that support it.
 
 Entropy source file to use.
 
+## `expand-alias`
+* Boolean
+* Default: no
+* Since: 4.1.0
+
+If this is enabled, ALIAS records are expanded (synthesised to their A/AAAA).
+
+If this is disabled (the default), ALIAS records will not expanded and the server will will return NODATA for A/AAAA queries for such names.
+
+**note**: [`resolver`](#resolver) must also be set for ALIAS expansion to work!
+
+**note**: In PowerDNS Authoritative Server 4.0.x, this setting did not exist and ALIAS was always expanded.
+
 ## `forward-dnsupdate`
 * Boolean
 * Default: no
@@ -479,8 +492,9 @@ Turn on master support. See ["Modes of operation"](modes-of-operation.md#master-
 * Integer
 * Default: 1000000
 
-Maximum number of cache entries. 1 million (the default) will generally suffice
-for most installations.
+Maximum number of entries in the query cache. 1 million (the default) will generally suffice
+for most installations. Starting with 4.1, the packet and query caches are distinct so you might
+also want to see `max-packet-cache-entries`.
 
 ## `max-ent-entries`
 * Integer
@@ -495,6 +509,14 @@ measure to avoid database explosion due to long names.
 
 Limit the number of NSEC3 hash iterations
 
+## `max-packet-cache-entries`
+* Integer
+* Default: 1000000
+
+Maximum number of entries in the packet cache. 1 million (the default) will generally suffice
+for most installations. This setting has been introduced in 4.1, previous used the `max-cache-entries`
+setting for both the packet and query caches.
+
 ## `max-queue-length`
 * Integer
 * Default: 5000
@@ -705,10 +727,11 @@ If set, recursive queries will be handed to the recursor specified here. See
 ["Recursion"](recursion.md).
 
 ## `resolver`
-* IP Address
+* IP Addresses with optional port, separated by commas
 * Added in: 4.1.0
 
-Use this resolver for ALIAS and the internal stub resolver.
+Use these resolver addresses for ALIAS and the internal stub resolver.
+If this is not set, `/etc/resolv.conf` is parsed for upstream resolvers.
 
 ## `retrieval-threads`
 * Integer
@@ -812,6 +835,14 @@ Limit TCP control to a specific client range.
 
 Password for TCP control.
 
+## `tcp-fast-open`
+* Integer
+* Default: 0 (Disabled)
+* Available since: 4.1
+
+Enable TCP Fast Open support, if available, on the listening sockets. The numerical
+value supplied is used as the queue size, 0 meaning disabled.
+
 ## `tcp-idle-timeout`
 * Integer
 * Default: 5
index 69a5da4ca3e21dcc3ef2250d3314ae9124b288cf..063f57de94673e82b96f1f7909608d7f4376ea6a 100644 (file)
@@ -2,6 +2,18 @@ Before proceeding, it is advised to check the release notes for your PowerDNS ve
 
 Please upgrade to the PowerDNS Authoritative Server 4.0.0 from 3.4.2+. See the [3.X](https://doc.powerdns.com/3/authoritative/upgrading/) upgrade notes if your version is older than 3.4.2.
 
+# 4.0.X to 4.1.0
+
+## Changed options
+
+### Changed defaults
+
+## Other changes
+
+The `--with-pgsql`, `--with-pgsql-libs`, `--with-pgsql-includes` and `--with-pgsql-config` `configure` options have been deprecated.
+`configure` now attempts to find the Postgresql client libraries via `pkg-config`, falling back to detecting `pg_config`.
+Use `--with-pg-config` to specify a path to a non-default `pg_config` if you have Postgresql installed in a non-default location.
+
 # 4.0.X to 4.0.2
 
 ## Changed options
index 12cb5ec9c0be7bbde2091480bf4e12cfdc4cc8c9..ddc2f4cfb708fcfbd099918079d7aceed7af7820 100644 (file)
@@ -98,8 +98,8 @@ The DNSQuestion object contains at least the following fields:
      * policyAction: The action taken by the engine
      * policyCustom: The CNAME content for the `pdns.policyactions.Custom` response, a string
      * policyTTL: The TTL in seconds for the `pdns.policyactions.Custom` response
-* wantsRPZ - A boolean that indicates the use of the Policy Engine, can be set to `false` in `preresolve` to disable RPZ for this query
-* data - a table that is persistent throughout the lifetime of the `dq` object and can be used to store custom data. All keys and values in the table must be of type `string`.
+* wantsRPZ - A boolean that indicates the use of the Policy Engine, can be set to `false` in `prerpz` to disable RPZ for this query
+* data - a Lua object reference that is persistent throughout the lifetime of the `dq` object for a single query. It can be used to store custom data. Most scripts initialise this to an empty table early on so they can store multiple items.
 
 It also supports the following methods:
 
@@ -107,11 +107,7 @@ It also supports the following methods:
   the answer too, which defaults to the name of the question
 * `addPolicyTag(tag)`: add a policy tag.
 * `discardPolicy(policyname)`: skip the filtering policy (for example RPZ) named `policyname` for this query. This is mostly useful in the `prerpz` hook.
-* `getDH()` - Returns the DNS Header of the query or nil. A DNS header offers the following methods:
-     * `getRD()`, `getAA()`, `getAD()`, `getCD()`, `getTC()`: query these bits from the DNS Header
-     * `getRCODE()`: get the RCODE of the query
-     * `getOPCODE()`: get the OPCODE of the query
-     * `getID()`: get the ID of the query
+* `getDH()` - Returns the DNS Header of the query or nil.
 * `getPolicyTags()`: get the current policy tags as a table of strings.
 * `getRecords()`: get a table of DNS Records in this DNS Question (or answer by now)
 * `setPolicyTags(tags)`: update the policy tags, taking a table of strings.
@@ -125,6 +121,12 @@ It also supports the following methods:
 * `getPolicyTags()`: Get a list the policyTags for this message
 * `setPolicyTags(tags)`: Set the policyTags for this message to `tags` (a list)
 
+A DNS header as returned by `getDH()` offers the following methods:
+* `getRD()`, `getAA()`, `getAD()`, `getCD()`, `getTC()`: query these bits from the DNS Header
+* `getRCODE()`: get the RCODE of the query
+* `getOPCODE()`: get the OPCODE of the query
+* `getID()`: get the ID of the query
+
 ## `function ipfilter ( remoteip, localip, dh )`
 This hook gets queried immediately after consulting the packet cache, but before
 parsing the DNS packet. If this hook returns something else than false, the packet is dropped. 
@@ -151,7 +153,7 @@ end
 This hook does not get the full DNSQuestion object, since filling out the fields
 would require packet parsing, which is what we are trying to prevent with `ipfilter`.
 
-### `function gettag(remote, ednssubnet, local, qname, qtype)`
+### `function gettag(remote, ednssubnet, local, qname, qtype, ednsoptions)`
 The `gettag` function is invoked when the Recursor attempts to discover in which
 packetcache an answer is available.
 
@@ -168,6 +170,11 @@ e.g. been filtered for certain IPs (this logic should be implemented in the
 setting dq.variable to `true`. In the latter case, repeated queries will pass
 through the entire Lua script.
 
+`ednsoptions` is a table whose keys are EDNS option codes and values are
+`EDNSOptionView` objects, with the EDNS option content size in the `size` member
+and the content accessible as a NULL-safe string object via `getContent()`.
+This table is empty unless the `gettag-needs-edns-options` parameter is set.
+
 ### `function prerpz(dq)`
 
 This hook is called before any filtering policy have been applied, making it
@@ -184,6 +191,7 @@ function prerpz(dq)
   if dq.qname:equal('example.com') then
     dq:discardPolicy('malware')
   end
+  return false
 end
 ```
 
index bcffc805eab4929d5cbf0ae4977f92f55d8fe50d..c25162599876dd99abafcb1e0ae0c7db8575da1b 100644 (file)
@@ -360,6 +360,14 @@ forward queries to other recursive servers.
 
 The DNSSEC notes from [`forward-zones`](#forward-zones) apply here as well.
 
+## `gettag-needs-edns-options`
+* Boolean
+* Default: no
+* Available since: 4.1.0
+
+If set, EDNS options in incoming queries are extracted and passed to the `gettag()`
+hook in the `ednsoptions` table.
+
 ## `hint-file`
 * Path
 
@@ -877,6 +885,14 @@ Size of the stack per thread.
 Number of entries in the remotes ringbuffer, which keeps statistics on who is
 querying your server. Can be read out using `rec_control top-remotes`.
 
+## `tcp-fast-open`
+* Integer
+* Default: 0 (Disabled)
+* Available since: 4.1
+
+Enable TCP Fast Open support, if available, on the listening sockets. The numerical
+value supplied is used as the queue size, 0 meaning disabled.
+
 ## `threads`
 * Integer
 * Default: 2
index 9cf72ca2d7850fe43876720d974f1adc730f4bd0..9416c906c3539cce6e7b04172d8b56418f957a35 100644 (file)
@@ -54,7 +54,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <boost/type_traits.hpp>
 #include <lua.hpp>
 
-#ifdef _MSC_VER
+#if defined(_MSC_VER) && _MSC_VER < 1900
 #   include "misc/exception.hpp"
 #endif
 
@@ -188,6 +188,17 @@ public:
     template<typename TFunctionType>
     class LuaFunctionCaller;
 
+    /**
+     * Opaque type that identifies a Lua object
+     */
+    struct LuaObject {
+        LuaObject() = default;
+        LuaObject(lua_State* state, int index=-1) {
+            this->objectInRegistry = std::make_shared<LuaContext::ValueInRegistry>(state, index);
+        }
+        std::shared_ptr<LuaContext::ValueInRegistry> objectInRegistry;
+    };
+
     /**
      * Opaque type that identifies a Lua thread
      */
@@ -388,7 +399,7 @@ public:
      * @tparam TType Type whose function belongs to
      */
     template<typename TType>
-    void unregisterFunction(const std::string& functionName)
+    void unregisterFunction(const std::string& /*functionName*/)
     {
         lua_pushlightuserdata(mState, const_cast<std::type_info*>(&typeid(TType)));
         lua_pushnil(mState);
@@ -546,7 +557,7 @@ public:
         result.threadInRegistry = std::unique_ptr<ValueInRegistry>(new ValueInRegistry(mState));
         lua_pop(mState, 1);
 
-        return std::move(result);
+        return result;
     }
 
     /**
@@ -688,7 +699,7 @@ private:
         PushedObject& operator=(PushedObject&& other) { std::swap(state, other.state); std::swap(num, other.num); return *this; }
         PushedObject(PushedObject&& other) : state(other.state), num(other.num) { other.num = 0; }
 
-        PushedObject operator+(PushedObject&& other) && { PushedObject obj(state, num + other.num); num = 0; other.num = 0; return std::move(obj); }
+        PushedObject operator+(PushedObject&& other) && { PushedObject obj(state, num + other.num); num = 0; other.num = 0; return obj; }
         void operator+=(PushedObject&& other) { assert(state == other.state); num += other.num; other.num = 0; }
         
         auto getState() const -> lua_State* { return state; }
@@ -1245,7 +1256,7 @@ private:
             std::array<char,512>    buffer;
 
             // read function ; "data" must be an instance of Reader
-            static const char* read(lua_State* l, void* data, size_t* size) {
+            static const char* read(lua_State* /*l*/, void* data, size_t* size) {
                 assert(size != nullptr);
                 assert(data != nullptr);
                 Reader& me = *static_cast<Reader*>(data);
@@ -1310,7 +1321,7 @@ private:
             RealReturnType;
         
         // we push the parameters on the stack
-        auto inArguments = Pusher<std::tuple<TParameters...>>::push(state, std::forward_as_tuple((input)...));
+        auto inArguments = Pusher<std::tuple<TParameters&&...>>::push(state, std::forward_as_tuple(std::forward<TParameters>(input)...));
 
         // 
         const int outArgumentsCount = std::tuple_size<RealReturnType>::value;
@@ -1530,7 +1541,7 @@ private:
             lua_setmetatable(state, -2);
             pushedTable.release();
             
-            return std::move(obj);
+            return obj;
         }
     };
     
@@ -1599,7 +1610,7 @@ private:
      * This functions reads multiple values starting at "index" and passes them to the callback
      */
     template<typename TRetValue, typename TCallback>
-    static auto readIntoFunction(lua_State* state, tag<TRetValue>, TCallback&& callback, int index)
+    static auto readIntoFunction(lua_State* /*state*/, tag<TRetValue>, TCallback&& callback, int /*index*/)
         -> TRetValue
     {
         return callback();
@@ -1615,7 +1626,7 @@ private:
 
         const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index);
         if (!firstElem)
-            throw WrongTypeException(lua_typename(state, index), typeid(TFirstType));
+            throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType));
 
         Binder<TCallback, const TFirstType&> binder{ callback, *firstElem };
         return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
@@ -1629,7 +1640,7 @@ private:
 
         const auto& firstElem = Reader<typename std::decay<TFirstType>::type>::read(state, index);
         if (!firstElem)
-            throw WrongTypeException(lua_typename(state, index), typeid(TFirstType));
+            throw WrongTypeException(lua_typename(state, lua_type(state, index)), typeid(TFirstType));
 
         Binder<TCallback, const TFirstType&> binder{ callback, *firstElem };
         return readIntoFunction(state, retValueTag, binder, index + 1, othersTags...);
@@ -1721,7 +1732,7 @@ static LuaContext::Metatable_t ATTR_UNUSED
 /*            PARTIAL IMPLEMENTATIONS             */
 /**************************************************/
 template<>
-inline auto LuaContext::readTopAndPop<void>(lua_State* state, PushedObject obj)
+inline auto LuaContext::readTopAndPop<void>(lua_State* /*state*/, PushedObject /*obj*/)
     -> void
 {
 }
@@ -1833,6 +1844,23 @@ private:
 /**************************************************/
 // specializations of the Pusher structure
 
+// opaque Lua references
+template<>
+struct LuaContext::Pusher<LuaContext::LuaObject> {
+    static const int minSize = 1;
+    static const int maxSize = 1;
+
+    static PushedObject push(lua_State* state, const LuaContext::LuaObject& value) noexcept {
+        if (value.objectInRegistry.get()) {
+            PushedObject obj = value.objectInRegistry->pop();
+            return obj;
+        } else {
+            lua_pushnil(state);
+            return PushedObject{state, 1};
+        }
+    }
+};
+
 // boolean
 template<>
 struct LuaContext::Pusher<bool> {
@@ -1911,8 +1939,7 @@ struct LuaContext::Pusher<std::nullptr_t> {
     static const int minSize = 1;
     static const int maxSize = 1;
 
-    static PushedObject push(lua_State* state, std::nullptr_t value) noexcept {
-        assert(value == nullptr);
+    static PushedObject push(lua_State* state, std::nullptr_t) noexcept {
         lua_pushnil(state);
         return PushedObject{state, 1};
     }
@@ -1969,7 +1996,7 @@ struct LuaContext::Pusher<std::map<TKey,TValue>> {
         for (auto i = value.begin(), e = value.end(); i != e; ++i)
             setTable<TValue>(state, obj, i->first, i->second);
         
-        return std::move(obj);
+        return obj;
     }
 };
 
@@ -1988,7 +2015,7 @@ struct LuaContext::Pusher<std::unordered_map<TKey,TValue>> {
         for (auto i = value.begin(), e = value.end(); i != e; ++i)
             setTable<TValue>(state, obj, i->first, i->second);
         
-        return std::move(obj);
+        return obj;
     }
 };
 
@@ -2007,7 +2034,7 @@ struct LuaContext::Pusher<std::vector<std::pair<TType1,TType2>>> {
         for (auto i = value.begin(), e = value.end(); i != e; ++i)
             setTable<TType2>(state, obj, i->first, i->second);
         
-        return std::move(obj);
+        return obj;
     }
 };
 
@@ -2025,7 +2052,7 @@ struct LuaContext::Pusher<std::vector<TType>> {
         for (unsigned int i = 0; i < value.size(); ++i)
             setTable<TType>(state, obj, i + 1, value[i]);
         
-        return std::move(obj);
+        return obj;
     }
 };
 
@@ -2313,7 +2340,7 @@ struct LuaContext::Pusher<boost::variant<TTypes...>>
         PushedObject obj{state, 0};
         VariantWriter writer{state, obj};
         value.apply_visitor(writer);
-        return std::move(obj);
+        return obj;
     }
 
 private:
@@ -2381,11 +2408,11 @@ private:
             push2(state, std::move(value), std::integral_constant<int,N+1>{});
     }
     
-    static int push2(lua_State* state, const std::tuple<TTypes...>&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
+    static int push2(lua_State* /*state*/, const std::tuple<TTypes...>&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
         return 0;
     }
     
-    static int push2(lua_State* state, std::tuple<TTypes...>&&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
+    static int push2(lua_State* /*state*/, std::tuple<TTypes...>&&, std::integral_constant<int,sizeof...(TTypes)>) noexcept {
         return 0;
     }
 };
@@ -2395,6 +2422,18 @@ private:
 /**************************************************/
 // specializations of the Reader structures
 
+// opaque Lua references
+template<>
+struct LuaContext::Reader<LuaContext::LuaObject>
+{
+    static auto read(lua_State* state, int index)
+        -> boost::optional<LuaContext::LuaObject>
+    {
+        LuaContext::LuaObject obj(state, index);
+        return obj;
+    }
+};
+
 // reading null
 template<>
 struct LuaContext::Reader<std::nullptr_t>
@@ -2704,7 +2743,7 @@ private:
     template<typename TIterBegin, typename TIterEnd>
     struct VariantReader<TIterBegin, TIterEnd, typename std::enable_if<boost::mpl::distance<TIterBegin, TIterEnd>::type::value == 0>::type>
     {
-        static auto read(lua_State* state, int index)
+        static auto read(lua_State* /*state*/, int /*index*/)
             -> boost::optional<ReturnType> 
         {
             return boost::none;
@@ -2729,7 +2768,7 @@ public:
 template<>
 struct LuaContext::Reader<std::tuple<>>
 {
-    static auto read(lua_State* state, int index, int maxSize = 0)
+    static auto read(lua_State* /*state*/, int /*index*/, int /*maxSize*/ = 0)
         -> boost::optional<std::tuple<>>
     {
         return std::tuple<>{};
index 31e2233efdffa1da0f17d545d182ad756536e145..12b7ad6a0f14ed0b5ff091bb1e8d214bb57d657b 100644 (file)
@@ -1424,6 +1424,8 @@ if test x$boost_cv_inc_path != xno; then
     _BOOST_gcc_test(6, 1) \
     _BOOST_mingw_test(6, 0) \
     _BOOST_gcc_test(6, 0) \
+    _BOOST_mingw_test(5, 4) \
+    _BOOST_gcc_test(5, 4) \
     _BOOST_mingw_test(5, 3) \
     _BOOST_gcc_test(5, 3) \
     _BOOST_mingw_test(5, 2) \
index b6f9d0ea33205b494b35c927a002396361534bdc..ddd1c884316af6bffc220208836181a341790822 100644 (file)
@@ -36,4 +36,19 @@ AC_DEFUN([PDNS_CHECK_LDAP],[
   )
 
   AC_ARG_VAR([LDAP_LIBS], [linker flags for openldap])
+
+  AC_CHECK_HEADERS([krb5.h],
+    [],
+    [AC_MSG_ERROR([Kerberos header (krb5.h) not found])]
+  )
+
+  AC_ARG_VAR([KRB5_LIBS], [linker flag to add Kerberos 5 libraries])
+
+  AC_CHECK_LIB([krb5], [krb5_init_context],
+    [
+      KRB5_LIBS="-lkrb5"
+    ]
+  )
+
+  AC_CHECK_FUNCS([krb5_get_init_creds_opt_set_default_flags])
 ])
index 96cb7345a9e7b485b2b7aaa3a376f459c41f3748..e1b44c6728e999771febc1b9e2d0298af472e1e5 100644 (file)
-AC_DEFUN([PDNS_WITH_POSTGRESQL],[
-  AC_ARG_WITH([pgsql],
-    AS_HELP_STRING([--with-pgsql=<path>],
-      [root directory path of PgSQL installation]
-   ),
-   [PGSQL_lib_check="$withval/lib/pgsql $withval/lib"
-    PGSQL_inc_check="$withval/include/pgsql $withval/include"
-   ]
-  )
-
-  AC_ARG_WITH([pgsql-lib],
-    AS_HELP_STRING([--with-pgsql-lib=<path>],
-      [directory path of PgSQL library installation]
-    ),
-    [PGSQL_lib_check="$withval/lib/pgsql $withval/pgsql $withval"]
-  )
-
-  AC_ARG_WITH([pgsql-includes],
-    AS_HELP_STRING([--with-pgsql-includes=<path>],
-      [directory path of PgSQL header installation]
-    ),
-    [PGSQL_inc_check="$withval/include/pgsql $withval/pgsql $withval"]
-  )
-
-  AC_ARG_WITH([pgsql-config], 
-    AS_HELP_STRING([--with-pgsql-config=<path>],
-      [location of pg_config]
-    ),
-    [PGSQL_pg_config="$withval"
-     if test "x$PGSQL_pg_config" = "xyes" || test ! -x "$PGSQL_pg_config"; then
-       AC_MSG_ERROR([--with-pgsql-config must provide a valid path to pg_config executable])
-     fi
-    ], 
-    [AC_PATH_PROG([PGSQL_pg_config], [pg_config])]
-  )
-
-  if test "x$PGSQL_pg_config" != "x"; then
-    if test "x$PGSQL_lib_check" = "x"; then
-      PGSQL_lib_check=$($PGSQL_pg_config --libdir)
-    fi
-    if test "x$PGSQL_inc_check" = "x"; then
-      PGSQL_inc_check=$($PGSQL_pg_config --includedir)
-    fi
-    PGSQL_CFLAGS=
-  fi
-
-  if test "x$PGSQL_lib_check" = "x"; then
-    PGSQL_lib_check="/usr/local/pgsql/lib/pgsql /usr/local/lib/pgsql /opt/pgsql/lib/pgsql /usr/lib/pgsql /usr/local/pgsql/lib /usr/local/lib /opt/pgsql/lib /usr/lib /usr/lib64 $full_libdir"
-  fi
-
-  if test "x$PGSQL_inc_check" = "x"; then
-    PGSQL_inc_check="/usr/local/pgsql/include/pgsql /usr/include /usr/local/include/postgresql/ /usr/local/include /opt/pgsql/include/pgsql /opt/pgsql/include /usr/include/pgsql/ /usr/include/postgresql"
-  fi
-
-  AC_SUBST([PGSQL_lib])
-  AC_MSG_CHECKING([for PgSQL library directory])
-  PGSQL_libdir=
-  for m in $PGSQL_lib_check; do
-    if test -d "$m" && (test -f "$m/libpq.a" || test -f "$m/libpq.so"); then
-      PGSQL_libdir=$m
-      break
-    fi
-  done
-  if test -z "$PGSQL_libdir"; then
-    AC_MSG_ERROR([Did not find the pgsql library dir in '$PGSQL_lib_check'])
-  fi
-  case "$PGSQL_libdir" in
-    /usr/lib)
-      PGSQL_lib="-lpq"
-      ;;
-    /usr/lib64)
-      PGSQL_lib="-lpq"
-      ;;
-    $full_libdir)
-      PGSQL_lib="-lpq"
-      ;;
-    /*)
-      PGSQL_lib="-L$PGSQL_libdir -Wl,-rpath,$PGSQL_libdir -lpq"
-      ;;
-    *)
-      AC_MSG_ERROR([The PgSQL library directory ($PGSQL_libdir) must be an absolute path.])
-      ;;
-  esac
-  AC_MSG_RESULT([$PGSQL_libdir])
-
-  AC_SUBST([PGSQL_inc]) 
-  AC_MSG_CHECKING([for PgSQL include directory])
-  PGSQL_incdir=
-  for m in $PGSQL_inc_check; do
-    if test -d "$m" && test -f "$m/libpq-fe.h"; then
-      PGSQL_incdir=$m
-      break
-    fi
-  done
-  if test -z "$PGSQL_incdir"; then 
-    AC_MSG_ERROR([Did not find the PgSQL include dir in '$PGSQL_inc_check'])
-  fi
-  case "$PGSQL_incdir" in
-    /*)
-      PGSQL_inc="-I$PGSQL_incdir"
-      ;;
-    * )
-      AC_MSG_ERROR([The PgSQL include directory ($PGSQL_incdir) must be an absolute path.])
-      ;;
-  esac
-  AC_MSG_RESULT([$PGSQL_incdir])
+dnl
+dnl Attempt to detect the flags we need for the Postgresql client libraries
+dnl First, use pkg-config
+dnl If that yields no results, use (optionally find) pg_config and use it to 
+dnl determine the CFLAGS and LIBS
+dnl
+AC_DEFUN([PDNS_WITH_POSTGRESQL], [
+  PG_CONFIG=""
+  AC_ARG_WITH([pg-config], [
+    AS_HELP_STRING([--with-pg-config=<path>], [path to pg_config])
+  ], [
+    PG_CONFIG="$withval"
+    AS_IF([test "x$PG_CONFIG" = "xyes" -o ! -x "$PG_CONFIG"], [
+      AC_MSG_ERROR([--with-pg-config must provide a valid path to the pg_config executable])
+    ])
+  ])
+
+  AS_IF([test "x$PG_CONFIG" = "x"], [
+    PKG_CHECK_MODULES([PGSQL], [libpq], [ : ], [ : ])
+  ])
+
+  AS_IF([test "x$PG_CONFIG" != "x" -o "x$PGSQL_LIBS" = "x"], [
+    dnl Either a path was provided, or pkg-config failed to produce a result
+    AS_IF([test "x$PG_CONFIG" == "x"], [
+      AC_PATH_PROG([PG_CONFIG], [pg_config])
+    ])
+    AS_IF([test "x$PG_CONFIG" == "x"], [
+      AC_MSG_ERROR([Can not find pg_config, use --with-pg-config to specify the path to pg_config])
+    ])
+    PGSQL_LIBS="-L$($PG_CONFIG --libdir) -lpq"
+    PGSQL_CFLAGS="-I$($PG_CONFIG --includedir)"
+  ])
+  AC_SUBST([PGSQL_LIBS])
+  AC_SUBST([PGSQL_CFLAGS])
 ])
-
index 5ad305701f59ef13f1455150ead249caff2dede9..5ebcb025fc14a0003ce48bf48fbc7d3e7e4fedb1 100644 (file)
@@ -1,4 +1,4 @@
-AM_CPPFLAGS += $(PGSQL_inc)
+AM_CPPFLAGS += $(PGSQL_CFLAGS)
 pkglib_LTLIBRARIES = libgpgsqlbackend.la
 
 EXTRA_DIST = \
@@ -18,4 +18,4 @@ libgpgsqlbackend_la_SOURCES = \
        spgsql.cc spgsql.hh
 
 libgpgsqlbackend_la_LDFLAGS = -module -avoid-version
-libgpgsqlbackend_la_LIBADD = $(PGSQL_lib)
+libgpgsqlbackend_la_LIBADD = $(PGSQL_LIBS)
index 4790eb81c0b9beb078fa8cd8e88fffa00d18944e..30be9e67830e1a5d102ce2053cada05eaa6ea92f 100644 (file)
@@ -1 +1 @@
--lssl -lcrypto $(PGSQL_lib)
+-lssl -lcrypto $(PGSQL_LIBS)
index 6b76a215e8ab3a8b6021ae17915410446fb0af77..93a6713242ad887ef86fe23d76a69ee80c703e59 100644 (file)
@@ -5,7 +5,9 @@ EXTRA_DIST = OBJECTFILES OBJECTLIBS
 libldapbackend_la_SOURCES = \
        ldapbackend.cc ldapbackend.hh \
        powerldap.cc powerldap.hh \
-       utils.hh
+       utils.hh exceptions.hh \
+       ldaputils.hh ldaputils.cc \
+       ldapauthenticator.hh ldapauthenticator_p.hh ldapauthenticator.cc
 
 libldapbackend_la_LDFLAGS = -module -avoid-version
-libldapbackend_la_LIBADD = $(LDAP_LIBS)
+libldapbackend_la_LIBADD = $(LDAP_LIBS) $(KRB5_LIBS)
index 59f4773cd75440ff50a92e467d6b9187155c1d0d..0f758368845a76ba7a048b3968a4bd3bb7ba7c8a 100644 (file)
@@ -1 +1 @@
-ldapbackend.lo powerldap.lo
+ldapbackend.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
index ab9c9d9c82163b9997ee0cfea496eab00cdcd5a9..f296c5a32f016f3a25a6e4edae0ead458b720824 100644 (file)
@@ -1 +1 @@
-$(LDAP_LIBS)
+$(LDAP_LIBS) $(KRB5_LIBS)
diff --git a/modules/ldapbackend/exceptions.hh b/modules/ldapbackend/exceptions.hh
new file mode 100644 (file)
index 0000000..53e0145
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *  PowerDNS LDAP Connector
+ *  By PowerDNS.COM BV
+ *  By Norbert Sendetzky <norbert@linuxnetworks.de> (2003-2007)
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+
+#ifndef LDAPEXCEPTIONS_HH
+#define LDAPEXCEPTIONS_HH
+
+class LDAPException : public std::runtime_error
+{
+  public:
+    explicit LDAPException( const std::string &str ) : std::runtime_error( str ) {}
+};
+
+class LDAPTimeout : public LDAPException
+{
+  public:
+    explicit LDAPTimeout() : LDAPException( "Timeout" ) {}
+};
+
+class LDAPNoConnection : public LDAPException
+{
+  public:
+    explicit LDAPNoConnection() : LDAPException( "No connection to LDAP server" ) {}
+};
+
+#endif // LDAPEXCEPTIONS_HH
diff --git a/modules/ldapbackend/ldapauthenticator.cc b/modules/ldapbackend/ldapauthenticator.cc
new file mode 100644 (file)
index 0000000..c2fa875
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ *  PowerDNS LDAP Backend
+ *  Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
+ *  Copyright (C) 2003-2007 Norbert Sendetzky <norbert@linuxnetworks.de>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <krb5.h>
+#include <pdns/logger.hh>
+#include "ldapauthenticator_p.hh"
+#include "ldaputils.hh"
+
+/*****************************
+ * 
+ * LdapSimpleAuthenticator
+ * 
+ ****************************/
+
+LdapSimpleAuthenticator::LdapSimpleAuthenticator( const std::string& dn, const std::string& pw, int tmout )
+  : binddn( dn ), bindpw( pw ), timeout( tmout )
+{
+}
+
+bool LdapSimpleAuthenticator::authenticate( LDAP *conn )
+{
+  int msgid;
+
+#ifdef HAVE_LDAP_SASL_BIND
+  int rc;
+  struct berval passwd;
+
+  passwd.bv_val = (char *)bindpw.c_str();
+  passwd.bv_len = strlen( passwd.bv_val );
+
+  if( ( rc = ldap_sasl_bind( conn, binddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid ) ) != LDAP_SUCCESS )
+  {
+    fillLastError( conn, rc );
+    return false;
+  }
+#else
+  if( ( msgid = ldap_bind( conn, binddn.c_str(), bindpw.c_str(), LDAP_AUTH_SIMPLE ) ) == -1 )
+  {
+    fillLastError( conn, msgid );
+    return false;
+  }
+#endif
+
+  ldapWaitResult( conn, msgid, timeout, NULL );
+  return true;
+}
+
+std::string LdapSimpleAuthenticator::getError() const
+{
+  return lastError;
+}
+
+void LdapSimpleAuthenticator::fillLastError( LDAP* conn, int code )
+{
+  lastError = ldapGetError( conn, code );
+}
+
+/*****************************
+ * 
+ * LdapGssapiAuthenticator
+ * 
+ ****************************/
+
+static int ldapGssapiAuthenticatorSaslInteractCallback( LDAP *conn, unsigned flags, void *defaults, void *in )
+{
+  return LDAP_SUCCESS;
+}
+
+LdapGssapiAuthenticator::LdapGssapiAuthenticator( const std::string& kt, const std::string &ccache, int tmout )
+  : logPrefix( "[LDAP GSSAPI] " ), keytabFile( kt ), cCacheFile( ccache ), timeout( tmout )
+{
+}
+
+bool LdapGssapiAuthenticator::authenticate( LDAP *conn )
+{
+  int code = attemptAuth( conn );
+
+  if ( code == -1 ) {
+    return false;
+  }
+  else if ( code == -2 ) {
+    // Here it may be possible to retry after obtainting a fresh ticket
+    L<<Logger::Debug << logPrefix << "No TGT found, trying to acquire a new one" << std::endl;
+    code = updateTgt();
+
+    if ( attemptAuth( conn ) != 0 ) {
+      L<<Logger::Error << logPrefix << "Failed to acquire a TGT" << std::endl;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+std::string LdapGssapiAuthenticator::getError() const
+{
+  return lastError;
+}
+
+int LdapGssapiAuthenticator::attemptAuth( LDAP *conn )
+{
+  // Create SASL defaults
+  SaslDefaults defaults;
+  char *ldapOption = 0;
+
+  ldap_get_option( conn, LDAP_OPT_X_SASL_MECH, ldapOption );
+  if ( !ldapOption )
+    defaults.mech = std::string( "GSSAPI" );
+  else
+    defaults.mech = std::string( ldapOption );
+  ldap_memfree( ldapOption );
+
+  ldap_get_option( conn, LDAP_OPT_X_SASL_REALM, ldapOption );
+  if ( ldapOption )
+    defaults.realm = std::string( ldapOption );
+  ldap_memfree( ldapOption );
+
+  ldap_get_option( conn, LDAP_OPT_X_SASL_AUTHCID, ldapOption );
+  if ( ldapOption )
+    defaults.authcid = std::string( ldapOption );
+  ldap_memfree( ldapOption );
+
+  ldap_get_option( conn, LDAP_OPT_X_SASL_AUTHZID, ldapOption );
+  if ( ldapOption )
+    defaults.authzid = std::string( ldapOption );
+  ldap_memfree( ldapOption );
+
+  // And now try to bind
+  int rc = ldap_sasl_interactive_bind_s( conn, "", defaults.mech.c_str(),
+                                         NULL, NULL, LDAP_SASL_QUIET,
+                                         ldapGssapiAuthenticatorSaslInteractCallback, &defaults );
+  L<<Logger::Debug << logPrefix << "ldap_sasl_interactive_bind_s returned " << rc << std::endl;
+
+  if ( rc == LDAP_LOCAL_ERROR ) {
+    // This may mean that the ticket has expired, so let the caller know
+    lastError = ldapGetError( conn, rc );
+    return -2;
+  }
+  else if ( rc != LDAP_SUCCESS ) {
+    lastError = ldapGetError( conn, rc );
+    return -1;
+  }
+
+  return rc;
+}
+
+int LdapGssapiAuthenticator::updateTgt()
+{
+  krb5_error_code code;
+  krb5_context context;
+  krb5_creds credentials;
+  krb5_keytab keytab;
+  krb5_principal principal;
+  krb5_ccache ccache;
+  krb5_get_init_creds_opt *options;
+
+  if ( ( code = krb5_init_context( &context ) ) != 0 ) {
+    L<<Logger::Error << logPrefix << "Failed to init krb5 context" << std::endl;
+    return code;
+  }
+
+  if ( !keytabFile.empty() ) {
+    std::string keytabStr( "FILE:" + keytabFile );
+    code = krb5_kt_resolve( context, keytabStr.c_str(), &keytab );
+  }
+  else {
+    code = krb5_kt_default( context, &keytab );
+  }
+  
+  if ( code != 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when locating the keytab file: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    return code;
+  }
+
+  // Extract the principal name from the keytab
+  krb5_kt_cursor cursor;
+  if ( ( code = krb5_kt_start_seq_get( context, keytab, &cursor ) ) != 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when initiating keytab search: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    krb5_kt_close( context, keytab );
+    return code;
+  }
+
+  krb5_keytab_entry entry;
+  if ( ( code = krb5_kt_next_entry( context, keytab, &entry, &cursor ) ) == 0 ) {
+    code = krb5_copy_principal( context, entry.principal, &principal );
+    krb5_kt_free_entry( context, &entry );
+  }
+
+  krb5_kt_end_seq_get( context, keytab, &cursor );
+  if ( code != 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when extracting principal information: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    krb5_kt_close( context, keytab );
+    krb5_free_principal( context, principal );
+    return code;
+  }
+
+  // Locate the credentials cache file
+  if ( !cCacheFile.empty() ) {
+    std::string cCacheStr( "FILE:" + cCacheFile );
+    code = krb5_cc_resolve( context, cCacheStr.c_str(), &ccache );
+  }
+  else {
+    code = krb5_cc_default( context, &ccache );
+  }
+
+  if ( code != 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when locating the credentials cache file: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    krb5_kt_close( context, keytab );
+    krb5_free_principal( context, principal );
+    return code;
+  }
+
+  // Initialize the credentials cache file
+  if ( ( code = krb5_cc_initialize( context, ccache, principal ) ) != 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when initializing the credentials cache file: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    krb5_kt_close( context, keytab );
+    krb5_free_principal( context, principal );
+    return code;
+  }
+
+  if ( ( code = krb5_get_init_creds_opt_alloc( context, &options ) ) != 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when allocating credentials cache structure: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    krb5_kt_close( context, keytab );
+    krb5_free_principal( context, principal );
+    return code;
+  }
+  krb5_get_init_creds_opt_set_default_flags( context, "pdns", NULL, options );
+
+  // And finally get the TGT!
+  code = krb5_get_init_creds_keytab( context, &credentials, principal, keytab, 0, NULL, options );
+  krb5_get_init_creds_opt_free( context, options );
+  krb5_kt_close( context, keytab );
+  krb5_free_principal( context, principal );
+
+  if ( code == 0 ) {
+    L<<Logger::Error << logPrefix << "krb5 error when getting the TGT: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+    code = krb5_cc_store_cred( context, ccache, &credentials );
+    krb5_free_cred_contents( context, &credentials );
+    krb5_cc_close( context, ccache );
+  }
+
+  krb5_free_context( context );
+  return code;
+}
diff --git a/modules/ldapbackend/ldapauthenticator.hh b/modules/ldapbackend/ldapauthenticator.hh
new file mode 100644 (file)
index 0000000..d8452a0
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  PowerDNS LDAP Backend
+ *  Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <ldap.h>
+#include <string>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef LDAPAUTHENTICATOR_HH
+#define LDAPAUTHENTICATOR_HH
+
+class LdapAuthenticator
+{
+  public:
+    virtual ~LdapAuthenticator() {}
+    virtual bool authenticate( LDAP *connection ) = 0;
+    virtual std::string getError() const = 0;
+};
+
+#endif // LDAPAUTHENTICATOR_HH
diff --git a/modules/ldapbackend/ldapauthenticator_p.hh b/modules/ldapbackend/ldapauthenticator_p.hh
new file mode 100644 (file)
index 0000000..e3fff8c
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  PowerDNS LDAP Backend
+ *  Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "ldapauthenticator.hh"
+
+#ifndef LDAPAUTHENTICATOR_P_HH
+#define LDAPAUTHENTICATOR_P_HH
+
+#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_DEFAULT_FLAGS
+#define krb5_get_init_creds_opt_set_default_flags( a, b, c, d ) /* This does not exist with MIT Kerberos */
+#endif
+
+class LdapSimpleAuthenticator : public LdapAuthenticator
+{
+    std::string binddn;
+    std::string bindpw;
+    int timeout;
+    std::string lastError;
+  
+    void fillLastError( LDAP *conn, int code );
+  
+  public:
+    LdapSimpleAuthenticator( const std::string &dn, const std::string &pw, int timeout );
+    virtual bool authenticate( LDAP *conn );
+    virtual std::string getError() const;
+};
+
+class LdapGssapiAuthenticator : public LdapAuthenticator
+{
+    std::string logPrefix;
+    std::string keytabFile;
+    std::string cCacheFile;
+    int timeout;
+    std::string lastError;
+    
+    struct SaslDefaults {
+      std::string mech;
+      std::string realm;
+      std::string authcid;
+      std::string authzid;
+    };
+  
+    int attemptAuth( LDAP *conn );
+    int updateTgt();
+  
+  public:
+    LdapGssapiAuthenticator( const std::string &keytab, const std::string &credsCache, int timeout );
+    virtual bool authenticate( LDAP *conn );
+    virtual std::string getError() const;
+};
+
+#endif // LDAPAUTHENTICATOR_P_HH
index 5c90b49c67453545765ef57c3619061acf323ef0..70d5671ca9c19a5a6e84dcba9ecd3b977cb9df6c 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#include "exceptions.hh"
+#include "ldapauthenticator_p.hh"
 #include "ldapbackend.hh"
+#include <cstdlib>
 
 unsigned int ldap_host_index = 0;
 
 LdapBackend::LdapBackend( const string &suffix )
 {
-        string hoststr;
-        unsigned int i, idx;
-        vector<string> hosts;
+  string hoststr;
+  unsigned int i, idx;
+  vector<string> hosts;
+
+
+  try
+  {
+    m_msgid = 0;
+    m_qname.clear();
+    m_pldap = NULL;
+    m_authenticator = NULL;
+    m_ttl = 0;
+    m_axfrqlen = 0;
+    m_last_modified = 0;
+    m_qlog = arg().mustDo( "query-logging" );
+    m_default_ttl = arg().asNum( "default-ttl" );
+    m_myname = "[LdapBackend]";
+
+    setArgPrefix( "ldap" + suffix );
+
+    m_getdn = false;
+    m_reconnect_attempts = getArgAsNum( "reconnect-attempts" );
+    m_list_fcnt = &LdapBackend::list_simple;
+    m_lookup_fcnt = &LdapBackend::lookup_simple;
+    m_prepare_fcnt = &LdapBackend::prepare_simple;
+
+    if( getArg( "method" ) == "tree" )
+    {
+      m_lookup_fcnt = &LdapBackend::lookup_tree;
+    }
+
+    if( getArg( "method" ) == "strict" || mustDo( "disable-ptrrecord" ) )
+    {
+      m_list_fcnt = &LdapBackend::list_strict;
+      m_lookup_fcnt = &LdapBackend::lookup_strict;
+      m_prepare_fcnt = &LdapBackend::prepare_strict;
+    }
+
+    stringtok( hosts, getArg( "host" ), ", " );
+    idx = ldap_host_index++ % hosts.size();
+    hoststr = hosts[idx];
+
+    for( i = 1; i < hosts.size(); i++ )
+    {
+      hoststr += " " + hosts[ ( idx + i ) % hosts.size() ];
+    }
+
+    L << Logger::Info << m_myname << " LDAP servers = " << hoststr << endl;
+
+    m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ) );
+    m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
+
+    string bindmethod = getArg( "bindmethod" );
+    if ( bindmethod == "gssapi" ) {
+      setenv( "KRB5CCNAME", getArg( "krb5-ccache" ).c_str(), 1 );
+      m_authenticator = new LdapGssapiAuthenticator( getArg( "krb5-keytab" ), getArg( "krb5-ccache" ), getArgAsNum( "timeout" ) );
+    }
+    else {
+      m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) );
+    }
+    m_pldap->bind( m_authenticator );
+
+    L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
+    return;
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Error << m_myname << " Ldap connection to server failed because of timeout" << endl;
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Ldap connection to server failed: " << le.what() << endl;
+  }
+  catch( std::exception &e )
+  {
+    L << Logger::Error << m_myname << " Caught STL exception: " << e.what() << endl;
+  }
+
+  if( m_pldap != NULL ) { delete( m_pldap ); }
+  throw( PDNSException( "Unable to connect to ldap server" ) );
+}
 
 
-        try
-        {
-               m_msgid = 0;
-               m_qname.clear();
-               m_pldap = NULL;
-               m_ttl = 0;
-               m_axfrqlen = 0;
-               m_last_modified = 0;
-               m_qlog = arg().mustDo( "query-logging" );
-               m_default_ttl = arg().asNum( "default-ttl" );
-               m_myname = "[LdapBackend]";
-
-               setArgPrefix( "ldap" + suffix );
-
-               m_getdn = false;
-               m_list_fcnt = &LdapBackend::list_simple;
-               m_lookup_fcnt = &LdapBackend::lookup_simple;
-               m_prepare_fcnt = &LdapBackend::prepare_simple;
-
-               if( getArg( "method" ) == "tree" )
-               {
-                       m_lookup_fcnt = &LdapBackend::lookup_tree;
-               }
-
-               if( getArg( "method" ) == "strict" || mustDo( "disable-ptrrecord" ) )
-               {
-                       m_list_fcnt = &LdapBackend::list_strict;
-                       m_lookup_fcnt = &LdapBackend::lookup_strict;
-                       m_prepare_fcnt = &LdapBackend::prepare_strict;
-               }
-
-               stringtok( hosts, getArg( "host" ), ", " );
-               idx = ldap_host_index++ % hosts.size();
-               hoststr = hosts[idx];
-
-               for( i = 1; i < hosts.size(); i++ )
-               {
-                       hoststr += " " + hosts[ ( idx + i ) % hosts.size() ];
-               }
-
-               L << Logger::Info << m_myname << " LDAP servers = " << hoststr << endl;
-
-               m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ) );
-               m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
-               m_pldap->bind( getArg( "binddn" ), getArg( "secret" ), LDAP_AUTH_SIMPLE, getArgAsNum( "timeout" ) );
-
-               L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
-               return;
-        }
-        catch( LDAPTimeout &lt )
-        {
-               L << Logger::Error << m_myname << " Ldap connection to server failed because of timeout" << endl;
-        }
-        catch( LDAPException &le )
-        {
-               L << Logger::Error << m_myname << " Ldap connection to server failed: " << le.what() << endl;
-        }
-        catch( std::exception &e )
-        {
-               L << Logger::Error << m_myname << " Caught STL exception: " << e.what() << endl;
-        }
 
-        if( m_pldap != NULL ) { delete( m_pldap ); }
-        throw( PDNSException( "Unable to connect to ldap server" ) );
+LdapBackend::~LdapBackend()
+{
+  delete( m_pldap );
+  delete( m_authenticator );
+  L << Logger::Notice << m_myname << " Ldap connection closed" << endl;
 }
 
 
 
-LdapBackend::~LdapBackend()
+bool LdapBackend::reconnect()
 {
-        if( m_pldap != NULL ) { delete( m_pldap ); }
-        try {
-                L << Logger::Notice << m_myname << " Ldap connection closed" << endl;
-        }
-        catch (...) {
-        }
+  int attempts = m_reconnect_attempts;
+  bool connected = false;
+  while ( !connected && attempts > 0 ) {
+    L << Logger::Debug << m_myname << " Reconnection attempts left: " << attempts << endl;
+    connected = m_pldap->connect();
+    if ( !connected )
+      Utility::usleep( 250 );
+    --attempts;
+  }
+
+  if ( connected )
+    m_pldap->bind( m_authenticator );
+
+  return connected;
 }
 
 
 
 bool LdapBackend::list( const DNSName& target, int domain_id, bool include_disabled )
 {
-        try
-        {
-               m_qname = target;
-               m_axfrqlen = target.toStringRootDot().length();
-               m_adomain = m_adomains.end();   // skip loops in get() first time
-
-               return (this->*m_list_fcnt)( target, domain_id );
-        }
-        catch( LDAPTimeout &lt )
-        {
-               L << Logger::Warning << m_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
-               throw( DBException( "LDAP server timeout" ) );
-        }
-        catch( LDAPException &le )
-        {
-               L << Logger::Error << m_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
-               throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
-        }
-        catch( std::exception &e )
-        {
-               L << Logger::Error << m_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
-               throw( DBException( "STL exception" ) );
-        }
-
-        return false;
+  try
+  {
+    m_qname = target;
+    m_axfrqlen = target.toStringRootDot().length();
+    m_adomain = m_adomains.end();   // skip loops in get() first time
+
+    return (this->*m_list_fcnt)( target, domain_id );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Warning << m_myname << " Unable to get zone " << target << " from LDAP directory: " << lt.what() << endl;
+    throw( DBException( "LDAP server timeout" ) );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->list( target, domain_id );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Unable to get zone " << target << " from LDAP directory: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    L << Logger::Error << m_myname << " Caught STL exception for target " << target << ": " << e.what() << endl;
+    throw( DBException( "STL exception" ) );
+  }
+
+  return false;
 }
 
 
 
 inline bool LdapBackend::list_simple( const DNSName& target, int domain_id )
 {
-        string dn;
-        string filter;
-    string qesc;
-
-
-        dn = getArg( "basedn" );
-        qesc = toLower( m_pldap->escape( target.toStringRootDot() ) );
-
-        // search for SOARecord of target
-        filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
-        m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
-        m_pldap->getSearchEntry( m_msgid, m_result, true );
-
-        if( m_result.count( "dn" ) && !m_result["dn"].empty() )
-        {
-               if( !mustDo( "basedn-axfr-override" ) )
-               {
-                       dn = m_result["dn"][0];
-               }
-               m_result.erase( "dn" );
-        }
-
-        prepare();
-        filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
-        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
-        m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
-
-        return true;
+  string dn;
+  string filter;
+  string qesc;
+
+
+  dn = getArg( "basedn" );
+  qesc = toLower( m_pldap->escape( target.toStringRootDot() ) );
+
+  // search for SOARecord of target
+  filter = strbind( ":target:", "&(associatedDomain=" + qesc + ")(sOARecord=*)", getArg( "filter-axfr" ) );
+  m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
+  m_pldap->getSearchEntry( m_msgid, m_result, true );
+
+  if( m_result.count( "dn" ) && !m_result["dn"].empty() )
+  {
+    if( !mustDo( "basedn-axfr-override" ) )
+    {
+      dn = m_result["dn"][0];
+    }
+    m_result.erase( "dn" );
+  }
+
+  prepare();
+  filter = strbind( ":target:", "associatedDomain=*." + qesc, getArg( "filter-axfr" ) );
+  DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn << ", filter: " << filter << endl );
+  m_msgid = m_pldap->search( dn, LDAP_SCOPE_SUBTREE, filter, (const char**) ldap_attrany );
+
+  return true;
 }
 
 
 
 inline bool LdapBackend::list_strict( const DNSName& target, int domain_id )
 {
-        if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName(".ip6.arpa")) )
-        {
-               L << Logger::Warning << m_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
-               return false;   // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
-        }
+  if( target.isPartOf(DNSName("in-addr.arpa")) || target.isPartOf(DNSName(".ip6.arpa")) )
+  {
+    L << Logger::Warning << m_myname << " Request for reverse zone AXFR, but this is not supported in strict mode" << endl;
+    return false;   // AXFR isn't supported in strict mode. Use simple mode and additional PTR records
+  }
 
-        return list_simple( target, domain_id );
+  return list_simple( target, domain_id );
 }
 
 
 
 void LdapBackend::lookup( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
 {
-        try
-        {
-               m_axfrqlen = 0;
-               m_qname = qname;
-               m_adomain = m_adomains.end();   // skip loops in get() first time
-               m_qtype = qtype;
-
-               if( m_qlog ) { L.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
-               (this->*m_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
-        }
-        catch( LDAPTimeout &lt )
-        {
-               L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
-               throw( DBException( "LDAP server timeout" ) );
-        }
-        catch( LDAPException &le )
-        {
-               L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
-               throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
-        }
-        catch( std::exception &e )
-        {
-               L << Logger::Error << m_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
-               throw( DBException( "STL exception" ) );
-        }
+  try
+  {
+    m_axfrqlen = 0;
+    m_qname = qname;
+    m_adomain = m_adomains.end();   // skip loops in get() first time
+    m_qtype = qtype;
+
+    if( m_qlog ) { L.log( "Query: '" + qname.toStringRootDot() + "|" + qtype.getName() + "'", Logger::Error ); }
+    (this->*m_lookup_fcnt)( qtype, qname, dnspkt, zoneid );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw( DBException( "LDAP server timeout" ) );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->lookup( qtype, qname, dnspkt, zoneid );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    L << Logger::Error << m_myname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
+    throw( DBException( "STL exception" ) );
+  }
 }
 
 
 
 void LdapBackend::lookup_simple( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
 {
-        string filter, attr, qesc;
-        const char** attributes = ldap_attrany + 1;   // skip associatedDomain
-        const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+  string filter, attr, qesc;
+  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
+  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
 
 
-        qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
-        filter = "associatedDomain=" + qesc;
+  qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
+  filter = "associatedDomain=" + qesc;
 
-        if( qtype.getCode() != QType::ANY )
-        {
-               attr = qtype.getName() + "Record";
-               filter = "&(" + filter + ")(" + attr + "=*)";
-               attronly[0] = attr.c_str();
-               attributes = attronly;
-        }
+  if( qtype.getCode() != QType::ANY )
+  {
+    attr = qtype.getName() + "Record";
+    filter = "&(" + filter + ")(" + attr + "=*)";
+    attronly[0] = attr.c_str();
+    attributes = attronly;
+  }
 
-        filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
 
-        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
-        m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
+  DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
+  m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
 }
 
 
 
 void LdapBackend::lookup_strict( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
 {
-        int len;
-        vector<string> parts;
-        string filter, attr, qesc;
-        const char** attributes = ldap_attrany + 1;   // skip associatedDomain
-        const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
-
-
-        qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
-        stringtok( parts, qesc, "." );
-        len = qesc.length();
-
-         if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" )   // IPv4 reverse lookups
-        {
-               filter = "aRecord=" + ptr2ip4( parts );
-               attronly[0] = "associatedDomain";
-               attributes = attronly;
-        }
-        else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) )   // IPv6 reverse lookups
-        {
-               filter = "aAAARecord=" + ptr2ip6( parts );
-               attronly[0] = "associatedDomain";
-               attributes = attronly;
-        }
-        else   // IPv4 and IPv6 lookups
-        {
-               filter = "associatedDomain=" + qesc;
-               if( qtype.getCode() != QType::ANY )
-               {
-                       attr = qtype.getName() + "Record";
-                       filter = "&(" + filter + ")(" + attr + "=*)";
-                       attronly[0] = attr.c_str();
-                       attributes = attronly;
-               }
-        }
-
-        filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
-
-        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
-        m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
+  int len;
+  vector<string> parts;
+  string filter, attr, qesc;
+  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
+  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+
+
+  qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
+  stringtok( parts, qesc, "." );
+  len = qesc.length();
+
+  if( parts.size() == 6 && len > 13 && qesc.substr( len - 13, 13 ) == ".in-addr.arpa" )   // IPv4 reverse lookups
+  {
+    filter = "aRecord=" + ptr2ip4( parts );
+    attronly[0] = "associatedDomain";
+    attributes = attronly;
+  }
+  else if( parts.size() == 34 && len > 9 && ( qesc.substr( len - 9, 9 ) == ".ip6.arpa" ) )   // IPv6 reverse lookups
+  {
+    filter = "aAAARecord=" + ptr2ip6( parts );
+    attronly[0] = "associatedDomain";
+    attributes = attronly;
+  }
+  else   // IPv4 and IPv6 lookups
+  {
+    filter = "associatedDomain=" + qesc;
+    if( qtype.getCode() != QType::ANY )
+    {
+      attr = qtype.getName() + "Record";
+      filter = "&(" + filter + ")(" + attr + "=*)";
+      attronly[0] = attr.c_str();
+      attributes = attronly;
+    }
+  }
+
+  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+
+  DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
+  m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attributes );
 }
 
 
 
 void LdapBackend::lookup_tree( const QType &qtype, const DNSName &qname, DNSPacket *dnspkt, int zoneid )
 {
-        string filter, attr, qesc, dn;
-        const char** attributes = ldap_attrany + 1;   // skip associatedDomain
-        const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
-        vector<string> parts;
+  string filter, attr, qesc, dn;
+  const char** attributes = ldap_attrany + 1;   // skip associatedDomain
+  const char* attronly[] = { NULL, "dNSTTL", "modifyTimestamp", NULL };
+  vector<string> parts;
 
 
-        qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
-        filter = "associatedDomain=" + qesc;
+  qesc = toLower( m_pldap->escape( qname.toStringRootDot() ) );
+  filter = "associatedDomain=" + qesc;
 
-        if( qtype.getCode() != QType::ANY )
-        {
-               attr = qtype.getName() + "Record";
-               filter = "&(" + filter + ")(" + attr + "=*)";
-               attronly[0] = attr.c_str();
-               attributes = attronly;
-        }
+  if( qtype.getCode() != QType::ANY )
+  {
+    attr = qtype.getName() + "Record";
+    filter = "&(" + filter + ")(" + attr + "=*)";
+    attronly[0] = attr.c_str();
+    attributes = attronly;
+  }
 
-        filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
+  filter = strbind( ":target:", filter, getArg( "filter-lookup" ) );
 
-        stringtok( parts, toLower( qname.toString() ), "." );
-        for(auto i = parts.crbegin(); i != parts.crend(); i++ )
-        {
-               dn = "dc=" + *i + "," + dn;
-        }
+  stringtok( parts, toLower( qname.toString() ), "." );
+  for(auto i = parts.crbegin(); i != parts.crend(); i++ )
+  {
+    dn = "dc=" + *i + "," + dn;
+  }
 
-        DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
-        m_msgid = m_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
+  DLOG( L << Logger::Debug << m_myname << " Search = basedn: " << dn + getArg( "basedn" ) << ", filter: " << filter << ", qtype: " << qtype.getName() << endl );
+  m_msgid = m_pldap->search( dn + getArg( "basedn" ), LDAP_SCOPE_BASE, filter, attributes );
 }
 
 
 inline bool LdapBackend::prepare()
 {
-        m_adomains.clear();
-        m_ttl = m_default_ttl;
-        m_last_modified = 0;
-
-        if( m_result.count( "dNSTTL" ) && !m_result["dNSTTL"].empty() )
-        {
-               char* endptr;
-
-               m_ttl = (uint32_t) strtol( m_result["dNSTTL"][0].c_str(), &endptr, 10 );
-               if( *endptr != '\0' )
-               {
-                       L << Logger::Warning << m_myname << " Invalid time to live for " << m_qname << ": " << m_result["dNSTTL"][0] << endl;
-                       m_ttl = m_default_ttl;
-               }
-               m_result.erase( "dNSTTL" );
-        }
-
-        if( m_result.count( "modifyTimestamp" ) && !m_result["modifyTimestamp"].empty() )
-        {
-               if( ( m_last_modified = str2tstamp( m_result["modifyTimestamp"][0] ) ) == 0 )
-               {
-                       L << Logger::Warning << m_myname << " Invalid modifyTimestamp for " << m_qname << ": " << m_result["modifyTimestamp"][0] << endl;
-               }
-               m_result.erase( "modifyTimestamp" );
-        }
-
-        if( !(this->*m_prepare_fcnt)() )
-        {
-               return false;
-        }
-
-        m_adomain = m_adomains.begin();
-        m_attribute = m_result.begin();
-        m_value = m_attribute->second.begin();
-
-        return true;
+  m_adomains.clear();
+  m_ttl = m_default_ttl;
+  m_last_modified = 0;
+
+  if( m_result.count( "dNSTTL" ) && !m_result["dNSTTL"].empty() )
+  {
+    char* endptr;
+
+    m_ttl = (uint32_t) strtol( m_result["dNSTTL"][0].c_str(), &endptr, 10 );
+    if( *endptr != '\0' )
+    {
+      L << Logger::Warning << m_myname << " Invalid time to live for " << m_qname << ": " << m_result["dNSTTL"][0] << endl;
+      m_ttl = m_default_ttl;
+    }
+    m_result.erase( "dNSTTL" );
+  }
+
+  if( m_result.count( "modifyTimestamp" ) && !m_result["modifyTimestamp"].empty() )
+  {
+    if( ( m_last_modified = str2tstamp( m_result["modifyTimestamp"][0] ) ) == 0 )
+    {
+      L << Logger::Warning << m_myname << " Invalid modifyTimestamp for " << m_qname << ": " << m_result["modifyTimestamp"][0] << endl;
+    }
+    m_result.erase( "modifyTimestamp" );
+  }
+
+  if( !(this->*m_prepare_fcnt)() )
+  {
+    return false;
+  }
+
+  m_adomain = m_adomains.begin();
+  m_attribute = m_result.begin();
+  m_value = m_attribute->second.begin();
+
+  return true;
 }
 
 
 
 inline bool LdapBackend::prepare_simple()
 {
-        if( !m_axfrqlen )   // request was a normal lookup()
-        {
-               m_adomains.push_back( m_qname );
-        }
-        else   // request was a list() for AXFR
-        {
-               if( m_result.count( "associatedDomain" ) )
-               {
-                       for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
-                               if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
-                                 m_adomains.push_back( DNSName(*i) );
-                               }
-                       }
-                       m_result.erase( "associatedDomain" );
-               }
-        }
-
-        return true;
+  if( !m_axfrqlen )   // request was a normal lookup()
+  {
+    m_adomains.push_back( m_qname );
+  }
+  else   // request was a list() for AXFR
+  {
+    if( m_result.count( "associatedDomain" ) )
+    {
+      for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
+        if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
+          m_adomains.push_back( DNSName(*i) );
+        }
+      }
+      m_result.erase( "associatedDomain" );
+    }
+  }
+
+  return true;
 }
 
 
 
 inline bool LdapBackend::prepare_strict()
 {
-        if( !m_axfrqlen )   // request was a normal lookup()
-        {
-               m_adomains.push_back( m_qname );
-               if( m_result.count( "associatedDomain" ) )
-               {
-                       m_result["PTRRecord"] = m_result["associatedDomain"];
-                       m_result.erase( "associatedDomain" );
-               }
-        }
-        else   // request was a list() for AXFR
-        {
-               if( m_result.count( "associatedDomain" ) )
-               {
-                       for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
-                               if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
-                                 m_adomains.push_back( DNSName(*i) );
-                               }
-                       }
-                       m_result.erase( "associatedDomain" );
-               }
-        }
-
-        return true;
+  if( !m_axfrqlen )   // request was a normal lookup()
+  {
+    m_adomains.push_back( m_qname );
+    if( m_result.count( "associatedDomain" ) )
+    {
+      m_result["PTRRecord"] = m_result["associatedDomain"];
+      m_result.erase( "associatedDomain" );
+    }
+  }
+  else   // request was a list() for AXFR
+  {
+    if( m_result.count( "associatedDomain" ) )
+    {
+      for(auto i = m_result["associatedDomain"].begin(); i != m_result["associatedDomain"].end(); i++ ) {
+        if( i->size() >= m_axfrqlen && i->substr( i->size() - m_axfrqlen, m_axfrqlen ) == m_qname.toStringRootDot() /* ugh */ ) {
+          m_adomains.push_back( DNSName(*i) );
+        }
+      }
+      m_result.erase( "associatedDomain" );
+    }
+  }
+
+  return true;
 }
 
 
 
 bool LdapBackend::get( DNSResourceRecord &rr )
 {
-        QType qt;
-        vector<string> parts;
-        string attrname, qstr;
-
-
-        try
-        {
-               do
-               {
-                       while( m_adomain != m_adomains.end() )
-                       {
-                               while( m_attribute != m_result.end() )
-                               {
-                                       attrname = m_attribute->first;
-                                       qstr = attrname.substr( 0, attrname.length() - 6 );   // extract qtype string from ldap attribute name
-                                       qt = const_cast<char*>(toUpper( qstr ).c_str());
-
-                                       while( m_value != m_attribute->second.end() )
-                                       {
-                                               if(m_qtype != qt && m_qtype != QType::ANY) {
-                                                       m_value++;
-                                                       continue;
-                                               }
-
-                                               rr.qtype = qt;
-                                               rr.qname = *m_adomain;
-                                               rr.ttl = m_ttl;
-                                               rr.last_modified = m_last_modified;
-                                               rr.content = *m_value;
-                                               m_value++;
-
-                                               DLOG( L << Logger::Debug << m_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
-                                               return true;
-                                       }
-
-                                       m_attribute++;
-                                       m_value = m_attribute->second.begin();
-                               }
-                               m_adomain++;
-                               m_attribute = m_result.begin();
-                               m_value = m_attribute->second.begin();
-                       }
-               }
-               while( m_pldap->getSearchEntry( m_msgid, m_result, m_getdn ) && prepare() );
-
-        }
-        catch( LDAPTimeout &lt )
-        {
-               L << Logger::Warning << m_myname << " Search failed: " << lt.what() << endl;
-               throw( DBException( "LDAP server timeout" ) );
-        }
-        catch( LDAPException &le )
-        {
-               L << Logger::Error << m_myname << " Search failed: " << le.what() << endl;
-               throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
-        }
-        catch( std::exception &e )
-        {
-               L << Logger::Error << m_myname << " Caught STL exception for " << m_qname << ": " << e.what() << endl;
-               throw( DBException( "STL exception" ) );
-        }
-
-        return false;
+  QType qt;
+  vector<string> parts;
+  string attrname, qstr;
+
+
+  try
+  {
+    do
+    {
+      while( m_adomain != m_adomains.end() )
+      {
+        while( m_attribute != m_result.end() )
+        {
+          attrname = m_attribute->first;
+          qstr = attrname.substr( 0, attrname.length() - 6 );   // extract qtype string from ldap attribute name
+          qt = const_cast<char*>(toUpper( qstr ).c_str());
+
+          while( m_value != m_attribute->second.end() )
+          {
+            if(m_qtype != qt && m_qtype != QType::ANY) {
+              m_value++;
+              continue;
+            }
+
+
+            rr.qtype = qt;
+            rr.qname = *m_adomain;
+            rr.ttl = m_ttl;
+            rr.last_modified = m_last_modified;
+            rr.content = *m_value;
+            m_value++;
+
+            DLOG( L << Logger::Debug << m_myname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
+            return true;
+          }
+
+          m_attribute++;
+          m_value = m_attribute->second.begin();
+        }
+        m_adomain++;
+        m_attribute = m_result.begin();
+        m_value = m_attribute->second.begin();
+      }
+    }
+    while( m_pldap->getSearchEntry( m_msgid, m_result, m_getdn ) && prepare() );
+
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Warning << m_myname << " Search failed: " << lt.what() << endl;
+    throw( DBException( "LDAP server timeout" ) );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Search failed: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    L << Logger::Error << m_myname << " Caught STL exception for " << m_qname << ": " << e.what() << endl;
+    throw( DBException( "STL exception" ) );
+  }
+
+  return false;
+}
+void LdapBackend::getUpdatedMasters( vector<DomainInfo>* domains )
+{
+  string filter;
+  int msgid;
+  PowerLDAP::sentry_t result;
+  const char* attronly[] = {
+    "associatedDomain",
+    NULL
+  };
+
+  try
+  {
+    // First get all domains on which we are master.
+    filter = strbind( ":target:", "&(SOARecord=*)(PdnsDomainId=*)", getArg( "filter-axfr" ) );
+    msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw( DBException( "LDAP server timeout" ) );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->getUpdatedMasters( domains );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    throw( DBException( "STL exception" ) );
+  }
+
+  while( m_pldap->getSearchEntry( msgid, result ) ) {
+    if( !result.count( "associatedDomain" ) || result["associatedDomain"].empty() )
+      continue;
+
+    DomainInfo di;
+    if ( !getDomainInfo( result["associatedDomain"][0], di ) )
+      continue;
+
+    di.backend = this;
+
+    if( di.notified_serial < di.serial )
+      domains->push_back( di );
+  }
 }
 
 
 
- bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di )
+void LdapBackend::setNotified( uint32_t id, uint32_t serial )
 {
-        string filter;
-        SOAData sd;
-        const char* attronly[] = { "sOARecord", NULL };
-
-
-        // search for SOARecord of domain
-        filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))";
-        m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
-        m_pldap->getSearchEntry( m_msgid, m_result );
+  string filter;
+  int msgid;
+  PowerLDAP::sresult_t results;
+  PowerLDAP::sentry_t entry;
+  const char* attronly[] = { "associatedDomain", NULL };
+
+  try
+  {
+    // Try to find the notified domain
+    filter = strbind( ":target:", "PdnsDomainId=" + std::to_string( id ), getArg( "filter-axfr" ) );
+    msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+    m_pldap->getSearchResults( msgid, results, true );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw( DBException( "LDAP server timeout" ) );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->setNotified( id, serial );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    throw( DBException( "STL exception" ) );
+  }
+
+  if ( results.empty() )
+    throw PDNSException( "No results found when trying to update domain notified_serial for ID " + std::to_string( id ) );
+
+  entry = results.front();
+  string dn = entry["dn"][0];
+  string serialStr = std::to_string( serial );
+  LDAPMod *mods[2];
+  LDAPMod mod;
+  char *vals[2];
+
+  mod.mod_op = LDAP_MOD_REPLACE;
+  mod.mod_type = (char*)"PdnsDomainNotifiedSerial";
+  vals[0] = const_cast<char*>( serialStr.c_str() );
+  vals[1] = NULL;
+  mod.mod_values = vals;
+
+  mods[0] = &mod;
+  mods[1] = NULL;
+
+  try
+  {
+    m_pldap->modify( dn, mods );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->setNotified( id, serial );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    throw( DBException( "STL exception" ) );
+  }
+}
 
-        if( m_result.count( "sOARecord" ) && !m_result["sOARecord"].empty() )
-        {
-               sd.serial = 0;
-               fillSOAData( m_result["sOARecord"][0], sd );
 
-               di.id = 0;
-               di.serial = sd.serial;
-               di.zone = DNSName(domain);
-               di.last_check = 0;
-               di.backend = this;
-               di.kind = DomainInfo::Master;
 
-               return true;
-        }
-
-        return false;
+bool LdapBackend::getDomainInfo( const string& domain, DomainInfo& di )
+{
+  string filter;
+  SOAData sd;
+  int msgid;
+  PowerLDAP::sentry_t result;
+  const char* attronly[] = {
+    "sOARecord",
+    "PdnsDomainId",
+    "PdnsDomainNotifiedSerial",
+    "PdnsDomainLastCheck",
+    "PdnsDomainMaster",
+    "PdnsDomainType",
+    NULL
+  };
+
+  try
+  {
+    // search for SOARecord of domain
+    filter = "(&(associatedDomain=" + toLower( m_pldap->escape( domain ) ) + ")(SOARecord=*))";
+    m_msgid = m_pldap->search( getArg( "basedn" ), LDAP_SCOPE_SUBTREE, filter, attronly );
+    m_pldap->getSearchEntry( msgid, result );
+  }
+  catch( LDAPTimeout &lt )
+  {
+    L << Logger::Warning << m_myname << " Unable to search LDAP directory: " << lt.what() << endl;
+    throw( DBException( "LDAP server timeout" ) );
+  }
+  catch( LDAPNoConnection &lnc )
+  {
+    L << Logger::Warning << m_myname << " Connection to LDAP lost, trying to reconnect" << endl;
+    if ( reconnect() )
+      this->getDomainInfo( domain, di );
+    else
+      throw PDNSException( "Failed to reconnect to LDAP server" );
+  }
+  catch( LDAPException &le )
+  {
+    L << Logger::Error << m_myname << " Unable to search LDAP directory: " << le.what() << endl;
+    throw( PDNSException( "LDAP server unreachable" ) );   // try to reconnect to another server
+  }
+  catch( std::exception &e )
+  {
+    throw( DBException( "STL exception" ) );
+  }
+
+  if( result.count( "sOARecord" ) && !result["sOARecord"].empty() )
+  {
+    sd.serial = 0;
+    fillSOAData( result["sOARecord"][0], sd );
+
+    if ( result.count( "PdnsDomainId" ) && !result["PdnsDomainId"].empty() )
+      di.id = std::stoi( result["PdnsDomainId"][0] );
+    else
+      di.id = 0;
+
+    di.serial = sd.serial;
+    di.zone = DNSName(domain);
+
+    if( result.count( "PdnsDomainLastCheck" ) && !result["PdnsDomainLastCheck"].empty() )
+      di.last_check = pdns_stou( result["PdnsDomainLastCheck"][0] );
+    else
+      di.last_check = 0;
+
+    if ( result.count( "PdnsDomainNotifiedSerial" ) && !result["PdnsDomainNotifiedSerial"].empty() )
+      di.notified_serial = pdns_stou( result["PdnsDomainNotifiedSerial"][0] );
+    else
+      di.notified_serial = 0;
+
+    if ( result.count( "PdnsDomainMaster" ) && !result["PdnsDomainMaster"].empty() )
+      di.masters = result["PdnsDomainMaster"];
+
+    if ( result.count( "PdnsDomainType" ) && !result["PdnsDomainType"].empty() ) {
+      string kind = result["PdnsDomainType"][0];
+      if ( kind == "master" )
+        di.kind = DomainInfo::Master;
+      else if ( kind == "slave" )
+        di.kind = DomainInfo::Slave;
+      else
+        di.kind = DomainInfo::Native;
+    }
+    else {
+      di.kind = DomainInfo::Native;
+    }
+
+    return true;
+  }
+
+  return false;
 }
 
 
@@ -526,32 +773,34 @@ bool LdapBackend::get( DNSResourceRecord &rr )
 
 class LdapFactory : public BackendFactory
 {
-
-public:
-
-        LdapFactory() : BackendFactory( "ldap" ) {}
-
-
-        void declareArguments( const string &suffix="" )
-        {
-               declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
-               declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
-               declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
-               declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" );
-               declare( suffix, "binddn", "User dn for non anonymous binds","" );
-               declare( suffix, "secret", "User password for non anonymous binds", "" );
-               declare( suffix, "timeout", "Seconds before connecting to server fails", "5" );
-               declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
-               declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" );
-               declare( suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", "(:target:)" );
-               declare( suffix, "disable-ptrrecord", "Deprecated, use ldap-method=strict instead", "no" );
-        }
-
-
-        DNSBackend* make( const string &suffix="" )
-        {
-               return new LdapBackend( suffix );
-        }
+  public:
+
+    LdapFactory() : BackendFactory( "ldap" ) {}
+
+    void declareArguments( const string &suffix="" )
+    {
+      declare( suffix, "host", "One or more LDAP server with ports or LDAP URIs (separated by spaces)","ldap://127.0.0.1:389/" );
+      declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
+      declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
+      declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" );
+      declare( suffix, "bindmethod", "Bind method to use (simple or gssapi)", "simple" );
+      declare( suffix, "binddn", "User dn for non anonymous binds","" );
+      declare( suffix, "secret", "User password for non anonymous binds", "" );
+      declare( suffix, "krb5-keytab", "The keytab to use for GSSAPI authentication", "" );
+      declare( suffix, "krb5-ccache", "The credentials cache used for GSSAPI authentication", "" );
+      declare( suffix, "timeout", "Seconds before connecting to server fails", "5" );
+      declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
+      declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" );
+      declare( suffix, "filter-lookup", "LDAP filter for limiting IP or name lookups", "(:target:)" );
+      declare( suffix, "disable-ptrrecord", "Deprecated, use ldap-method=strict instead", "no" );
+      declare( suffix, "reconnect-attempts", "Number of attempts to re-establish a lost LDAP connection", "5" );
+    }
+
+
+    DNSBackend* make( const string &suffix="" )
+    {
+      return new LdapBackend( suffix );
+    }
 };
 
 
@@ -560,19 +809,19 @@ public:
 
 class LdapLoader
 {
-        LdapFactory factory;
+   LdapFactory factory;
 
-public:
+  public:
 
-        LdapLoader()
-        {
-               BackendMakers().report( &factory );
-               L << Logger::Info << "[ldapbackend] This is the ldap backend version " VERSION
+   LdapLoader()
+   {
+     BackendMakers().report( &factory );
+     L << Logger::Info << "[ldapbackend] This is the ldap backend version " VERSION
 #ifndef REPRODUCIBLE
-                 << " (" __DATE__ " " __TIME__ ")"
+       << " (" __DATE__ " " __TIME__ ")"
 #endif
-                 << " reporting" << endl;
-        }
+       << " reporting" << endl;
+   }
 };
 
 
index 0b12bb459d74280658fb394cc01404babf511875..f3e584c8dfb289b7fe319c76f5a75ebac3fac245 100644 (file)
@@ -45,7 +45,7 @@
 using std::string;
 using std::vector;
 
-
+class LdapAuthenticator;
 
 /*
  *  Known DNS RR types
@@ -53,92 +53,101 @@ using std::vector;
  */
 
 static const char* ldap_attrany[] = {
-        "associatedDomain",
-        "dNSTTL",
-        "aRecord",
-        "nSRecord",
-        "cNAMERecord",
-        "sOARecord",
-        "pTRRecord",
-        "hInfoRecord",
-        "mXRecord",
-        "tXTRecord",
-        "rPRecord",
-        "aFSDBRecord",
-//        "SigRecord",
-        "KeyRecord",
-//        "gPosRecord",
-        "aAAARecord",
-        "lOCRecord",
-        "sRVRecord",
-        "nAPTRRecord",
-        "kXRecord",
-        "certRecord",
-//        "a6Record",
-//        "dNameRecord",
-//        "aPLRecord",
-        "dSRecord",
-        "sSHFPRecord",
-        "iPSecKeyRecord",
-        "rRSIGRecord",
-        "nSECRecord",
-        "dNSKeyRecord",
-        "dHCIDRecord",
-        "sPFRecord",
-        "TYPE65534Record",
-        "EUI48Record",
-        "EUI64Record",
-        "TYPE65226Record",
-        "modifyTimestamp",
-        NULL
+  "associatedDomain",
+  "dNSTTL",
+  "aRecord",
+  "nSRecord",
+  "cNAMERecord",
+  "sOARecord",
+  "pTRRecord",
+  "hInfoRecord",
+  "mXRecord",
+  "tXTRecord",
+  "rPRecord",
+  "aFSDBRecord",
+//  "SigRecord",
+  "KeyRecord",
+//  "gPosRecord",
+  "aAAARecord",
+  "lOCRecord",
+  "sRVRecord",
+  "nAPTRRecord",
+  "kXRecord",
+  "certRecord",
+//  "a6Record",
+//  "dNameRecord",
+//  "aPLRecord",
+  "dSRecord",
+  "sSHFPRecord",
+  "iPSecKeyRecord",
+  "rRSIGRecord",
+  "nSECRecord",
+  "dNSKeyRecord",
+  "dHCIDRecord",
+  "sPFRecord",
+  "TYPE65534Record",
+  "EUI48Record",
+  "EUI64Record",
+  "TYPE65226Record",
+  "modifyTimestamp",
+  NULL
 };
 
 
 
 class LdapBackend : public DNSBackend
 {
-        bool m_getdn;
-        bool m_qlog;
-        int m_msgid;
-        uint32_t m_ttl;
-        uint32_t m_default_ttl;
-        unsigned int m_axfrqlen;
-        time_t m_last_modified;
-        string m_myname;
-        DNSName m_qname;
-        PowerLDAP* m_pldap;
-        PowerLDAP::sentry_t m_result;
-        PowerLDAP::sentry_t::iterator m_attribute;
-        vector<string>::iterator m_value;
-        vector<DNSName>::iterator m_adomain;
-        vector<DNSName> m_adomains;
-        QType m_qtype;
-
-        bool (LdapBackend::*m_list_fcnt)( const DNSName&, int );
-        void (LdapBackend::*m_lookup_fcnt)( const QType&, const DNSName&, DNSPacket*, int );
-        bool (LdapBackend::*m_prepare_fcnt)();
-
-        bool list_simple( const DNSName& target, int domain_id );
-        bool list_strict( const DNSName& target, int domain_id );
-
-        void lookup_simple( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
-        void lookup_strict( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
-        void lookup_tree( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
-
-        bool prepare();
-        bool prepare_simple();
-        bool prepare_strict();
-
-        bool getDomainInfo( const string& domain, DomainInfo& di );
-
-public:
-
-        LdapBackend( const string &suffix="" );
-        ~LdapBackend();
-
-        bool list( const DNSName& target, int domain_id, bool include_disabled=false );
-        void lookup( const QType& qtype, const DNSName& qdomain, DNSPacket* p = 0, int zoneid = -1 );
-        bool get( DNSResourceRecord& rr );
+    bool m_getdn;
+    bool m_qlog;
+    int m_msgid;
+    uint32_t m_ttl;
+    uint32_t m_default_ttl;
+    unsigned int m_axfrqlen;
+    time_t m_last_modified;
+    string m_myname;
+    DNSName m_qname;
+    PowerLDAP* m_pldap;
+    LdapAuthenticator *m_authenticator;
+    PowerLDAP::sentry_t m_result;
+    PowerLDAP::sentry_t::iterator m_attribute;
+    vector<string>::iterator m_value;
+    vector<DNSName>::iterator m_adomain;
+    vector<DNSName> m_adomains;
+    QType m_qtype;
+    int m_reconnect_attempts;
+
+    bool (LdapBackend::*m_list_fcnt)( const DNSName&, int );
+    void (LdapBackend::*m_lookup_fcnt)( const QType&, const DNSName&, DNSPacket*, int );
+    bool (LdapBackend::*m_prepare_fcnt)();
+
+    bool list_simple( const DNSName& target, int domain_id );
+    bool list_strict( const DNSName& target, int domain_id );
+
+    void lookup_simple( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
+    void lookup_strict( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
+    void lookup_tree( const QType& qtype, const DNSName& qdomain, DNSPacket* p, int zoneid );
+
+    bool prepare();
+    bool prepare_simple();
+    bool prepare_strict();
+
+    bool reconnect();
+
+  public:
+
+    LdapBackend( const string &suffix="" );
+    ~LdapBackend();
+
+    // Native backend
+    bool list( const DNSName& target, int domain_id, bool include_disabled=false );
+    void lookup( const QType& qtype, const DNSName& qdomain, DNSPacket* p = 0, int zoneid = -1 );
+    bool get( DNSResourceRecord& rr );
+
+    bool getDomainInfo( const string& domain, DomainInfo& di );
+
+    // Master backend
+    void getUpdatedMasters( vector<DomainInfo>* domains );
+    void setNotified( uint32_t id, uint32_t serial );
 };
 
 #endif /* LDAPBACKEND_HH */
diff --git a/modules/ldapbackend/ldaputils.cc b/modules/ldapbackend/ldaputils.cc
new file mode 100644 (file)
index 0000000..92d888b
--- /dev/null
@@ -0,0 +1,49 @@
+#include "ldaputils.hh"
+#include <sys/time.h>
+
+void ldapSetOption( LDAP *conn, int option, void *value )
+{
+  if( ldap_set_option( conn, option, value ) != LDAP_OPT_SUCCESS )
+  {
+    throw( LDAPException( "Unable to set option" ) );
+  }
+}
+
+void ldapGetOption( LDAP *conn, int option, void *value )
+{
+  if( ldap_get_option( conn, option, value ) != LDAP_OPT_SUCCESS )
+  {
+    throw( LDAPException( "Unable to get option" ) );
+  }
+}
+
+std::string ldapGetError( LDAP *conn, int code )
+{
+  if ( code == -1 )
+    ldapGetOption( conn, LDAP_OPT_ERROR_NUMBER, &code );
+  return std::string( ldap_err2string( code ) );
+}
+
+int ldapWaitResult( LDAP *conn, int msgid, int timeout, LDAPMessage** result )
+{
+  struct timeval tv;
+  LDAPMessage* res;
+
+
+  tv.tv_sec = timeout;
+  tv.tv_usec = 0;
+
+  int rc = ldap_result( conn, msgid, LDAP_MSG_ONE, &tv, &res );
+
+  if ( rc == -1 || rc == 0 )
+    return rc;
+
+  if( result == NULL )
+  {
+    ldap_msgfree( res );
+    return rc;
+  }
+
+  *result = res;
+  return rc;
+}
diff --git a/modules/ldapbackend/ldaputils.hh b/modules/ldapbackend/ldaputils.hh
new file mode 100644 (file)
index 0000000..ff26af3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ *  PowerDNS LDAP Connector
+ *  By PowerDNS.COM BV
+ *  By Norbert Sendetzky <norbert@linuxnetworks.de> (2003-2007)
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "exceptions.hh"
+
+#include <ldap.h>
+#include <string>
+
+#ifndef LDAPUTILS_HH
+#define LDAPUTILS_HH
+
+void ldapSetOption( LDAP *conn, int option, void *value );
+
+void ldapGetOption( LDAP *conn, int option, void *value );
+
+std::string ldapGetError( LDAP *conn, int code );
+
+int ldapWaitResult( LDAP *conn, int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
+
+#endif // LDAPUTILS_HH
index 5b184e137ee186f14bf3145997164951e0151048..e7f6cc3cb7050c8da820bf67213ad290cee3606f 100644 (file)
@@ -23,6 +23,9 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#include "exceptions.hh"
+#include "ldapauthenticator.hh"
+#include "ldaputils.hh"
 #include "powerldap.hh"
 #include "pdns/misc.hh"
 #include <sys/time.h>
 
 PowerLDAP::PowerLDAP( const string& hosts, uint16_t port, bool tls )
 {
-        d_ld = 0;
-        d_hosts = hosts;
-        d_port = port;
-        d_tls = tls;
-        ensureConnect();
+  d_ld = 0;
+  d_hosts = hosts;
+  d_port = port;
+  d_tls = tls;
+  ensureConnect();
 }
 
 void PowerLDAP::ensureConnect()
 {
-        int err;
+  int err;
 
-        if(d_ld) {
-          ldap_unbind_ext( d_ld, NULL, NULL );
-        }
+  if(d_ld) {
+    ldap_unbind_ext( d_ld, NULL, NULL );
+  }
 
 #ifdef HAVE_LDAP_INITIALIZE
-        if( ( err = ldap_initialize( &d_ld, d_hosts.c_str() ) ) != LDAP_SUCCESS )
-        {
-               string ldapuris;
-               vector<string> uris;
-               stringtok( uris, d_hosts );
-
-               for( size_t i = 0; i < uris.size(); i++ )
-               {
-                       ldapuris += " ldap://" + uris[i];
-               }
-
-               if( ( err = ldap_initialize( &d_ld, ldapuris.c_str() ) ) != LDAP_SUCCESS )
-               {
-                               throw LDAPException( "Error initializing LDAP connection to '" + ldapuris + ": " + getError( err ) );
-               }
-        }
+  if( ( err = ldap_initialize( &d_ld, d_hosts.c_str() ) ) != LDAP_SUCCESS )
+  {
+    string ldapuris;
+    vector<string> uris;
+    stringtok( uris, d_hosts );
+
+    for( size_t i = 0; i < uris.size(); i++ )
+    {
+      ldapuris += " ldap://" + uris[i];
+    }
+
+    if( ( err = ldap_initialize( &d_ld, ldapuris.c_str() ) ) != LDAP_SUCCESS )
+    {
+        throw LDAPException( "Error initializing LDAP connection to '" + ldapuris + ": " + getError( err ) );
+    }
+  }
 #else
-        if( ( d_ld = ldap_init( d_hosts.c_str(), d_port ) ) == NULL )
-        {
-               throw LDAPException( "Error initializing LDAP connection to '" + d_hosts + "': " + string( strerror( errno ) ) );
-        }
+  if( ( d_ld = ldap_init( d_hosts.c_str(), d_port ) ) == NULL )
+  {
+    throw LDAPException( "Error initializing LDAP connection to '" + d_hosts + "': " + string( strerror( errno ) ) );
+  }
 #endif
 
-        int protocol = LDAP_VERSION3;
-        if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
-        {
-               protocol = LDAP_VERSION2;
-               if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
-               {
-                       ldap_unbind_ext( d_ld, NULL, NULL );
-                       throw LDAPException( "Couldn't set protocol version to LDAPv3 or LDAPv2" );
-               }
-        }
-
-        if( d_tls && ( err = ldap_start_tls_s( d_ld, NULL, NULL ) ) != LDAP_SUCCESS )
-        {
-               ldap_unbind_ext( d_ld, NULL, NULL );
-               throw LDAPException( "Couldn't perform STARTTLS: " + getError( err ) );
-        }
+  int protocol = LDAP_VERSION3;
+  if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
+  {
+    protocol = LDAP_VERSION2;
+    if( ldap_set_option( d_ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ) != LDAP_OPT_SUCCESS )
+    {
+      ldap_unbind_ext( d_ld, NULL, NULL );
+      throw LDAPException( "Couldn't set protocol version to LDAPv3 or LDAPv2" );
+    }
+  }
+
+  if( d_tls && ( err = ldap_start_tls_s( d_ld, NULL, NULL ) ) != LDAP_SUCCESS )
+  {
+    ldap_unbind_ext( d_ld, NULL, NULL );
+    throw LDAPException( "Couldn't perform STARTTLS: " + getError( err ) );
+  }
 }
 
 
 PowerLDAP::~PowerLDAP()
 {
-        ldap_unbind_ext( d_ld, NULL, NULL );
+  ldap_unbind_ext( d_ld, NULL, NULL );
+}
+
+
+bool PowerLDAP::connect()
+{
+  try
+  {
+    ensureConnect();
+    return true;
+  }
+  catch( LDAPException &le )
+  {
+    return false;
+  }
 }
 
 
 void PowerLDAP::setOption( int option, int value )
 {
-        if( ldap_set_option( d_ld, option, (void*) &value ) != LDAP_OPT_SUCCESS )
-        {
-               throw( LDAPException( "Unable to set option" ) );
-        }
+  ldapSetOption( d_ld, option, (void*) &value );
 }
 
 
 void PowerLDAP::getOption( int option, int *value )
 {
-        if( ldap_get_option( d_ld, option, (void*) value ) != LDAP_OPT_SUCCESS )
-        {
-               throw( LDAPException( "Unable to get option" ) );
-        }
+  ldapGetOption( d_ld, option, (void*) value );
+}
+
+
+void PowerLDAP::bind( LdapAuthenticator* authenticator )
+{
+  if ( !authenticator->authenticate( d_ld ) )
+    throw LDAPException( "Failed to bind to LDAP server: " + authenticator->getError() );
 }
 
 
 void PowerLDAP::bind( const string& ldapbinddn, const string& ldapsecret, int method, int timeout )
 {
-        int msgid;
+  int msgid;
 
 #ifdef HAVE_LDAP_SASL_BIND
-        int rc;
-        struct berval passwd;
+  int rc;
+  struct berval passwd;
 
-        passwd.bv_val = (char *)ldapsecret.c_str();
-        passwd.bv_len = strlen( passwd.bv_val );
+  passwd.bv_val = (char *)ldapsecret.c_str();
+  passwd.bv_len = strlen( passwd.bv_val );
 
-        if( ( rc = ldap_sasl_bind( d_ld, ldapbinddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid ) ) != LDAP_SUCCESS )
-        {
-               throw LDAPException( "Failed to bind to LDAP server: " + getError( rc ) );
-        }
+  if( ( rc = ldap_sasl_bind( d_ld, ldapbinddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid ) ) != LDAP_SUCCESS )
+  {
+    throw LDAPException( "Failed to bind to LDAP server: " + getError( rc ) );
+  }
 #else
-        if( ( msgid = ldap_bind( d_ld, ldapbinddn.c_str(), ldapsecret.c_str(), method ) ) == -1 )
-        {
-               throw LDAPException( "Failed to bind to LDAP server: " + getError( msgid ) );
-        }
+  if( ( msgid = ldap_bind( d_ld, ldapbinddn.c_str(), ldapsecret.c_str(), method ) ) == -1 )
+  {
+    throw LDAPException( "Failed to bind to LDAP server: " + getError( msgid ) );
+  }
 #endif
 
-        waitResult( msgid, timeout, NULL );
+  waitResult( msgid, timeout, NULL );
 }
 
 
@@ -145,20 +163,31 @@ void PowerLDAP::bind( const string& ldapbinddn, const string& ldapsecret, int me
 
 void PowerLDAP::simpleBind( const string& ldapbinddn, const string& ldapsecret )
 {
-        this->bind( ldapbinddn, ldapsecret, LDAP_AUTH_SIMPLE, 30 );
+  this->bind( ldapbinddn, ldapsecret, LDAP_AUTH_SIMPLE, 30 );
+}
+
+
+void PowerLDAP::modify( const string &dn, LDAPMod *mods[], LDAPControl **scontrols, LDAPControl **ccontrols )
+{
+  int rc;
+
+  rc = ldap_modify_ext_s( d_ld, dn.c_str(), mods, scontrols, ccontrols );
+  if ( rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR )
+    throw LDAPNoConnection();
+  else if ( rc != LDAP_SUCCESS )
+    throw LDAPException( "Error modifying LDAP entry " + dn + ": " + getError( rc ) );
 }
 
 
 int PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr )
 {
-        int msgid, rc;
+  int msgid, rc;
 
-        if( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) != LDAP_SUCCESS )
-        {
-               throw LDAPException( "Starting LDAP search: " + getError( rc ) );
-        }
+  if ( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) ) {
+    throw LDAPException( "Starting LDAP search: " + getError( rc ) );
+  }
 
-        return msgid;
+  return msgid;
 }
 
 
@@ -170,144 +199,135 @@ int PowerLDAP::search( const string& base, int scope, const string& filter, cons
 
 int PowerLDAP::waitResult( int msgid, int timeout, LDAPMessage** result )
 {
-        struct timeval tv;
-        LDAPMessage* res;
-
-        tv.tv_sec = timeout;
-        tv.tv_usec = 0;
-        int rc;
-      
-        rc = ldap_result( d_ld, msgid, LDAP_MSG_ONE, &tv, &res );
-
-        switch( rc )
-        {
-               case -1:
-            ensureConnect();
-                       throw LDAPException( "Error waiting for LDAP result: " + getError() );
-               case 0:
-                       throw LDAPTimeout();
-        }
-
-        if( result == NULL )
-        {
-               ldap_msgfree( res );
-               return rc;
-        }
-
-        *result = res;
-        return rc;
+  return ldapWaitResult( d_ld, msgid, timeout, result );
 }
 
 
 bool PowerLDAP::getSearchEntry( int msgid, sentry_t& entry, bool dn, int timeout )
 {
-        int i;
-        char* attr;
-        BerElement* ber;
-        struct berval** berval;
-        vector<string> values;
-        LDAPMessage* result;
-        LDAPMessage* object;
-
-
-        if( ( i = waitResult( msgid, timeout, &result ) ) == LDAP_RES_SEARCH_RESULT )
-        {
-               ldap_msgfree( result );
-               return false;
-        }
-
-        if( i != LDAP_RES_SEARCH_ENTRY )
+  int i;
+  char* attr;
+  BerElement* ber;
+  struct berval** berval;
+  vector<string> values;
+  LDAPMessage* result;
+  LDAPMessage* object;
+  bool hasResult = false;
+
+  while ( !hasResult ) {
+    i = waitResult( msgid, timeout, &result );
+    // Here we deliberately ignore LDAP_RES_SEARCH_REFERENCE as we don't follow them.
+    // Instead we get the next result.
+    // If the function returned an error (i <= 0) we'll deal with after this loop too.
+    if ( i == LDAP_RES_SEARCH_ENTRY || i == LDAP_RES_SEARCH_RESULT || i <= 0 )
+      hasResult = true;
+  }
+
+  if ( i == -1 ) {
+    // Error while retrieving the message
+    int err_code;
+    ldapGetOption( d_ld, LDAP_OPT_ERROR_NUMBER, &err_code );
+    if ( err_code == LDAP_SERVER_DOWN || err_code == LDAP_CONNECT_ERROR )
+      throw LDAPNoConnection();
+    else
+      throw LDAPException( "PowerLDAP::getSearchEntry(): Error when retrieving LDAP result: " + getError( err_code ) );
+  }
+
+  if ( i == 0 ) {
+    // Timeout expired before the message could be retrieved
+    throw LDAPTimeout();
+  }
+
+  if ( i == LDAP_RES_SEARCH_RESULT ) {
+    // We're done with this request
+    ldap_msgfree( result );
+    return false;
+  }
+
+  if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
+  {
+    ldap_msgfree( result );
+    throw LDAPException( "Couldn't get first result entry: " + getError() );
+  }
+
+  entry.clear();
+
+  if( dn )
+  {
+    attr = ldap_get_dn( d_ld, object );
+    values.push_back( string( attr ) );
+    ldap_memfree( attr );
+    entry["dn"] = values;
+  }
+
+  if( ( attr = ldap_first_attribute( d_ld, object, &ber ) ) != NULL )
+  {
+    do
+    {
+      if( ( berval = ldap_get_values_len( d_ld, object, attr ) ) != NULL )
+      {
+        values.clear();
+        for( i = 0; i < ldap_count_values_len( berval ); i++ )
         {
-               ldap_msgfree( result );
-               throw LDAPException( "Search returned an unexpected result" );
+          values.push_back( berval[i]->bv_val );   // use berval[i]->bv_len for non string values?
         }
 
-        if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
-        {
-               ldap_msgfree( result );
-               throw LDAPException( "Couldn't get first result entry: " + getError() );
-        }
-
-        entry.clear();
-
-        if( dn )
-        {
-               attr = ldap_get_dn( d_ld, object );
-               values.push_back( string( attr ) );
-               ldap_memfree( attr );
-               entry["dn"] = values;
-        }
+        entry[attr] = values;
+        ldap_value_free_len( berval );
+      }
+      ldap_memfree( attr );
+    }
+    while( ( attr = ldap_next_attribute( d_ld, object, ber ) ) != NULL );
 
-        if( ( attr = ldap_first_attribute( d_ld, object, &ber ) ) != NULL )
-        {
-               do
-               {
-                       if( ( berval = ldap_get_values_len( d_ld, object, attr ) ) != NULL )
-                       {
-                               values.clear();
-                               for( i = 0; i < ldap_count_values_len( berval ); i++ )
-                               {
-                                       values.push_back( berval[i]->bv_val );   // use berval[i]->bv_len for non string values?
-                               }
-
-                               entry[attr] = values;
-                               ldap_value_free_len( berval );
-                       }
-                       ldap_memfree( attr );
-               }
-               while( ( attr = ldap_next_attribute( d_ld, object, ber ) ) != NULL );
-
-               ber_free( ber, 0 );
-        }
+    ber_free( ber, 0 );
+  }
 
-        ldap_msgfree( result );
-        return true;
+  ldap_msgfree( result );
+  return true;
 }
 
 
 void PowerLDAP::getSearchResults( int msgid, sresult_t& result, bool dn, int timeout )
 {
-        sentry_t entry;
+  sentry_t entry;
 
-        result.clear();
-        while( getSearchEntry( msgid, entry, dn, timeout ) )
-        {
-               result.push_back( entry );
-        }
+  result.clear();
+  while( getSearchEntry( msgid, entry, dn, timeout ) )
+  {
+    result.push_back( entry );
+  }
 }
 
 
 const string PowerLDAP::getError( int rc )
 {
-        if( rc == -1 ) { getOption( LDAP_OPT_ERROR_NUMBER, &rc ); }
-
-        return string( ldap_err2string( rc ) );;
+  return ldapGetError( d_ld, rc );
 }
 
 
 const string PowerLDAP::escape( const string& str )
 {
-        string a;
-        string::const_iterator i;
-        char tmp[4];
-
-        for( i = str.begin(); i != str.end(); i++ )
-        {
-            // RFC4515 3
-            if( *i == '*' ||
-                *i == '(' ||
-                *i == ')' ||
-                *i == '\\' ||
-                *i == '\0' ||
-                *i > 127)
-            {
-                sprintf(tmp,"\\%02x", (unsigned char)*i);
-
-                a += tmp;
-            }
-            else
-                a += *i;
-        }
-
-        return a;
+  string a;
+  string::const_iterator i;
+  char tmp[4];
+
+  for( i = str.begin(); i != str.end(); i++ )
+  {
+      // RFC4515 3
+      if( *i == '*' ||
+          *i == '(' ||
+          *i == ')' ||
+          *i == '\\' ||
+          *i == '\0' ||
+          *i > 127)
+      {
+          sprintf(tmp,"\\%02x", (unsigned char)*i);
+
+          a += tmp;
+      }
+      else
+          a += *i;
+  }
+
+  return a;
 }
index 18cc25c9966b421bb0b07652bf47d8f34435a1dd..27e2f032c0700aee949586d471da8796970d1cb9 100644 (file)
@@ -23,7 +23,6 @@
 #include <map>
 #include <string>
 #include <vector>
-#include <exception>
 #include <stdexcept>
 #include <inttypes.h>
 #include <errno.h>
@@ -40,47 +39,41 @@ using std::map;
 using std::string;
 using std::vector;
 
-class LDAPException : public std::runtime_error
-{
-public:
-        explicit LDAPException( const string &str ) : std::runtime_error( str ) {}
-};
-
-class LDAPTimeout : public LDAPException
-{
-public:
-        explicit LDAPTimeout() : LDAPException( "Timeout" ) {}
-};
+class LdapAuthenticator;
 
 class PowerLDAP
 {
-        LDAP* d_ld;
-        string d_hosts;
-        int d_port;
-        bool d_tls;
-
-        const string getError( int rc = -1 );
-        int waitResult( int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
-        void ensureConnect();
-        
-public:
-        typedef map<string, vector<string> > sentry_t;
-        typedef vector<sentry_t> sresult_t;
-
-        PowerLDAP( const string& hosts = "ldap://127.0.0.1/", uint16_t port = LDAP_PORT, bool tls = false );
-        ~PowerLDAP();
-
-        void getOption( int option, int* value );
-        void setOption( int option, int value );
-
-        void bind( const string& ldapbinddn = "", const string& ldapsecret = "", int method = LDAP_AUTH_SIMPLE, int timeout = 5 );
-        void simpleBind( const string& ldapbinddn = "", const string& ldapsecret = "" );
-        int search( const string& base, int scope, const string& filter, const char** attr = 0 );
-
-        bool getSearchEntry( int msgid, sentry_t& entry, bool dn = false, int timeout = 5 );
-        void getSearchResults( int msgid, sresult_t& result, bool dn = false, int timeout = 5 );
-
-        static const string escape( const string& tobe );
+    LDAP* d_ld;
+    string d_hosts;
+    int d_port;
+    bool d_tls;
+  
+    const string getError( int rc = -1 );
+    int waitResult( int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
+    void ensureConnect();
+    
+  public:
+    typedef map<string, vector<string> > sentry_t;
+    typedef vector<sentry_t> sresult_t;
+  
+    PowerLDAP( const string& hosts = "ldap://127.0.0.1/", uint16_t port = LDAP_PORT, bool tls = false );
+    ~PowerLDAP();
+  
+    bool connect();
+  
+    void getOption( int option, int* value );
+    void setOption( int option, int value );
+  
+    void bind( LdapAuthenticator *authenticator );
+    void bind( const string& ldapbinddn = "", const string& ldapsecret = "", int method = LDAP_AUTH_SIMPLE, int timeout = 5 );
+    void simpleBind( const string& ldapbinddn = "", const string& ldapsecret = "" );
+    int search( const string& base, int scope, const string& filter, const char** attr = 0 );
+    void modify( const string& dn, LDAPMod *mods[], LDAPControl **scontrols = 0, LDAPControl **ccontrols = 0 );
+  
+    bool getSearchEntry( int msgid, sentry_t& entry, bool dn = false, int timeout = 5 );
+    void getSearchResults( int msgid, sresult_t& result, bool dn = false, int timeout = 5 );
+  
+    static const string escape( const string& tobe );
 };
 
 
index cc15d23657eb863011f3da4f445dfb7c5e1420d0..902c3d53251d9cebd07abe04b342835b8037cee3 100644 (file)
@@ -36,123 +36,123 @@ using std::vector;
 
 inline string ptr2ip4( vector<string>& parts )
 {
-        string ip;
-        parts.pop_back();
-        parts.pop_back();
+  string ip;
+  parts.pop_back();
+  parts.pop_back();
 
 
-        ip = parts.back();
-        parts.pop_back();
+  ip = parts.back();
+  parts.pop_back();
 
-        while( !parts.empty() )
-        {
-               ip += "." + parts.back();
-               parts.pop_back();
-        }
+  while( !parts.empty() )
+  {
+    ip += "." + parts.back();
+    parts.pop_back();
+  }
 
-        return ip;
+  return ip;
 }
 
 
 inline string ptr2ip6( vector<string>& parts )
 {
-        int i = 0;
-        string ip;
-
-
-        parts.pop_back();
-        parts.pop_back();
-
-        while( i < 3 && parts.size() > 1 && parts.back() == "0" )
-        {
-               parts.pop_back();
-               i++;
-        }
-
-        while( i++ < 4 && !parts.empty() )
-        {
-               ip += parts.back();
-               parts.pop_back();
-        }
-
-        while( !parts.empty() )
-        {
-               i = 0;
-               ip += ":";
-
-               while( i < 3 && parts.size() > 1 && parts.back() == "0" )
-               {
-                       parts.pop_back();
-                       i++;
-               }
-
-               while( i++ < 4 && !parts.empty() )
-               {
-                       ip += parts.back();
-                       parts.pop_back();
-               }
-        }
-
-        return ip;
+  int i = 0;
+  string ip;
+
+
+  parts.pop_back();
+  parts.pop_back();
+
+  while( i < 3 && parts.size() > 1 && parts.back() == "0" )
+  {
+    parts.pop_back();
+    i++;
+  }
+
+  while( i++ < 4 && !parts.empty() )
+  {
+    ip += parts.back();
+    parts.pop_back();
+  }
+
+  while( !parts.empty() )
+  {
+    i = 0;
+    ip += ":";
+
+    while( i < 3 && parts.size() > 1 && parts.back() == "0" )
+    {
+      parts.pop_back();
+      i++;
+    }
+
+    while( i++ < 4 && !parts.empty() )
+    {
+      ip += parts.back();
+      parts.pop_back();
+    }
+  }
+
+  return ip;
 }
 
 
 inline string ip2ptr4( const string& ip )
 {
-        string ptr;
-        vector<string> parts;
+  string ptr;
+  vector<string> parts;
 
-        stringtok( parts, ip, "." );
-        while( !parts.empty() )
-        {
-               ptr += parts.back() +  ".";
-               parts.pop_back();
-        }
+  stringtok( parts, ip, "." );
+  while( !parts.empty() )
+  {
+    ptr += parts.back() +  ".";
+    parts.pop_back();
+  }
 
-        return ptr + "in-addr.arpa";
+  return ptr + "in-addr.arpa";
 }
 
 
 inline string ip2ptr6( const string& ip )
 {
-        string ptr, part, defstr;
-        vector<string> parts;
-
-        stringtok( parts, ip, ":" );
-        while( !parts.empty() )
-        {
-               defstr = "0.0.0.0.";
-               part = parts.back();
-
-               while( part.length() < 4 )
-               {
-                       part = "0" + part;
-               }
-
-               defstr[0] = part[3];
-               defstr[2] = part[2];
-               defstr[4] = part[1];
-               defstr[6] = part[0];
-               ptr += defstr;
-               parts.pop_back();
-        }
-
-        return ptr + "ip6.arpa";
+  string ptr, part, defstr;
+  vector<string> parts;
+
+  stringtok( parts, ip, ":" );
+  while( !parts.empty() )
+  {
+    defstr = "0.0.0.0.";
+    part = parts.back();
+
+    while( part.length() < 4 )
+    {
+      part = "0" + part;
+    }
+
+    defstr[0] = part[3];
+    defstr[2] = part[2];
+    defstr[4] = part[1];
+    defstr[6] = part[0];
+    ptr += defstr;
+    parts.pop_back();
+  }
+
+  return ptr + "ip6.arpa";
 }
 
 
 inline string strbind( const string& search, const string& replace, string subject )
 {
-        size_t pos = 0;
+  size_t pos = 0;
 
 
-        while( ( pos = subject.find( search, pos ) ) != string::npos )
-        {
-               subject.replace( pos, search.size(), replace );
-               pos += replace.size();
-        }
+  while( ( pos = subject.find( search, pos ) ) != string::npos )
+  {
+    subject.replace( pos, search.size(), replace );
+    pos += replace.size();
+  }
 
-        return subject;
+  return subject;
 }
 
 /*
@@ -161,17 +161,17 @@ inline string strbind( const string& search, const string& replace, string subje
 
 inline time_t str2tstamp( const string& str )
 {
-        char* tmp;
-        struct tm tm;
+  char* tmp;
+  struct tm tm;
 
-        tmp =  strptime( str.c_str(), "%Y%m%d%H%M%SZ", &tm );
+  tmp =  strptime( str.c_str(), "%Y%m%d%H%M%SZ", &tm );
 
-        if( tmp != NULL && *tmp == 0 )
-        {
-               return Utility::timegm( &tm );
-        }
+  if( tmp != NULL && *tmp == 0 )
+  {
+    return Utility::timegm( &tm );
+  }
 
-        return 0;
+  return 0;
 }
 
 #endif
index e965c51b42abaffb6e500baa5e8f063cf32881d8..19eef3825e346030b641779f8c392c64ef453fb9 100644 (file)
@@ -95,6 +95,8 @@ BUILT_SOURCES = ../../pdns/dnslabeltext.cc
 
 libtestremotebackend_la_SOURCES = \
        ../../pdns/arguments.hh ../../pdns/arguments.cc \
+       ../../pdns/auth-packetcache.cc ../../pdns/auth-packetcache.hh \
+       ../../pdns/auth-querycache.cc ../../pdns/auth-querycache.hh \
        ../../pdns/base32.cc \
        ../../pdns/base64.cc \
        ../../pdns/dnsbackend.hh ../../pdns/dnsbackend.cc \
@@ -105,11 +107,12 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/dnsrecords.cc \
        ../../pdns/dnssecinfra.cc \
        ../../pdns/ednssubnet.cc \
+       ../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
        ../../pdns/iputils.cc \
        ../../pdns/logger.cc \
        ../../pdns/misc.cc \
        ../../pdns/nsecrecords.cc \
-       ../../pdns/packetcache.hh ../../pdns/packetcache.cc \
+       ../../pdns/packetcache.hh \
        ../../pdns/qtype.cc \
        ../../pdns/sillyrecords.cc \
        ../../pdns/statbag.cc \
index 97fa3c141ab294997a614de55c6d02eca3498372..e92d4bbe4c999b8db7ddb7525feb05b5df7b235c 100644 (file)
 #include "pdns/arguments.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 ArgvMap &arg()
 {
   static ArgvMap arg;
index 1e341fb0e080903b6a8e7235ec37f1a95508ec10..fb56ee10a13441758588b8e847b019b6c2884806 100644 (file)
 #include "pdns/arguments.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 ArgvMap &arg()
 {
   static ArgvMap arg;
index 80876d237619e95a1fbb24e3a0a37e912027d5aa..8d4ca4ea4356bd4d2f91f389a115851acf1c6872 100644 (file)
 #include "pdns/dnsrecords.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 ArgvMap &arg()
 {
   static ArgvMap arg;
index a3858317ed56a9e1afdfdfcd6c1d3c5fdfefaa97..785fb6461b0c8bf5f52cee52328995866b2b5f76 100644 (file)
 #include "pdns/arguments.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 ArgvMap &arg()
 {
   static ArgvMap arg;
index b9fd94abe05862539b7dc3517b222c9046d5c13d..bf3f9da20dd31079d08fc3dca82015e6ccc74a0c 100644 (file)
 #include "pdns/dnsrecords.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 ArgvMap &arg()
 {
   static ArgvMap arg;
index 7015c04c31552249969763c6849324c7c5bfdbe9..9a08fc8fd5e780c797c7e1e1c58c515115b9e900 100644 (file)
 #include "pdns/dnsrecords.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
+#include "pdns/auth-packetcache.hh"
+#include "pdns/auth-querycache.hh"
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 ArgvMap &arg()
 {
   static ArgvMap arg;
index 96596cc5b3de1c9888ec2b075fbc93403502a09a..42e50ff30327a8e305f01ac4aa761469e54db912 100644 (file)
@@ -38,7 +38,6 @@
 #include "pdns/arguments.hh"
 #include "pdns/json.hh"
 #include "pdns/statbag.hh"
-#include "pdns/packetcache.hh"
 
 #include "test-remotebackend-keys.hh"
 
index 77bfec458bd051b5b584bfb8a0e4d9080ed20c9a..40f1f03d5e2ebaf6dc2898e244682c27744a096d 100644 (file)
@@ -1,24 +1,18 @@
 #2847484148 auto axfr-get
-Zexample.com:ns1.example.com.:ahu.example.com.:2847484148:28800:7200:604800:86400:100000
+&dsdelegation.example.com::ns.example.com.:120
 &example.com::ns1.example.com.:120
 &example.com::ns2.example.com.:120
-@example.com::smtp-servers.example.com.:10:120
-@example.com::smtp-servers.test.com.:15:120
-+double.example.com:192.168.5.1:120
-&dsdelegation.example.com::ns.example.com.:120
-:dsdelegation.example.com:43:m\341\010\001\312\361\352\256\315\253\347afpx\217\220\042EK\365\375\237\332:120
-:escapedtext.example.com:16:\005begin\022the\040\042middle\042\040p\134art\007the\040end:120
-Cexternal.example.com:somewhere.else.net.:120
-@external-mail.example.com::server1.test.com.:25:120
 &france.example.com::ns1.otherprovider.net.:120
 &france.example.com::ns2.otherprovider.net.:120
-:google-alias.example.com:65401:\023google-public-dns-a\006google\300\024:120
+&italy.example.com::italy-ns1.example.com.:120
+&italy.example.com::italy-ns2.example.com.:120
+&usa.example.com::usa-ns1.usa.example.com.:120
+&usa.example.com::usa-ns2.usa.example.com.:120
++\052.w5.example.com:1.2.3.5:120
++double.example.com:192.168.5.1:120
 +hightype.example.com:192.168.1.5:120
-:hightype.example.com:65534:\007\355\046\000\001:120
 +host-0.example.com:192.168.1.0:120
-:host-0.example.com:108:\000PV\233\000\347:120
 +host-1.example.com:192.168.1.1:120
-:host-1.example.com:109:\000PV\233\000\347\176W:120
 +host-10.example.com:192.168.1.10:120
 +host-100.example.com:192.168.1.100:120
 +host-1000.example.com:192.168.1.232:120
@@ -20018,47 +20012,17 @@ Cexternal.example.com:somewhere.else.net.:120
 +host-9998.example.com:192.168.1.14:120
 +host-9999.example.com:192.168.1.15:120
 +host-for-auto-ptr.example.com:192.0.2.1:120
-:hwinfo.example.com:13:\003abc\003def:120
-:ipv6.example.com:28:\040\001\006\250\000\000\000\001\002\020K\377\376KLa:120
-&italy.example.com::italy-ns1.example.com.:120
-&italy.example.com::italy-ns2.example.com.:120
++host.\052.sub.example.com:192.168.6.1:120
 +italy-ns1.example.com:192.168.5.1:120
 +italy-ns2.example.com:192.168.5.2:120
 +localhost.example.com:127.0.0.1:120
-:location.example.com:29:\000\022\026\023\213\044\310\373\201D\030\300\000\230\230\020:120
-:location.example.com:29:\000\042\026\023t\3331\320\201D\030\300\000\230\230\020:120
-:location.example.com:29:\0002\026\023\213\044\323e\176\273\347\100\000\230\230\020:120
-:location.example.com:29:\000B\026\023t\333\053\274\176\273\347\100\000\230\230\020:120
-Cloop1.example.com:loop2.example.com.:120
-Cloop2.example.com:loop3.example.com.:120
-Cloop3.example.com:loop1.example.com.:120
-@mail.example.com::smtp1.example.com.:25:120
-:multitext.example.com:16:\015text\040part\040one\015text\040part\040two\017text\040part\040three:120
 +ns1.example.com:192.168.1.1:120
 +ns2.example.com:192.168.1.2:120
-Cnxd.example.com:nxdomain.example.com.:120
 +outpost.example.com:192.168.2.1:120
-Crhs-at-expansion.example.com:example.com.:120
-Csemi-external.example.com:bla.something.wtest.com.:120
-Cserver1.example.com:server1.france.example.com.:120
 +smtp-servers.example.com:192.168.0.2:120
 +smtp-servers.example.com:192.168.0.3:120
 +smtp-servers.example.com:192.168.0.4:120
-Csmtp1.example.com:outpost.example.com.:120
-Cstart.example.com:x.y.z.w1.example.com.:120
-Cstart1.example.com:start2.example.com.:120
-Cstart2.example.com:start3.example.com.:120
-Cstart3.example.com:start4.example.com.:120
 +start4.example.com:192.168.2.2:120
-+host.\052.sub.example.com:192.168.6.1:120
-:text.example.com:16:\025Hi\054\040this\040is\040some\040text:120
-:text0.example.com:16:\014k\075rsa\073\040p\075one:120
-:text1.example.com:16:\014k\075rsa\073\040p\075one:120
-:text2.example.com:16:\015k\075rsa\134\073\040p\075one:120
-:text3.example.com:16:\015k\075rsa\134\073\040p\075one:120
-@together-too-much.example.com::toomuchinfo-a.example.com.:25:120
-@together-too-much.example.com::toomuchinfo-b.example.com.:25:120
-+toomuchinfo-a.example.com:192.168.99.1:120
 +toomuchinfo-a.example.com:192.168.99.10:120
 +toomuchinfo-a.example.com:192.168.99.11:120
 +toomuchinfo-a.example.com:192.168.99.12:120
@@ -20069,13 +20033,14 @@ Cstart3.example.com:start4.example.com.:120
 +toomuchinfo-a.example.com:192.168.99.17:120
 +toomuchinfo-a.example.com:192.168.99.18:120
 +toomuchinfo-a.example.com:192.168.99.19:120
-+toomuchinfo-a.example.com:192.168.99.2:120
++toomuchinfo-a.example.com:192.168.99.1:120
 +toomuchinfo-a.example.com:192.168.99.20:120
 +toomuchinfo-a.example.com:192.168.99.21:120
 +toomuchinfo-a.example.com:192.168.99.22:120
 +toomuchinfo-a.example.com:192.168.99.23:120
 +toomuchinfo-a.example.com:192.168.99.24:120
 +toomuchinfo-a.example.com:192.168.99.25:120
++toomuchinfo-a.example.com:192.168.99.2:120
 +toomuchinfo-a.example.com:192.168.99.3:120
 +toomuchinfo-a.example.com:192.168.99.4:120
 +toomuchinfo-a.example.com:192.168.99.5:120
@@ -20133,61 +20098,94 @@ Cstart3.example.com:start4.example.com.:120
 +toomuchinfo-b.example.com:192.168.99.88:120
 +toomuchinfo-b.example.com:192.168.99.89:120
 +toomuchinfo-b.example.com:192.168.99.90:120
-Cunauth.example.com:no-idea.example.org.:120
-&usa.example.com::usa-ns1.usa.example.com.:120
-&usa.example.com::usa-ns2.usa.example.com.:120
 +usa-ns1.usa.example.com:192.168.4.1:120
 +usa-ns2.usa.example.com:192.168.4.2:120
+:dsdelegation.example.com:43:m\341\010\001\312\361\352\256\315\253\347afpx\217\220\042EK\365\375\237\332:120
+:escapedtext.example.com:16:\005begin\022the\040\042middle\042\040p\134art\007the\040end:120
+:google-alias.example.com:65401:\023google-public-dns-a\006google\300\024:120
+:hightype.example.com:65534:\007\355\046\000\001:120
+:host-0.example.com:108:\000PV\233\000\347:120
+:host-1.example.com:109:\000PV\233\000\347\176W:120
+:hwinfo.example.com:13:\003abc\003def:120
+:ipv6.example.com:28:\040\001\006\250\000\000\000\001\002\020K\377\376KLa:120
+:location.example.com:29:\0002\026\023\213\044\323e\176\273\347\100\000\230\230\020:120
+:location.example.com:29:\000B\026\023t\333\053\274\176\273\347\100\000\230\230\020:120
+:location.example.com:29:\000\022\026\023\213\044\310\373\201D\030\300\000\230\230\020:120
+:location.example.com:29:\000\042\026\023t\3331\320\201D\030\300\000\230\230\020:120
+:multitext.example.com:16:\015text\040part\040one\015text\040part\040two\017text\040part\040three:120
+:text.example.com:16:\025Hi\054\040this\040is\040some\040text:120
+:text0.example.com:16:\014k\075rsa\073\040p\075one:120
+:text1.example.com:16:\014k\075rsa\073\040p\075one:120
+:text2.example.com:16:\015k\075rsa\134\073\040p\075one:120
+:text3.example.com:16:\015k\075rsa\134\073\040p\075one:120
+@example.com::smtp-servers.example.com.:10:120
+@example.com::smtp-servers.test.com.:15:120
+@external-mail.example.com::server1.test.com.:25:120
+@mail.example.com::smtp1.example.com.:25:120
+@together-too-much.example.com::toomuchinfo-a.example.com.:25:120
+@together-too-much.example.com::toomuchinfo-b.example.com.:25:120
 C\052.w1.example.com:x.y.z.w2.example.com.:120
 C\052.w2.example.com:x.y.z.w3.example.com.:120
 C\052.w3.example.com:x.y.z.w4.example.com.:120
 C\052.w4.example.com:x.y.z.w5.example.com.:120
-+\052.w5.example.com:1.2.3.5:120
+Cexternal.example.com:somewhere.else.net.:120
+Cloop1.example.com:loop2.example.com.:120
+Cloop2.example.com:loop3.example.com.:120
+Cloop3.example.com:loop1.example.com.:120
+Cnxd.example.com:nxdomain.example.com.:120
+Crhs-at-expansion.example.com:example.com.:120
+Csemi-external.example.com:bla.something.wtest.com.:120
+Cserver1.example.com:server1.france.example.com.:120
+Csmtp1.example.com:outpost.example.com.:120
+Cstart.example.com:x.y.z.w1.example.com.:120
+Cstart1.example.com:start2.example.com.:120
+Cstart2.example.com:start3.example.com.:120
+Cstart3.example.com:start4.example.com.:120
+Cunauth.example.com:no-idea.example.org.:120
 Cwww.example.com:outpost.example.com.:120
+Zexample.com:ns1.example.com.:ahu.example.com.:2847484148:28800:7200:604800:86400:100000
 #2005092501 auto axfr-get
-Ztest.com:ns1.test.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
+&blah.test.com::blah.test.com.:3600
+&sub.test.test.com::ns-test.example.net.test.com.:3600
 &test.com::ns1.test.com.:3600
 &test.com::ns2.test.com.:3600
-@test.com::..:10:3600
-@test.com::smtp-servers.test.com.:15:3600
-:_underscore.test.com:16:\030underscores\040are\040terrible:3600
-:aland.test.com:16:\016\303\205LAND\040ISLANDS:3600
-&blah.test.com::blah.test.com.:3600
-+blah.test.com:192.168.6.1:3600
-+b.c.test.com:5.6.7.8:3600
 +\052.a.b.c.test.com:8.7.6.5:3600
++b.c.test.com:5.6.7.8:3600
++blah.test.com:192.168.6.1:3600
 +counter.test.com:1.1.1.5:3600
-:d.test.com:39:\002d2\005test2\003com\000:3600
++interrupted-rrset.test.com:1.1.1.1:3600
++interrupted-rrset.test.com:2.2.2.2:3600
++ns1.test.com:1.1.1.1:3600
++ns2.test.com:2.2.2.2:3600
++server1.test.com:1.2.3.4:3600
++www.test.test.com:4.3.2.1:3600
 :_double._tcp.dc.test.com:33:\000\000\000d\001\205\007server1\004test\003com\000:3600
 :_double._tcp.dc.test.com:33:\000\001\000d\001\205\007server1\004test\003com\000:3600
 :_ldap._tcp.dc.test.com:33:\000\000\000d\001\205\007server2\007example\003net\000:3600
 :_root._tcp.dc.test.com:33:\000\000\000\000\000\000\000:3600
+:_underscore.test.com:16:\030underscores\040are\040terrible:3600
+:aland.test.com:16:\016\303\205LAND\040ISLANDS:3600
+:d.test.com:39:\002d2\005test2\003com\000:3600
 :enum.test.com:35:\000d\0002\001u\007e2u\053sip\000\010testuser\006domain\003com\000:3600
 :hightxt.test.com:16:\042v\075spf1\040mx\040ip4\07278.46.192.210\040\342\200\223all:3600
 :hightxt.test.com:99:\042v\075spf1\040mx\040ip4\07278.46.192.210\040\342\200\223all:3600
-+ns1.test.com:1.1.1.1:3600
-+ns2.test.com:2.2.2.2:3600
-+server1.test.com:1.2.3.4:3600
+:interrupted-rrset.test.com:16:\023check\040AXFR\040signpipe:3600
 :server1.test.com:17:\003ahu\004ds9a\002nl\000\007counter\004test\003com\000:3600
-C\052.test.test.com:server1.test.com.:3600
-&sub.test.test.com::ns-test.example.net.test.com.:3600
-+www.test.test.com:4.3.2.1:3600
-Ctoroot.test.com:..:3600
 :urc65226.test.com:65226:ABC:3600
 :very-long-txt.test.com:16:\377A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\040won\047t\040believe\040how\040long.\040A\040very\040long\040TXT\040record\041\040boy\040you\030\040won\047t\040believe\040how\040long\041:3600
+@test.com::..:10:3600
+@test.com::smtp-servers.test.com.:15:3600
+C\052.test.test.com:server1.test.com.:3600
+Ctoroot.test.com:..:3600
 Cwithin-server.test.com:outpost.example.com.:3600
 Cwww.test.com:server1.test.com.:3600
+Ztest.com:ns1.test.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2012060701 auto axfr-get
-Ztest.dyndns:ns1.test.dyndns.:ahu.example.dyndns.:2012060701:28800:7200:604800:86400:3600
 &test.dyndns::ns1.test.dyndns.:3600
 &test.dyndns::ns2.test.dyndns.:3600
-@test.dyndns::host-1.test.dyndns.:10:3600
-@test.dyndns::host-2.test.dyndns.:20:3600
-Ccname1.test.dyndns:host-1.test.dyndns.:3600
-Ccname2.test.dyndns:host-2.test.dyndns.:3600
-+delete-add.test.dyndns:127.0.0.108:3600
-:delete-add.test.dyndns:16:\034Should\040be\040gone\040after\040a\040while:3600
++\052.wild.test.dyndns:127.0.1.255:3600
 +a.host.test.dyndns:1.1.1.1:3600
++delete-add.test.dyndns:127.0.0.108:3600
 +e.host.test.dyndns:1.1.1.1:3600
 +host-1.test.dyndns:127.0.0.101:3600
 +host-2.test.dyndns:127.0.0.102:3600
@@ -20199,90 +20197,95 @@ Ccname2.test.dyndns:host-2.test.dyndns.:3600
 +ns2.test.dyndns:127.0.0.2:3600
 +replace.test.dyndns:127.0.0.1:3600
 +ttl.test.dyndns:127.0.0.1:3600
+:delete-add.test.dyndns:16:\034Should\040be\040gone\040after\040a\040while:3600
 :txt.test.dyndns:16:\021This\040is\040some\040text:3600
-+\052.wild.test.dyndns:127.0.1.255:3600
+@test.dyndns::host-1.test.dyndns.:10:3600
+@test.dyndns::host-2.test.dyndns.:20:3600
+Ccname1.test.dyndns:host-1.test.dyndns.:3600
+Ccname2.test.dyndns:host-2.test.dyndns.:3600
+Ztest.dyndns:ns1.test.dyndns.:ahu.example.dyndns.:2012060701:28800:7200:604800:86400:3600
 #2005092501 auto axfr-get
-Zwtest.com:ns1.wtest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 &wtest.com::..:3600
 &wtest.com::ns1.wtest.com.:3600
-@wtest.com::smtp-servers.example.com.:10:3600
-@wtest.com::smtp-servers.wtest.com.:15:3600
-+wtest.com:9.9.9.9:3600
-C\052.wtest.com:server1.wtest.com.:3600
-:e.wtest.com:16:\011non-empty:3600
-:d.e.wtest.com:16:\011non-empty:3600
-:c.d.e.wtest.com:16:\011non-empty:3600
-:b.c.d.e.wtest.com:16:\011non-empty:3600
-:a.b.c.d.e.wtest.com:16:\011non-empty:3600
 +\052.a.b.c.d.e.wtest.com:6.7.8.9:3600
++\052.something.wtest.com:4.3.2.1:3600
++a.something.wtest.com:10.11.12.13:3600
 +ns1.wtest.com:2.3.4.5:3600
-@secure.wtest.com::server1.wtest.com.:10:3600
 +server1.wtest.com:1.2.3.4:3600
++wtest.com:9.9.9.9:3600
+:a.b.c.d.e.wtest.com:16:\011non-empty:3600
+:b.c.d.e.wtest.com:16:\011non-empty:3600
+:c.d.e.wtest.com:16:\011non-empty:3600
+:d.e.wtest.com:16:\011non-empty:3600
+:e.wtest.com:16:\011non-empty:3600
 :something.wtest.com:16:\045make\040the\040empty\040non-terminal\040non-empty:3600
-+\052.something.wtest.com:4.3.2.1:3600
-+a.something.wtest.com:10.11.12.13:3600
+@secure.wtest.com::server1.wtest.com.:10:3600
+@wtest.com::smtp-servers.example.com.:10:3600
+@wtest.com::smtp-servers.wtest.com.:15:3600
+C\052.wtest.com:server1.wtest.com.:3600
+Zwtest.com:ns1.wtest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2005092501 auto axfr-get
-Znztest.com:ns1.nztest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 &nztest.com::ns1.nztest.com.:3600
 &nztest.com::ns2.nztest.com.:3600
-+nztest.com:127.0.0.1:3600
 +ns1.nztest.com:1.1.1.1:3600
 +ns2.nztest.com:2.2.2.2:3600
++nztest.com:127.0.0.1:3600
+Znztest.com:ns1.nztest.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2005092501 auto axfr-get
-Zdnssec-parent.com:ns1.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
+&delegated.dnssec-parent.com::ns1.delegated.dnssec-parent.com.:3600
+&delegated.dnssec-parent.com::ns2.delegated.dnssec-parent.com.:3600
 &dnssec-parent.com::ns1.dnssec-parent.com.:3600
 &dnssec-parent.com::ns2.dnssec-parent.com.:3600
-+dnssec-parent.com:9.9.9.9:3600
 &insecure-delegated.ent.ent.auth-ent.dnssec-parent.com::ns.example.com.:3600
-+something1.auth-ent.dnssec-parent.com:1.1.2.3:3600
-&delegated.dnssec-parent.com::ns1.delegated.dnssec-parent.com.:3600
-&delegated.dnssec-parent.com::ns2.delegated.dnssec-parent.com.:3600
-+ns1.delegated.dnssec-parent.com:4.5.6.7:3600
-+ns2.delegated.dnssec-parent.com:5.6.7.8:3600
-+ns1.dnssec-parent.com:1.2.3.4:3600
-+ns2.dnssec-parent.com:4.3.2.1:3600
-:secure-delegated.dnssec-parent.com:43:\324\057\010\002\240\271\303\214\323\044\030\052\360\357f\203\015\012\016\205\241\325\211y\311\203N\030\310qw\236\004\010W\267:3600
 &secure-delegated.dnssec-parent.com::ns1.secure-delegated.dnssec-parent.com.:3600
 &secure-delegated.dnssec-parent.com::ns2.secure-delegated.dnssec-parent.com.:3600
++dnssec-parent.com:9.9.9.9:3600
++ns1.delegated.dnssec-parent.com:4.5.6.7:3600
++ns1.dnssec-parent.com:1.2.3.4:3600
 +ns1.secure-delegated.dnssec-parent.com:1.2.3.4:3600
++ns2.delegated.dnssec-parent.com:5.6.7.8:3600
++ns2.dnssec-parent.com:4.3.2.1:3600
 +ns2.secure-delegated.dnssec-parent.com:5.6.7.8:3600
++something1.auth-ent.dnssec-parent.com:1.1.2.3:3600
+:secure-delegated.dnssec-parent.com:43:\324\057\010\002\240\271\303\214\323\044\030\052\360\357f\203\015\012\016\205\241\325\211y\311\203N\030\310qw\236\004\010W\267:3600
+Zdnssec-parent.com:ns1.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2005092501 auto axfr-get
-Zdelegated.dnssec-parent.com:ns1.delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 &delegated.dnssec-parent.com::ns1.delegated.dnssec-parent.com.:3600
 &delegated.dnssec-parent.com::ns2.delegated.dnssec-parent.com.:3600
 +delegated.dnssec-parent.com:9.9.9.9:3600
-:delegated.dnssec-parent.com:43:\253\376\010\002\324\303\325U\053\206y\372\356\2741\176\137\004\213aK.\137\140\175\305\177\025S\030-I\253\041y\367:3600
 +ns1.delegated.dnssec-parent.com:4.5.6.7:3600
 +ns2.delegated.dnssec-parent.com:5.6.7.8:3600
+:delegated.dnssec-parent.com:43:\253\376\010\002\324\303\325U\053\206y\372\356\2741\176\137\004\213aK.\137\140\175\305\177\025S\030-I\253\041y\367:3600
 Cwww.delegated.dnssec-parent.com:delegated.dnssec-parent.com.:3600
+Zdelegated.dnssec-parent.com:ns1.delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2005092501 auto axfr-get
-Zsecure-delegated.dnssec-parent.com:ns1.secure-delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 &secure-delegated.dnssec-parent.com::ns1.secure-delegated.dnssec-parent.com.:3600
 &secure-delegated.dnssec-parent.com::ns2.secure-delegated.dnssec-parent.com.:3600
-+secure-delegated.dnssec-parent.com:9.9.9.9:3600
 +ns1.secure-delegated.dnssec-parent.com:1.2.3.4:3600
 +ns2.secure-delegated.dnssec-parent.com:5.6.7.8:3600
++secure-delegated.dnssec-parent.com:9.9.9.9:3600
 Cwww.secure-delegated.dnssec-parent.com:secure-delegated.dnssec-parent.com.:3600
+Zsecure-delegated.dnssec-parent.com:ns1.secure-delegated.dnssec-parent.com.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2000081501 auto axfr-get
-Zminimal.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 &minimal.com::ns1.example.com.:120
 &minimal.com::ns2.example.com.:120
+Zminimal.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 #2000081501 auto axfr-get
-Ztsig.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 &tsig.com::ns1.example.com.:120
 &tsig.com::ns2.example.com.:120
+Ztsig.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 #2000081501 auto axfr-get
-Zstest.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 &stest.com::ns1.example.com.:120
 &stest.com::ns2.example.com.:120
+Zstest.com:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 #2005092501 auto axfr-get
-Zcdnskey-cds-test.com:ns1.cdnskey-cds-test.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 &cdnskey-cds-test.com::ns1.cdnskey-cds-test.com.:3600
 &cdnskey-cds-test.com::ns2.cdnskey-cds-test.com.:3600
 +cdnskey-cds-test.com:127.0.0.1:3600
 +ns1.cdnskey-cds-test.com:1.1.1.1:3600
 +ns2.cdnskey-cds-test.com:2.2.2.2:3600
+Zcdnskey-cds-test.com:ns1.cdnskey-cds-test.:ahu.example.com.:2005092501:28800:7200:604800:86400:3600
 #2000081501 auto axfr-get
-Z2.0.192.in-addr.arpa:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
 &2.0.192.in-addr.arpa::ns1.example.com.:120
 &2.0.192.in-addr.arpa::ns2.example.com.:120
+Z2.0.192.in-addr.arpa:ns1.example.com.:ahu.example.com.:2000081501:28800:7200:604800:86400:120
index 79214720f669b86424150e152f9cac9fec785954..40206e6c4a9e79bc09d4649b6f6ba16cc0daf668 100644 (file)
Binary files a/modules/tinydnsbackend/data.cdb and b/modules/tinydnsbackend/data.cdb differ
index ea1925fa2036706847fdb8cd6b84576148fb0502..250d85bb1f5fcd9181351406bee062971bab2a6a 100755 (executable)
@@ -34,7 +34,7 @@ cd $startdir
 for zone in $(grep 'zone ' ../../regression-tests/named.conf | cut -f2 -d\")
 do
   $TCPCLIENT 127.0.0.1 5300 $AXFRGET $zone $zone.out $zone.out.tmp
-  cat $zone.out >> data
+  LC_ALL=C sort $zone.out >> data
   rm $zone.out
 done
 
index 65482f5305795559e4aa0d6df4194e42df97720a..bda281e1ab01ab143af1cfb6862ece1ebc9e7571 100644 (file)
@@ -133,7 +133,11 @@ EXTRA_PROGRAMS = \
 
 pdns_server_SOURCES = \
        arguments.cc arguments.hh \
+       ascii.hh \
        auth-carbon.cc \
+       auth-caches.cc auth-caches.hh \
+       auth-packetcache.cc auth-packetcache.hh \
+       auth-querycache.cc auth-querycache.hh \
        backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \
        backends/gsql/ssql.hh \
        base32.cc base32.hh \
@@ -163,6 +167,7 @@ pdns_server_SOURCES = \
        dynhandler.cc dynhandler.hh \
        dynlistener.cc dynlistener.hh \
        dynmessenger.hh \
+       ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
        gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
@@ -180,7 +185,7 @@ pdns_server_SOURCES = \
        namespaces.hh \
        nsecrecords.cc \
        opensslsigners.cc opensslsigners.hh \
-       packetcache.cc packetcache.hh \
+       packetcache.hh \
        packethandler.cc packethandler.hh \
        pdnsexception.hh \
        qtype.cc qtype.hh \
@@ -259,6 +264,9 @@ endif
 
 pdnsutil_SOURCES = \
        arguments.cc \
+       auth-caches.cc auth-caches.hh \
+       auth-packetcache.cc auth-packetcache.hh \
+       auth-querycache.cc auth-querycache.hh \
        backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \
        backends/gsql/ssql.hh \
        base32.cc \
@@ -279,6 +287,7 @@ pdnsutil_SOURCES = \
        dnssecsigner.cc \
        dnswriter.cc dnswriter.hh \
        dynlistener.cc \
+       ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
        gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
@@ -287,7 +296,6 @@ pdnsutil_SOURCES = \
        misc.cc misc.hh \
        nsecrecords.cc \
        opensslsigners.cc opensslsigners.hh \
-       packetcache.cc \
        pdnsutil.cc \
        qtype.cc \
        randomhelper.cc \
@@ -885,8 +893,8 @@ dnsreplay_SOURCES = \
        dnsrecords.cc \
        dnsreplay.cc \
        dnswriter.cc dnswriter.hh \
-       ednssubnet.cc ednssubnet.hh \
        ednsoptions.cc ednsoptions.hh \
+       ednssubnet.cc ednssubnet.hh \
        iputils.cc \
        logger.cc \
        misc.cc \
@@ -1104,6 +1112,9 @@ pdns.conf-dist: pdns_server
 
 testrunner_SOURCES = \
        arguments.cc \
+       auth-caches.cc auth-caches.hh \
+       auth-packetcache.cc auth-packetcache.hh \
+       auth-querycache.cc auth-querycache.hh \
        base32.cc \
        base64.cc \
        bindlexer.l \
@@ -1128,7 +1139,6 @@ testrunner_SOURCES = \
        misc.cc \
        nameserver.cc \
        nsecrecords.cc \
-       packetcache.cc \
        qtype.cc \
        rcpgenerator.cc \
        responsestats.cc \
@@ -1147,6 +1157,7 @@ testrunner_SOURCES = \
        test-dnsparser_hh.cc \
        test-dnsrecords_cc.cc \
        test-iputils_hh.cc \
+       test-lock_hh.cc \
        test-md5_hh.cc \
        test-misc_hh.cc \
        test-nameserver_cc.cc \
index d6ade37f6da4c4abe68b0aa4b6aad7f0751307ea..c2f33782562c693cb25d00a7d820070c69562435 100644 (file)
@@ -870,16 +870,18 @@ The first step is to define a cache, then to assign that cache to the chosen poo
 the default one being represented by the empty string:
 
 ```
-pc = newPacketCache(10000, 86400, 0, 60, 60)
+pc = newPacketCache(10000, 86400, 0, 60, 60, false)
 getPool(""):setCache(pc)
 ```
 
 The first parameter (10000) is the maximum number of entries stored in the cache, and is the
-only one required. All the other parameter are optional and in seconds.
+only one required. The second, third, fourth and fifth parameters are optional and in seconds.
 The second one (86400) is the maximum lifetime of an entry in the cache, the third one (0) is
 the minimum TTL an entry should have to be considered for insertion in the cache,
-the fourth one (60) is the TTL used for a Server Failure or a Refused response. The last
-one (60) is the TTL that will be used when a stale cache entry is returned.
+the fourth one (60) is the TTL used for a Server Failure or a Refused response. The fifth
+one (60) is the TTL that will be used when a stale cache entry is returned. The last one
+is a boolean that indicates whether the TTL of reponses should be reduced by the number of
+seconds the response has been in the cache.
 For performance reasons the cache will pre-allocate buckets based on the maximum number
 of entries, so be careful to set the first parameter to a reasonable value. Something
 along the lines of a dozen bytes per pre-allocated entry can be expected on 64-bit.
@@ -1104,6 +1106,10 @@ Provider fingerprint is: E1D7:2108:9A59:BF8D:F101:16FA:ED5E:EA6A:9F6C:C78F:7F91:
 > generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil)
 ```
 
+Note that 'validFrom' and 'validUntil' are UNIX epoch timestamps. These can
+easily be calculated as 'os.time(), os.time()+2*365*86400' for example to
+get a certificate that is valid for two years from now.
+
 Ideally, the certificates and keys should be generated on an offline dedicated hardware and not on the resolver.
 The resolver key should be regularly rotated and should never touch persistent storage, being stored in a tmpfs
 with no swap configured.
@@ -1537,7 +1543,7 @@ instantiate a server with additional parameters
     * `expunge(n)`: remove entries from the cache, leaving at most `n` entries
     * `expungeByName(DNSName [, qtype=ANY, suffixMatch=false])`: remove entries matching the supplied DNSName and type from the cache. If suffixMatch is specified also removes names below DNSName
     * `isFull()`: return true if the cache has reached the maximum number of entries
-    * `newPacketCache(maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60])`: return a new PacketCache
+    * `newPacketCache(maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false])`: return a new PacketCache
     * `printStats()`: print the cache stats (hits, misses, deferred lookups and deferred inserts)
     * `purgeExpired(n)`: remove expired entries from the cache until there is at most `n` entries remaining in the cache
     * `toString()`: return the number of entries in the Packet Cache, and the maximum number of entries
@@ -1616,6 +1622,7 @@ instantiate a server with additional parameters
     * `setCacheCleaningDelay(n)`: set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries
     * `setCacheCleaningPercentage(n)`: set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are removed
     * `setStaleCacheEntriesTTL(n)`: allows using cache entries expired for at most `n` seconds when no backend available to answer for a query
+    * `setTCPDownstreamCleanupInterval(interval)`: minimum interval in seconds between two cleanups of the idle TCP downstream connections. Defaults to 60s
     * `setTCPUseSinglePipe(bool)`: whether the incoming TCP connections should be put into a single queue instead of using per-thread queues. Defaults to false
     * `setTCPRecvTimeout(n)`: set the read timeout on TCP connections from the client, in seconds
     * `setTCPSendTimeout(n)`: set the write timeout on TCP connections from the client, in seconds
@@ -1623,7 +1630,7 @@ instantiate a server with additional parameters
  * DNSCrypt related:
     * `addDNSCryptBind("127.0.0.1:8443", "provider name", "/path/to/resolver.cert", "/path/to/resolver.key", [false], [TCP Fast Open queue size]):` listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of "provider name", using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter sets SO_REUSEPORT when available. The last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0.
     * `generateDNSCryptProviderKeys("/path/to/providerPublic.key", "/path/to/providerPrivate.key"):` generate a new provider keypair
-    * `generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil):` generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key
+    * `generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil):` generate a new resolver private key and related certificate, valid from the `validFrom` UNIX timestamp until the `validUntil` one, signed with the provider private key
     * `printDNSCryptProviderFingerprint("/path/to/providerPublic.key")`: display the fingerprint of the provided resolver public key
     * `showDNSCryptBinds():`: display the currently configured DNSCrypt binds
  * BPFFilter related:
diff --git a/pdns/ascii.hh b/pdns/ascii.hh
new file mode 100644 (file)
index 0000000..69b1a39
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+inline unsigned char dns_tolower(unsigned char c)
+{
+  if(c>='A' && c<='Z')
+    c+='a'-'A';
+  return c;
+}
diff --git a/pdns/auth-caches.cc b/pdns/auth-caches.cc
new file mode 100644 (file)
index 0000000..c33575a
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "auth-querycache.hh"
+#include "auth-packetcache.hh"
+
+extern AuthPacketCache PC;
+extern AuthQueryCache QC;
+
+/* empty all caches */
+uint64_t purgeAuthCaches()
+{
+  uint64_t ret = 0;
+  ret += PC.purge();
+  ret += QC.purge();
+  return ret;
+}
+
+ /* remove specific entries from all caches, can be $ terminated */
+uint64_t purgeAuthCaches(const std::string& match)
+{
+  uint64_t ret = 0;
+  ret += PC.purge(match);
+  ret += QC.purge(match);
+  return ret;
+}
+
+/* remove specific entries from all caches, no wildcard matching */
+uint64_t purgeAuthCachesExact(const DNSName& qname)
+{
+  uint64_t ret = 0;
+  ret += PC.purgeExact(qname);
+  ret += QC.purgeExact(qname);
+  return ret;
+}
+
+
+
diff --git a/pdns/auth-caches.hh b/pdns/auth-caches.hh
new file mode 100644 (file)
index 0000000..76c248f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef AUTH_CACHES_HH
+#define AUTH_CACHES_HH
+
+#include <string>
+#include <cstdint>
+
+#include "dnsname.hh"
+
+uint64_t purgeAuthCaches(); /* empty all caches */
+uint64_t purgeAuthCaches(const std::string& match); /* remove specific entries from all caches, can be $ terminated */
+uint64_t purgeAuthCachesExact(const DNSName& qname); /* remove specific entries from all caches, no wildcard matching */
+
+#endif /* AUTH_CACHES_HH */
diff --git a/pdns/auth-packetcache.cc b/pdns/auth-packetcache.cc
new file mode 100644 (file)
index 0000000..f28a6fd
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "auth-packetcache.hh"
+#include "logger.hh"
+#include "statbag.hh"
+#include "cachecleaner.hh"
+extern StatBag S;
+
+const unsigned int AuthPacketCache::s_mincleaninterval, AuthPacketCache::s_maxcleaninterval;
+
+AuthPacketCache::AuthPacketCache(size_t mapsCount): d_lastclean(time(nullptr))
+{
+  d_maps.resize(mapsCount);
+  for(auto& mc : d_maps) {
+    pthread_rwlock_init(&mc.d_mut, 0);
+  }
+
+  S.declare("packetcache-hit", "Number of hits on the packet cache");
+  S.declare("packetcache-miss", "Number of misses on the packet cache");
+  S.declare("packetcache-size", "Number of entries in the packet cache");
+  S.declare("deferred-packetcache-inserts","Amount of packet cache inserts that were deferred because of maintenance");
+  S.declare("deferred-packetcache-lookup","Amount of packet cache lookups that were deferred because of maintenance");
+
+  d_statnumhit=S.getPointer("packetcache-hit");
+  d_statnummiss=S.getPointer("packetcache-miss");
+  d_statnumentries=S.getPointer("packetcache-size");
+}
+
+AuthPacketCache::~AuthPacketCache()
+{
+  try {
+    vector<WriteLock*> locks;
+    for(auto& mc : d_maps) {
+      locks.push_back(new WriteLock(&mc.d_mut));
+    }
+    for(auto wl : locks) {
+      delete wl;
+    }
+  }
+  catch(...) {
+  }
+}
+
+bool AuthPacketCache::get(DNSPacket *p, DNSPacket *cached)
+{
+  cleanupIfNeeded();
+
+  if(!d_ttl) {
+    (*d_statnummiss)++;
+    return false;
+  }
+
+  uint32_t hash = canHashPacket(p->getString(), false);
+  p->setHash(hash);
+
+  string value;
+  bool haveSomething;
+  time_t now = time(nullptr);
+  auto& mc = getMap(p->qdomain);
+  {
+    TryReadLock rl(&mc.d_mut);
+    if(!rl.gotIt()) {
+      S.inc("deferred-packetcache-lookup");
+      return false;
+    }
+
+    haveSomething = getEntryLocked(mc.d_map, hash, p->qdomain, p->qtype.getCode(), p->d_tcp, now, value);
+  }
+
+  if (!haveSomething) {
+    (*d_statnummiss)++;
+    return false;
+  }
+
+  if(cached->noparse(value.c_str(), value.size()) < 0) {
+    return false;
+  }
+
+  (*d_statnumhit)++;
+  cached->spoofQuestion(p); // for correct case
+  cached->qdomain = p->qdomain;
+  cached->qtype = p->qtype;
+
+  return true;
+}
+
+void AuthPacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxTTL)
+{
+  cleanupIfNeeded();
+
+  if (ntohs(q->d.qdcount) != 1) {
+    return; // do not try to cache packets with multiple questions
+  }
+
+  if (q->qclass != QClass::IN) // we only cache the INternet
+    return;
+
+  uint32_t ourttl = std::min(d_ttl, maxTTL);
+  if (ourttl == 0) {
+    return;
+  }  
+
+  uint32_t hash = q->getHash();
+  time_t now = time(nullptr);
+  CacheEntry entry;
+  entry.hash = hash;
+  entry.created = now;
+  entry.ttd = now + ourttl;
+  entry.qname = q->qdomain;
+  entry.qtype = q->qtype.getCode();
+  entry.value = r->getString();
+  entry.tcp = r->d_tcp;
+  
+  auto& mc = getMap(entry.qname);
+  {
+    TryWriteLock l(&mc.d_mut);
+    if (!l.gotIt()) {
+      S.inc("deferred-packetcache-inserts");
+      return;
+    }
+
+    auto& idx = mc.d_map.get<HashTag>();
+    auto range = idx.equal_range(hash);
+    auto iter = range.first;
+
+    for( ; iter != range.second ; ++iter)  {
+      if (iter->tcp != entry.tcp || iter->qtype != entry.qtype || iter->qname != entry.qname)
+        continue;
+
+      iter->value = entry.value;
+      iter->ttd = now + ourttl;
+      iter->created = now;
+      return;
+    }
+
+    /* no existing entry found to refresh */
+    mc.d_map.insert(entry);
+    (*d_statnumentries)++;
+  }
+}
+
+bool AuthPacketCache::getEntryLocked(cmap_t& map, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& value)
+{
+  auto& idx = map.get<HashTag>();
+  auto range = idx.equal_range(hash);
+
+  for(auto iter = range.first; iter != range.second ; ++iter)  {
+    if (iter->ttd < now)
+      continue;
+
+    if (iter->tcp != tcp || iter->qtype != qtype || iter->qname != qname)
+      continue;
+
+    value = iter->value;
+    return true;
+  }
+
+  return false;
+}
+
+/* clears the entire cache. */
+uint64_t AuthPacketCache::purge()
+{
+  d_statnumentries->store(0);
+
+  return purgeLockedCollectionsVector(d_maps);
+}
+
+uint64_t AuthPacketCache::purgeExact(const DNSName& qname)
+{
+  auto& mc = getMap(qname);
+  uint64_t delcount = purgeExactLockedCollection(mc, qname);
+
+  *d_statnumentries -= delcount;
+
+  return delcount;
+}
+
+/* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
+uint64_t AuthPacketCache::purge(const string &match)
+{
+  uint64_t delcount = 0;
+
+  if(ends_with(match, "$")) {
+    delcount = purgeLockedCollectionsVector(d_maps, match);
+    *d_statnumentries -= delcount;
+  }
+  else {
+    delcount = purgeExact(DNSName(match));
+  }
+
+  return delcount;
+}
+                          
+void AuthPacketCache::cleanup()
+{
+  uint64_t maxCached = d_maxEntries;
+  uint64_t cacheSize = *d_statnumentries;
+  uint64_t totErased = 0;
+
+  totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize);
+  *d_statnumentries -= totErased;
+
+  DLOG(L<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
+}
+
+/* the logic:
+   after d_nextclean operations, we clean. We also adjust the cleaninterval
+   a bit so we slowly move it to a value where we clean roughly every 30 seconds.
+
+   If d_nextclean has reached its maximum value, we also test if we were called
+   within 30 seconds, and if so, we skip cleaning. This means that under high load,
+   we will not clean more often than every 30 seconds anyhow.
+*/
+
+void AuthPacketCache::cleanupIfNeeded()
+{
+  if (d_ops++ == d_nextclean) {
+    time_t now = time(nullptr);
+    int timediff = max((int)(now - d_lastclean), 1);
+
+    DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
+
+    if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
+      d_cleanskipped = true;
+      d_nextclean += d_cleaninterval;
+
+      DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
+
+      return;
+    }
+
+    if(!d_cleanskipped) {
+      d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
+      d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
+      d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
+
+      DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
+    } else {
+      d_cleanskipped = false;
+    }
+
+    d_nextclean += d_cleaninterval;
+    d_lastclean=now;
+    cleanup();
+  }
+}
diff --git a/pdns/auth-packetcache.hh b/pdns/auth-packetcache.hh
new file mode 100644 (file)
index 0000000..d817b5d
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef AUTH_PACKETCACHE_HH
+#define AUTH_PACKETCACHE_HH
+
+#include <string>
+#include <map>
+#include "dns.hh"
+#include <boost/version.hpp>
+#include "namespaces.hh"
+using namespace ::boost::multi_index;
+
+#include <boost/multi_index/hashed_index.hpp> 
+
+#include "dnspacket.hh"
+#include "lock.hh"
+#include "packetcache.hh"
+
+/** This class performs 'whole packet caching'. Feed it a question packet and it will
+    try to find an answer. If you have an answer, insert it to have it cached for later use. 
+    Take care not to replace existing cache entries. While this works, it is wasteful. Only
+    insert packets that where not found by get()
+
+    Locking! 
+
+    The cache itself is protected by a read/write lock. Because deleting is a two step process, which 
+    first marks and then sweeps, a second lock is present to prevent simultaneous inserts and deletes.
+*/
+
+class AuthPacketCache : public PacketCache
+{
+public:
+  AuthPacketCache(size_t mapsCount=1024);
+  ~AuthPacketCache();
+
+  void insert(DNSPacket *q, DNSPacket *r, uint32_t maxTTL);  //!< We copy the contents of *p into our cache. Do not needlessly call this to insert questions already in the cache as it wastes resources
+
+  bool get(DNSPacket *p, DNSPacket *q); //!< We return a dynamically allocated copy out of our cache. You need to delete it. You also need to spoof in the right ID with the DNSPacket.spoofID() method.
+
+  void cleanup(); //!< force the cache to preen itself from expired packets
+  uint64_t purge();
+  uint64_t purge(const std::string& match); // could be $ terminated. Is not a dnsname!
+  uint64_t purgeExact(const DNSName& qname); // no wildcard matching here
+
+  uint64_t size() const { return *d_statnumentries; };
+
+  void setMaxEntries(uint64_t maxEntries) 
+  {
+    d_maxEntries = maxEntries;
+  }
+  void setTTL(uint32_t ttl)
+  {
+    d_ttl = ttl;
+  }  
+private:
+
+  struct CacheEntry
+  {
+    mutable string value;
+    DNSName qname;
+
+    mutable time_t created{0};
+    mutable time_t ttd{0};
+    uint32_t hash{0};
+    uint16_t qtype{0};
+    bool tcp{false};
+  };
+
+  struct HashTag{};
+  struct NameTag{};
+  struct SequenceTag{};
+  typedef multi_index_container<
+    CacheEntry,
+    indexed_by <
+      hashed_non_unique<tag<HashTag>, member<CacheEntry,uint32_t,&CacheEntry::hash> >,
+      ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+      sequenced<tag<SequenceTag>>
+      >
+    > cmap_t;
+
+  struct MapCombo
+  {
+    pthread_rwlock_t d_mut;    
+    cmap_t d_map;
+  };
+
+  vector<MapCombo> d_maps;
+  MapCombo& getMap(const DNSName& name)
+  {
+    return d_maps[name.hash() % d_maps.size()];
+  }
+
+  bool getEntryLocked(cmap_t& map, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& entry);
+  void cleanupIfNeeded();
+
+  AtomicCounter d_ops{0};
+  AtomicCounter *d_statnumhit;
+  AtomicCounter *d_statnummiss;
+  AtomicCounter *d_statnumentries;
+
+  uint64_t d_maxEntries{0};
+  time_t d_lastclean; // doesn't need to be atomic
+  unsigned long d_nextclean{4096};
+  unsigned int d_cleaninterval{4096};
+  uint32_t d_ttl{0};
+  bool d_cleanskipped{false};
+
+  static const unsigned int s_mincleaninterval=1000, s_maxcleaninterval=300000;
+};
+
+#endif /* AUTH_PACKETCACHE_HH */
diff --git a/pdns/auth-querycache.cc b/pdns/auth-querycache.cc
new file mode 100644 (file)
index 0000000..e91349c
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "auth-querycache.hh"
+#include "logger.hh"
+#include "statbag.hh"
+#include "cachecleaner.hh"
+extern StatBag S;
+
+const unsigned int AuthQueryCache::s_mincleaninterval, AuthQueryCache::s_maxcleaninterval;
+
+extern StatBag S;
+
+AuthQueryCache::AuthQueryCache(size_t mapsCount): d_lastclean(time(nullptr))
+{
+  d_maps.resize(mapsCount);
+  for(auto& mc : d_maps) {
+    pthread_rwlock_init(&mc.d_mut, 0);
+  }
+
+  S.declare("query-cache-hit","Number of hits on the query cache");
+  S.declare("query-cache-miss","Number of misses on the query cache");
+  S.declare("query-cache-size", "Number of entries in the query cache");
+  S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
+  S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
+
+  d_statnumhit=S.getPointer("query-cache-hit");
+  d_statnummiss=S.getPointer("query-cache-miss");
+  d_statnumentries=S.getPointer("query-cache-size");
+}
+
+AuthQueryCache::~AuthQueryCache()
+{
+  try {
+    vector<WriteLock*> locks;
+    for(auto& mc : d_maps) {
+      locks.push_back(new WriteLock(&mc.d_mut));
+    }
+    for(auto wl : locks) {
+      delete wl;
+    }
+  }
+  catch(...) {
+  }
+}
+
+// called from ueberbackend
+bool AuthQueryCache::getEntry(const DNSName &qname, const QType& qtype, vector<DNSZoneRecord>& value, int zoneID)
+{
+  cleanupIfNeeded();
+
+  time_t now = time(nullptr);
+  uint16_t qt = qtype.getCode();
+  auto& mc = getMap(qname);
+  {
+    TryReadLock rl(&mc.d_mut);
+    if(!rl.gotIt()) {
+      S.inc("deferred-cache-lookup");
+      return false;
+    }
+
+    return getEntryLocked(mc.d_map, qname, qt, value, zoneID, now);
+  }
+}
+
+void AuthQueryCache::insert(const DNSName &qname, const QType& qtype, const vector<DNSZoneRecord>& value, uint32_t ttl, int zoneID)
+{
+  cleanupIfNeeded();
+
+  if(!ttl)
+    return;
+  
+  time_t now = time(nullptr);
+  CacheEntry val;
+  val.created = now;
+  val.ttd = now + ttl;
+  val.qname = qname;
+  val.qtype = qtype.getCode();
+  val.drs = value;
+  val.zoneID = zoneID;
+
+  auto& mc = getMap(val.qname);
+
+  {
+    TryWriteLock l(&mc.d_mut);
+    if(!l.gotIt()) {
+      S.inc("deferred-cache-inserts"); 
+      return;
+    }
+
+    bool inserted;
+    cmap_t::iterator place;
+    tie(place, inserted) = mc.d_map.insert(val);
+
+    if (!inserted) {
+      mc.d_map.replace(place, val);
+    }
+    else {
+      (*d_statnumentries)++;
+    }
+  }
+}
+
+bool AuthQueryCache::getEntryLocked(cmap_t& map, const DNSName &qname, uint16_t qtype, vector<DNSZoneRecord>& value, int zoneID, time_t now)
+{
+  auto& idx = boost::multi_index::get<HashTag>(map);
+  auto iter = idx.find(tie(qname, qtype, zoneID));
+
+  if (iter == idx.end())
+    return false;
+
+  if (iter->ttd < now)
+    return false;
+
+  value = iter->drs;
+  return true;
+}
+
+map<char,uint64_t> AuthQueryCache::getCounts()
+{
+  uint64_t queryCacheEntries=0, negQueryCacheEntries=0;
+
+  for(auto& mc : d_maps) {
+    ReadLock l(&mc.d_mut);
+    
+    for(cmap_t::const_iterator iter = mc.d_map.begin() ; iter != mc.d_map.end(); ++iter) {
+      if(iter->drs.empty())
+        negQueryCacheEntries++;
+      else
+        queryCacheEntries++;
+    }
+  }
+  map<char,uint64_t> ret;
+
+  ret['!']=negQueryCacheEntries;
+  ret['Q']=queryCacheEntries;
+  return ret;
+}
+
+/* clears the entire cache. */
+uint64_t AuthQueryCache::purge()
+{
+  d_statnumentries->store(0);
+
+  return purgeLockedCollectionsVector(d_maps);
+}
+
+uint64_t AuthQueryCache::purgeExact(const DNSName& qname)
+{
+  auto& mc = getMap(qname);
+  uint64_t delcount = purgeExactLockedCollection(mc, qname);
+
+  *d_statnumentries -= delcount;
+
+  return delcount;
+}
+
+/* purges entries from the querycache. If match ends on a $, it is treated as a suffix */
+uint64_t AuthQueryCache::purge(const string &match)
+{
+  uint64_t delcount = 0;
+
+  if(ends_with(match, "$")) {
+    delcount = purgeLockedCollectionsVector(d_maps, match);
+    *d_statnumentries -= delcount;
+  }
+  else {
+    delcount = purgeExact(DNSName(match));
+  }
+
+  return delcount;
+}
+
+void AuthQueryCache::cleanup()
+{
+  uint64_t maxCached = d_maxEntries;
+  uint64_t cacheSize = *d_statnumentries;
+  uint64_t totErased = 0;
+
+  totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize);
+
+  *d_statnumentries -= totErased;
+  DLOG(L<<"Done with cache clean, cacheSize: "<<*d_statnumentries<<", totErased"<<totErased<<endl);
+}
+
+/* the logic:
+   after d_nextclean operations, we clean. We also adjust the cleaninterval
+   a bit so we slowly move it to a value where we clean roughly every 30 seconds.
+
+   If d_nextclean has reached its maximum value, we also test if we were called
+   within 30 seconds, and if so, we skip cleaning. This means that under high load,
+   we will not clean more often than every 30 seconds anyhow.
+*/
+
+void AuthQueryCache::cleanupIfNeeded()
+{
+  if (d_ops++ == d_nextclean) {
+    time_t now = time(nullptr);
+    int timediff = max((int)(now - d_lastclean), 1);
+
+    DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
+
+    if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
+      d_cleanskipped = true;
+      d_nextclean += d_cleaninterval;
+
+      DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
+
+      return;
+    }
+
+    if(!d_cleanskipped) {
+      d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
+      d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
+      d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
+
+      DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
+    } else {
+      d_cleanskipped = false;
+    }
+
+    d_nextclean += d_cleaninterval;
+    d_lastclean=now;
+    cleanup();
+  }
+}
diff --git a/pdns/auth-querycache.hh b/pdns/auth-querycache.hh
new file mode 100644 (file)
index 0000000..5e6d436
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef AUTH_QUERYCACHE_HH
+#define AUTH_QUERYCACHE_HH
+
+#include <string>
+#include <map>
+#include "dns.hh"
+#include <boost/version.hpp>
+#include "namespaces.hh"
+using namespace ::boost::multi_index;
+
+#include <boost/multi_index/hashed_index.hpp> 
+
+#include "dns.hh"
+#include "dnspacket.hh"
+#include "lock.hh"
+
+class AuthQueryCache : public boost::noncopyable
+{
+public:
+  AuthQueryCache(size_t mapsCount=1024);
+  ~AuthQueryCache();
+
+  void insert(const DNSName &qname, const QType& qtype, const vector<DNSZoneRecord>& content, uint32_t ttl, int zoneID);
+
+  bool getEntry(const DNSName &qname, const QType& qtype, vector<DNSZoneRecord>& entry, int zoneID);
+
+  size_t size() { return *d_statnumentries; } //!< number of entries in the cache
+  void cleanup(); //!< force the cache to preen itself from expired querys
+  uint64_t purge();
+  uint64_t purge(const std::string& match); // could be $ terminated. Is not a dnsname!
+  uint64_t purgeExact(const DNSName& qname); // no wildcard matching here
+
+  map<char,uint64_t> getCounts();
+
+  void setMaxEntries(uint64_t maxEntries)
+  {
+    d_maxEntries = maxEntries;
+  }
+private:
+
+  struct CacheEntry
+  {
+    DNSName qname;
+    mutable vector<DNSZoneRecord> drs;
+    mutable time_t created{0};
+    mutable time_t ttd{0};
+    uint16_t qtype{0};
+    int zoneID{-1};
+  };
+
+  struct HashTag{};
+  struct NameTag{};
+  struct SequenceTag{};
+  typedef multi_index_container<
+    CacheEntry,
+    indexed_by <
+      hashed_unique<tag<HashTag>, composite_key<CacheEntry,
+                                                         member<CacheEntry,DNSName,&CacheEntry::qname>,
+                                                         member<CacheEntry,uint16_t,&CacheEntry::qtype>,
+                                                         member<CacheEntry,int, &CacheEntry::zoneID> > > ,
+      ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+      sequenced<tag<SequenceTag>>
+                           >
+  > cmap_t;
+
+
+  struct MapCombo
+  {
+    pthread_rwlock_t d_mut;    
+    cmap_t d_map;
+  };
+
+  vector<MapCombo> d_maps;
+  MapCombo& getMap(const DNSName& qname)
+  {
+    return d_maps[qname.hash() % d_maps.size()];
+  }
+
+  bool getEntryLocked(cmap_t& map, const DNSName &content, uint16_t qtype, vector<DNSZoneRecord>& entry, int zoneID, time_t now);
+  void cleanupIfNeeded();
+
+  AtomicCounter d_ops{0};
+  AtomicCounter *d_statnumhit;
+  AtomicCounter *d_statnummiss;
+  AtomicCounter *d_statnumentries;
+
+  uint64_t d_maxEntries{0};
+  time_t d_lastclean; // doesn't need to be atomic
+  unsigned long d_nextclean{4906};
+  unsigned int d_cleaninterval{4096};
+  bool d_cleanskipped{false};
+
+  static const unsigned int s_mincleaninterval=1000, s_maxcleaninterval=300000;
+};
+
+#endif /* AUTH_QUERYCACHE_HH */
index 0adbe370df6da4ceeeb3a9ad81caa3032d1e54ca..0e746fa5e0913ce7280384edc035e52729858db0 100644 (file)
@@ -19,8 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
-#ifndef PDNS_CACHECLEANER_HH
-#define PDNS_CACHECLEANER_HH
+#pragma once
+
+#include "lock.hh"
 
 // this function can clean any cache that has a getTTD() method on its entries, and a 'sequence' index as its second index
 // the ritual is that the oldest entries are in *front* of the sequence collection, so on a hit, move an item to the end
@@ -101,4 +102,93 @@ template <typename T> void moveCacheItemToBack(T& collection, typename T::iterat
   moveCacheItemToFrontOrBack(collection, iter, false);
 }
 
-#endif
+template <typename T> uint64_t pruneLockedCollectionsVector(vector<T>& maps, uint64_t maxCached, uint64_t cacheSize)
+{
+  time_t now = time(nullptr);
+  uint64_t totErased = 0;
+  uint64_t toTrim = 0;
+  uint64_t lookAt = 0;
+
+  // two modes - if toTrim is 0, just look through 10%  of the cache and nuke everything that is expired
+  // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
+  if (maxCached && cacheSize > maxCached) {
+    toTrim = cacheSize - maxCached;
+    lookAt = 5 * toTrim;
+  } else {
+    lookAt = cacheSize / 10;
+  }
+
+  for(auto& mc : maps) {
+    WriteLock wl(&mc.d_mut);
+    auto& sidx = boost::multi_index::get<2>(mc.d_map);
+    uint64_t erased = 0, lookedAt = 0;
+    for(auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
+      if(i->ttd < now) {
+        i = sidx.erase(i);
+        erased++;
+      } else {
+        ++i;
+      }
+
+      if(toTrim && erased > toTrim / maps.size())
+        break;
+
+      if(lookedAt > lookAt / maps.size())
+        break;
+    }
+    totErased += erased;
+  }
+
+  return totErased;
+}
+
+template <typename T> uint64_t purgeLockedCollectionsVector(vector<T>& maps)
+{
+  uint64_t delcount=0;
+
+  for(auto& mc : maps) {
+    WriteLock wl(&mc.d_mut);
+    delcount += mc.d_map.size();
+    mc.d_map.clear();
+  }
+
+  return delcount;
+}
+
+template <typename T> uint64_t purgeLockedCollectionsVector(vector<T>& maps, const std::string& match)
+{
+  uint64_t delcount=0;
+  string prefix(match);
+  prefix.resize(prefix.size()-1);
+  DNSName dprefix(prefix);
+  for(auto& mc : maps) {
+    WriteLock wl(&mc.d_mut);
+    auto& idx = boost::multi_index::get<1>(mc.d_map);
+    auto iter = idx.lower_bound(dprefix);
+    auto start = iter;
+
+    for(; iter != idx.end(); ++iter) {
+      if(!iter->qname.isPartOf(dprefix)) {
+        break;
+      }
+      delcount++;
+    }
+    idx.erase(start, iter);
+  }
+
+  return delcount;
+}
+
+template <typename T> uint64_t purgeExactLockedCollection(T& mc, const DNSName& qname)
+{
+  uint64_t delcount=0;
+  WriteLock wl(&mc.d_mut);
+  auto& idx = boost::multi_index::get<1>(mc.d_map);
+  auto range = idx.equal_range(qname);
+  if(range.first != range.second) {
+    delcount += distance(range.first, range.second);
+    idx.erase(range.first, range.second);
+  }
+
+  return delcount;
+}
index 8eaa5b92c8dbe14061e7067af2507725296d7136..4089cee68c770d6d5891359db6b37bdbee00109e 100644 (file)
@@ -39,7 +39,8 @@ typedef Distributor<DNSPacket,DNSPacket,PacketHandler> DNSDistributor;
 
 ArgvMap theArg;
 StatBag S;  //!< Statistics are gathered across PDNS via the StatBag class S
-PacketCache PC; //!< This is the main PacketCache, shared across all threads
+AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
+AuthQueryCache QC;
 DNSProxy *DP;
 DynListener *dl;
 CommunicatorClass Communicator;
@@ -171,7 +172,8 @@ void declareArguments()
   ::arg().set("setuid","If set, change user id to this uid for more security")="";
   ::arg().set("setgid","If set, change group id to this gid for more security")="";
 
-  ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
+  ::arg().set("max-cache-entries", "Maximum number of entries in the query cache")="1000000";
+  ::arg().set("max-packet-cache-entries", "Maximum number of entries in the packet cache")="1000000";
   ::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries")="";
   ::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone")="100000";
   ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
@@ -191,11 +193,14 @@ void declareArguments()
   ::arg().set("include-dir","Include *.conf files from this directory");
   ::arg().set("security-poll-suffix","Domain name from which to query security update notifications")="secpoll.powerdns.com.";
 
+  ::arg().setSwitch("expand-alias", "Expand ALIAS records")="no";
   ::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no";
   ::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no";
   ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial")="no";
 
   ::arg().set("xfr-max-received-mbytes", "Maximum number of megabytes received from an incoming XFR")="100";
+
+  ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
 }
 
 static time_t s_start=time(0);
@@ -280,12 +285,6 @@ void declareStats(void)
 
   S.declare("qsize-q","Number of questions waiting for database attention", getQCount);
 
-  S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
-  S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
-
-  S.declare("query-cache-hit","Number of hits on the query cache");
-  S.declare("query-cache-miss","Number of misses on the query cache");
-
   S.declare("dnsupdate-queries", "DNS update packets received.");
   S.declare("dnsupdate-answers", "DNS update packets successfully answered.");
   S.declare("dnsupdate-refused", "DNS update packets that are refused.");
@@ -438,7 +437,7 @@ try
         continue;
       }
     }
-    
+
     if(distributor->isOverloaded()) {
       if(logDNSQueries) 
         L<<"Dropped query, backends are overloaded"<<endl;
@@ -495,6 +494,10 @@ void mainthread()
    DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold"));
    DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
 
+   PC.setTTL(::arg().asNum("cache-ttl"));
+   PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
+   QC.setMaxEntries(::arg().asNum("max-cache-entries"));
+
    stubParseResolveConf();
 
    if(!::arg()["chroot"].empty()) {
index 76112ce19d8d3808baee5df5495beaa1ea4d8c64..2bcff68a5aadf3ea512c0e87e9e91dd3c9f06e67 100644 (file)
@@ -22,7 +22,8 @@
 #ifndef COMMON_STARTUP_HH
 #define COMMON_STARTUP_HH
 
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
 #include "utility.hh"
 #include "arguments.hh"
 #include "communicator.hh"
@@ -36,7 +37,8 @@
 
 extern ArgvMap theArg;
 extern StatBag S;  //!< Statistics are gathered across PDNS via the StatBag class S
-extern PacketCache PC; //!< This is the main PacketCache, shared across all threads
+extern AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads
+extern AuthQueryCache QC;
 extern DNSProxy *DP;
 extern DynListener *dl;
 extern CommunicatorClass Communicator;
index 7b41b63d484309552e6b19de23bd2723350cb448..305dcc677e663561b52d21647c526f9d254e47f3 100644 (file)
@@ -24,7 +24,7 @@
 #include "dnsparser.hh"
 #include "dnsdist-cache.hh"
 
-DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t staleTTL): d_maxEntries(maxEntries), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_minTTL(minTTL), d_staleTTL(staleTTL)
+DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t staleTTL, bool dontAge): d_maxEntries(maxEntries), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge)
 {
   pthread_rwlock_init(&d_lock, 0);
   /* we reserve maxEntries + 1 to avoid rehashing from occurring
@@ -209,7 +209,7 @@ bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t
     }
   }
 
-  if (!skipAging) {
+  if (!d_dontAge && !skipAging) {
     ageDNSPacket(response, *responseLen, age);
   }
 
index b4c180c7856fd74b74489661570abeb735b38ff9..4fcd2dabbcd5195d13c336770e4f4a1af7ebad60 100644 (file)
@@ -30,7 +30,7 @@ struct DNSQuestion;
 class DNSDistPacketCache : boost::noncopyable
 {
 public:
-  DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t staleTTL=60);
+  DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t staleTTL=60, bool dontAge=false);
   ~DNSDistPacketCache();
 
   void insert(uint32_t key, const DNSName& qname, uint16_t qtype, uint16_t qclass, const char* response, uint16_t responseLen, bool tcp, uint8_t rcode);
@@ -85,4 +85,5 @@ private:
   uint32_t d_tempFailureTTL;
   uint32_t d_minTTL;
   uint32_t d_staleTTL;
+  bool d_dontAge;
 };
index e73b9541c665aed7b06eca015329caeab680e636..bc4febd4a6cf11fcf2259c42bea34629b832701a 100644 (file)
@@ -329,6 +329,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
   { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
   { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
+  { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false]", "return a new Packet Cache" },
   { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
   { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
   { "newRuleAction", true, "DNS rule, DNS action", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
@@ -375,6 +376,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setServerPolicy", true, "policy", "set server selection policy to that policy" },
   { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
   { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
+  { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
   { "setTCPUseSinglePipe", true, "bool", "whether the incoming TCP connections should be put into a single queue instead of using per-thread queues. Defaults to false" },
   { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
   { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
index fe0abd01be683d969a43fb96c31dca68f20dae49..4ac112111490c966d5d38712a48da240cbc8d16f 100644 (file)
@@ -1462,11 +1462,18 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
     });
   
   g_lua.writeFunction("setKey", [](const std::string& key) {
+      if(!g_configurationDone && ! g_key.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
+        return;                                     // but later setKeys() trump the -k value again
+      }
+
       setLuaSideEffect();
-      if(B64Decode(key, g_key) < 0) {
-         g_outputBuffer=string("Unable to decode ")+key+" as Base64";
-         errlog("%s", g_outputBuffer);
-       }
+      string newkey;
+      if(B64Decode(key, newkey) < 0) {
+        g_outputBuffer=string("Unable to decode ")+key+" as Base64";
+        errlog("%s", g_outputBuffer);
+      }
+      else
+       g_key=newkey;
     });
 
   
index 1bd3a1bc8201c241a00bcf28e2778d9b267a864a..89276ec9b369020ec8fd04e0eaaa1b746a2a0f46 100644 (file)
@@ -693,8 +693,8 @@ void moreLua(bool client)
         }
     });
 
-    g_lua.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL) {
-        return std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, staleTTL ? *staleTTL : 60);
+    g_lua.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge) {
+        return std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false);
       });
     g_lua.registerFunction("toString", &DNSDistPacketCache::toString);
     g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull);
@@ -1337,4 +1337,9 @@ void moreLua(bool client)
           g_outputBuffer=poolObj->policy->name+"\n";
         }
       });
+
+    g_lua.writeFunction("setTCPDownstreamCleanupInterval", [](uint16_t interval) {
+        setLuaSideEffect();
+        g_downstreamTCPCleanupInterval = interval;
+      });
 }
index 75d1caf749b41fa871ae7492602a15a367c5e601..95e3ac204511a13169f33c3d2062e474c5ecfc79 100644 (file)
@@ -93,6 +93,7 @@ size_t g_maxTCPConnectionsPerClient{0};
 static std::mutex tcpClientsCountMutex;
 static std::map<ComboAddress,size_t,ComboAddress::addressOnlyLessThan> tcpClientsCount;
 bool g_useTCPSinglePipe{false};
+std::atomic<uint16_t> g_downstreamTCPCleanupInterval{60};
 
 void* tcpClientThread(int pipefd);
 
@@ -194,6 +195,19 @@ static bool maxConnectionDurationReached(unsigned int maxConnectionDuration, tim
   return false;
 }
 
+void cleanupClosedTCPConnections(std::map<ComboAddress,int>& sockets)
+{
+  for(auto it = sockets.begin(); it != sockets.end(); ) {
+    if (isTCPSocketUsable(it->second)) {
+      ++it;
+    }
+    else {
+      close(it->second);
+      it = sockets.erase(it);
+    }
+  }
+}
+
 std::shared_ptr<TCPClientCollection> g_tcpclientthreads;
 
 void* tcpClientThread(int pipefd)
@@ -203,6 +217,7 @@ void* tcpClientThread(int pipefd)
      
   bool outstanding = false;
   blockfilter_t blockFilter = 0;
+  time_t lastTCPCleanup = time(nullptr);
   
   {
     std::lock_guard<std::mutex> lock(g_luamutex);
@@ -602,6 +617,11 @@ void* tcpClientThread(int pipefd)
       --ds->outstanding;
     }
     decrementTCPClientCount(ci.remote);
+
+    if (g_downstreamTCPCleanupInterval > 0 && (connectionStartTime > (lastTCPCleanup + g_downstreamTCPCleanupInterval))) {
+      cleanupClosedTCPConnections(sockets);
+      lastTCPCleanup = time(nullptr);
+    }
   }
   return 0;
 }
index ccecf1a48aa2043525026c1c18ad2463b00c27af..f82fe5f66de3cc440a3e31250b2ca2b97120a681 100644 (file)
@@ -349,7 +349,7 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
           {"order", (int)a->order},
           {"pools", pools},
           {"latency", (int)(a->latencyUsec/1000.0)},
-          {"queries", (int)a->queries}};
+          {"queries", (double)a->queries}};
 
         /* sending a latency for a DOWN server doesn't make sense */
         if (a->availability == DownstreamState::Availability::Down) {
@@ -380,7 +380,7 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
       for(const auto& a : localRules) {
        Json::object rule{
          {"id", num++},
-         {"matches", (int)a.first->d_matches},
+         {"matches", (double)a.first->d_matches},
          {"rule", a.first->toString()},
           {"action", a.second->toString()}, 
           {"action-stats", a.second->getStats()} 
@@ -394,7 +394,7 @@ static void connectionThread(int sock, ComboAddress remote, string password, str
       for(const auto& a : localResponseRules) {
         Json::object rule{
           {"id", num++},
-          {"matches", (int)a.first->d_matches},
+          {"matches", (double)a.first->d_matches},
           {"rule", a.first->toString()},
           {"action", a.second->toString()},
         };
index 9e10eea9048dadf9082e7a92251df600c50ddac3..bf3a53307cc631a0828b09d53542907d218e68b3 100644 (file)
@@ -1072,7 +1072,11 @@ try
 
       uint16_t len = (uint16_t) ret;
       ComboAddress dest;
-      if (!HarvestDestinationAddress(&msgh, &dest)) {
+      if (HarvestDestinationAddress(&msgh, &dest)) {
+        /* we don't get the port, only the address */
+        dest.sin4.sin_port = cs->local.sin4.sin_port;
+      }
+      else {
         dest.sin4.sin_family = 0;
       }
 
@@ -1670,9 +1674,6 @@ struct
   string pidfile;
   string command;
   string config;
-#ifdef HAVE_LIBSODIUM
-  string setKey;
-#endif
   string uid;
   string gid;
 } g_cmdLine;
@@ -1804,7 +1805,7 @@ try
       break;
 #ifdef HAVE_LIBSODIUM
     case 'k':
-      if (B64Decode(string(optarg), g_cmdLine.setKey) < 0) {
+      if (B64Decode(string(optarg), g_key) < 0) {
         cerr<<"Unable to decode key '"<<optarg<<"'."<<endl;
         exit(EXIT_FAILURE);
       }
@@ -1870,10 +1871,6 @@ try
     setupLua(true, g_cmdLine.config);
     if (clientAddress != ComboAddress())
       g_serverControl = clientAddress;
-#ifdef HAVE_LIBSODIUM
-    if (!g_cmdLine.setKey.empty())
-      g_key = g_cmdLine.setKey;
-#endif
     doClient(g_serverControl, g_cmdLine.command);
     _exit(EXIT_SUCCESS);
   }
@@ -2176,6 +2173,19 @@ try
   _exit(EXIT_SUCCESS);
 
 }
+catch(const LuaContext::ExecutionErrorException& e) {
+  try {
+    errlog("Fatal Lua error: %s", e.what());
+    std::rethrow_if_nested(e);
+  } catch(const std::exception& e) {
+    errlog("Details: %s", e.what());
+  }
+  catch(PDNSException &ae)
+  {
+    errlog("Fatal pdns error: %s", ae.reason);
+  }
+  _exit(EXIT_FAILURE);
+}
 catch(std::exception &e)
 {
   errlog("Fatal error: %s", e.what());
index 100dd43b90848c4fd802c6d1cd2ffd31753134f4..36823820dd4de0cb19c464bc0dc18b83be5168c8 100644 (file)
@@ -673,6 +673,7 @@ extern std::string g_apiConfigDirectory;
 extern bool g_servFailOnNoPolicy;
 extern uint32_t g_hashperturb;
 extern bool g_useTCPSinglePipe;
+extern std::atomic<uint16_t> g_downstreamTCPCleanupInterval;
 
 struct ConsoleKeyword {
   std::string name;
index 05394142deaf32236c4d42d2b8f6c30a564e14ed..5ae8da3154b4cd8e3f64af70cc246fd613d4ab1e 100644 (file)
@@ -64,6 +64,7 @@ endif
 dnsdist-web.$(OBJEXT): htmlfiles.h
 
 dnsdist_SOURCES = \
+       ascii.hh \
        base64.hh \
        bpf-filter.cc bpf-filter.hh \
        dns.cc dns.hh \
diff --git a/pdns/dnsdistdist/ascii.hh b/pdns/dnsdistdist/ascii.hh
new file mode 120000 (symlink)
index 0000000..6d5e3eb
--- /dev/null
@@ -0,0 +1 @@
+../ascii.hh
\ No newline at end of file
index 80a22a81b46248870a48d71bdc248e051bd7338c..6e86d19605324f10cce716929c90e862212c2ae4 100644 (file)
@@ -1,7 +1,7 @@
 AC_DEFUN([DNSDIST_ENABLE_DNSCRYPT], [
   AC_MSG_CHECKING([whether to enable DNSCrypt support])
   AC_ARG_ENABLE([dnscrypt],
-    AS_HELP_STRING([--enable-dnscrypt], [enable DNSCrypt support (require libsodium) @<:@default=no@:>@]),
+    AS_HELP_STRING([--enable-dnscrypt], [enable DNSCrypt support (requires libsodium) @<:@default=no@:>@]),
     [enable_dnscrypt=$enableval],
     [enable_dnscrypt=no]
   )
index 3f98d84877a9f284096a7a3b80ba0f49cc2e3ecc..51115c9306887c63f0f7b1d29d6ff65ca8030f50 100644 (file)
@@ -225,7 +225,7 @@ bool DNSName::isPartOf(const DNSName& parent) const
     if (static_cast<size_t>(distance) == parent.d_storage.size()) {
       auto p = parent.d_storage.cbegin();
       for(; us != d_storage.cend(); ++us, ++p) {
-        if(dns2_tolower(*p) != dns2_tolower(*us))
+        if(dns_tolower(*p) != dns_tolower(*us))
           return false;
       }
       return true;
@@ -340,6 +340,13 @@ std::string DNSName::getRawLabel(unsigned int pos) const
   throw std::out_of_range("trying to get label at position "+std::to_string(pos)+" of a DNSName that only has "+std::to_string(currentPos)+" labels");
 }
 
+DNSName DNSName::getLastLabel() const
+{
+  DNSName ret(*this);
+  ret.trimToLabels(1);
+  return ret;
+}
+
 bool DNSName::chopOff()
 {
   if(d_storage.empty() || d_storage[0]==0)
index eba45665cab76660b73a54a9d02f838dbef228e7..c57faf996fb05c04e199dfdb50f3169af2ede2d6 100644 (file)
@@ -34,6 +34,8 @@
 #include <boost/container/string.hpp>
 #endif
 
+#include "ascii.hh"
+
 uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t init);
 
 // #include "dns.hh"
@@ -53,13 +55,6 @@ uint32_t burtleCI(const unsigned char* k, uint32_t length, uint32_t init);
    NOTE: For now, everything MUST be . terminated, otherwise it is an error
 */
 
-inline unsigned char dns2_tolower(unsigned char c)
-{
-  if(c>='A' && c<='Z')
-    return c+('a'-'A');
-  return c;
-}
-
 class DNSName
 {
 public:
@@ -83,6 +78,7 @@ public:
   void prependRawLabel(const std::string& str); //!< Prepend this unescaped label
   std::vector<std::string> getRawLabels() const; //!< Individual raw unescaped labels
   std::string getRawLabel(unsigned int pos) const; //!< Get the specified raw unescaped label
+  DNSName getLastLabel() const; //!< Get the DNSName of the last label
   bool chopOff();                               //!< Turn www.powerdns.com. into powerdns.com., returns false for .
   DNSName makeRelative(const DNSName& zone) const;
   DNSName makeLowerCase() const
@@ -90,7 +86,7 @@ public:
     DNSName ret;
     ret.d_storage = d_storage;
     for(auto & c : ret.d_storage) {
-      c=dns2_tolower(c);
+      c=dns_tolower(c);
     }
     return ret;
   }
@@ -128,7 +124,7 @@ public:
     return std::lexicographical_compare(d_storage.rbegin(), d_storage.rend(), 
                                 rhs.d_storage.rbegin(), rhs.d_storage.rend(),
                                 [](const unsigned char& a, const unsigned char& b) {
-                                         return dns2_tolower(a) < dns2_tolower(b); 
+                                         return dns_tolower(a) < dns_tolower(b);
                                        }); // note that this is case insensitive, including on the label lengths
   }
 
@@ -192,7 +188,7 @@ inline bool DNSName::canonCompare(const DNSName& rhs) const
                                          rhs.d_storage.c_str() + rhspos[rhscount] + 1, 
                                          rhs.d_storage.c_str() + rhspos[rhscount] + 1 + *(rhs.d_storage.c_str() + rhspos[rhscount]),
                                          [](const unsigned char& a, const unsigned char& b) {
-                                           return dns2_tolower(a) < dns2_tolower(b); 
+                                           return dns_tolower(a) < dns_tolower(b);
                                          });
     
     //    cout<<"Forward: "<<res<<endl;
@@ -204,7 +200,7 @@ inline bool DNSName::canonCompare(const DNSName& rhs) const
                                          d_storage.c_str() + ourpos[ourcount] + 1, 
                                          d_storage.c_str() + ourpos[ourcount] + 1 + *(d_storage.c_str() + ourpos[ourcount]),
                                          [](const unsigned char& a, const unsigned char& b) {
-                                           return dns2_tolower(a) < dns2_tolower(b); 
+                                           return dns_tolower(a) < dns_tolower(b);
                                          });
     //    cout<<"Reverse: "<<res<<endl;
     if(res)
@@ -374,7 +370,7 @@ bool DNSName::operator==(const DNSName& rhs) const
   auto us = d_storage.cbegin();
   auto p = rhs.d_storage.cbegin();
   for(; us != d_storage.cend() && p != rhs.d_storage.cend(); ++us, ++p) { 
-    if(dns2_tolower(*p) != dns2_tolower(*us))
+    if(dns_tolower(*p) != dns_tolower(*us))
       return false;
   }
   return true;
index 1b28744a460e8190edc0316aa0480e6e38132ed7..6af20a75e61637e98825cc875dd6df44c9f94efb 100644 (file)
@@ -130,6 +130,7 @@ DNSPacket::DNSPacket(const DNSPacket &orig)
   d=orig.d;
 
   d_isQuery = orig.d_isQuery;
+  d_hash = orig.d_hash;
 }
 
 void DNSPacket::setRcode(int v)
index 4b740344a9b29a2a444ce88bb08275435f0bbba2..7fb90f3cf07292cd406ae6b332a12409300c3625 100644 (file)
@@ -137,6 +137,9 @@ public:
     d_ednsrcode=extRCode;
   };
   uint8_t getEDNSRCode() const { return d_ednsrcode; };
+  uint32_t getHash() const { return d_hash; };
+  void setHash(uint32_t hash) { d_hash = hash; };
+
   //////// DATA !
 
   DNSName qdomain;  //!< qname of the question 4 - unsure how this is used
@@ -188,6 +191,8 @@ private:
   // WARNING! This is really 12 bits
   uint16_t d_ednsrcode;
 
+  uint32_t d_hash{0};
+  
   bool d_compress; // 1
   bool d_tsigtimersonly;
   bool d_wantsnsid;
index 8f6baa7cbed2d0e715d8dacd3800c95bdd4996e7..37493a0863f88c178c4564845024d9f48ad1100b 100644 (file)
@@ -286,7 +286,7 @@ void MOADNSParser::init(bool query, const char *packet, unsigned int len)
 
       if (query && (dr.d_place == DNSResourceRecord::ANSWER || dr.d_place == DNSResourceRecord::AUTHORITY || (dr.d_type != QType::OPT && dr.d_type != QType::TSIG && dr.d_type != QType::SIG && dr.d_type != QType::TKEY) || ((dr.d_type == QType::TSIG || dr.d_type == QType::SIG || dr.d_type == QType::TKEY) && dr.d_class != QClass::ANY))) {
 //        cerr<<"discarding RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
-        dr.d_content=std::shared_ptr<DNSRecordContent>(new UnknownRecordContent(dr, pr));
+        dr.d_content=std::make_shared<UnknownRecordContent>(dr, pr);
       }
       else {
 //        cerr<<"parsing RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
index 10a6ab17afa3d88a53e4641c2346740a6d87d0a9..1f0ca6a5b925a4b92242316279d625ecd99e4a82 100644 (file)
@@ -361,7 +361,7 @@ public:
 
   typedef vector<pair<DNSRecord, uint16_t > > answers_t;
   
-  //! All answers contained in this packet
+  //! All answers contained in this packet (everything *but* the question section)
   answers_t d_answers;
 
   shared_ptr<PacketReader> getPacketReader(uint16_t offset)
index 91c60a416b6be28da38afe3f4e8f1aa972ea1d32..95d24edcc88d07fd06b6133d810cca5c58a0f247 100644 (file)
@@ -34,7 +34,6 @@
 #include "dns_random.hh"
 
 extern StatBag S;
-extern PacketCache PC;
 
 DNSProxy::DNSProxy(const string &remote)
 {
@@ -42,7 +41,10 @@ DNSProxy::DNSProxy(const string &remote)
   d_resanswers=S.getPointer("recursing-answers");
   d_resquestions=S.getPointer("recursing-questions");
   d_udpanswers=S.getPointer("udp-answers");
-  ComboAddress remaddr(remote, 53);
+
+  vector<string> addresses;
+  stringtok(addresses, remote, " ,\t");
+  ComboAddress remaddr(addresses[0], 53);
   
   if((d_sock=socket(remaddr.sin4.sin_family, SOCK_DGRAM,0))<0)
     throw PDNSException(string("socket: ")+strerror(errno));
@@ -269,7 +271,6 @@ void DNSProxy::mainloop(void)
         if(sendmsg(i->second.outsock, &msgh, 0) < 0)
           L<<Logger::Warning<<"dnsproxy.cc: Error sending reply with sendmsg (socket="<<i->second.outsock<<"): "<<strerror(errno)<<endl;
         
-        PC.insert(&q, &p, true);
         i->second.created=0;
       }
     }
index 11fc94a9207a456969b07dfff171d70104ebefab..0a69e7955a1175278bb345c66aeca05bef3771dd 100644 (file)
@@ -500,6 +500,9 @@ uint16_t DNSKEYRecordContent::getTag()
 }
 
 
+/*
+ * Fills `eo` by parsing the EDNS(0) OPT RR (RFC 6891)
+ */
 bool getEDNSOpts(const MOADNSParser& mdp, EDNSOpts* eo)
 {
   eo->d_Z=0;
index 9d8a4e0b7243816bc686c4d5bc073f0765ba0a5d..916a23b9958ed866630e8cfe8bb7e2a51e5db80b 100644 (file)
@@ -94,14 +94,14 @@ void DNSPacketWriter::startRecord(const DNSName& name, uint16_t qtype, uint32_t
   d_sor=d_content.size(); // this will remind us where to stuff the record size
 }
 
-void DNSPacketWriter::addOpt(uint16_t udpsize, int extRCode, int Z, const vector<pair<uint16_t,string> >& options)
+void DNSPacketWriter::addOpt(uint16_t udpsize, int extRCode, int Z, const vector<pair<uint16_t,string> >& options, uint8_t version)
 {
   uint32_t ttl=0;
 
   EDNS0Record stuff;
 
   stuff.extRCode=extRCode;
-  stuff.version=0;
+  stuff.version=version;
   stuff.Z=htons(Z);
 
   static_assert(sizeof(EDNS0Record) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(ttl)");
index 3c5f33e266d123fe094bdf45c8a88204da2dedb1..49cf6955cac9ee49dac7bff778d7a02eac396deb 100644 (file)
@@ -70,7 +70,7 @@ public:
 
   /** Shorthand way to add an Opt-record, for example for EDNS0 purposes */
   typedef vector<pair<uint16_t,std::string> > optvect_t;
-  void addOpt(uint16_t udpsize, int extRCode, int Z, const optvect_t& options=optvect_t());
+  void addOpt(uint16_t udpsize, int extRCode, int Z, const optvect_t& options=optvect_t(), uint8_t version=0);
 
   /** needs to be called after the last record is added, but can be called again and again later on. Is called internally by startRecord too.
       The content of the vector<> passed to the constructor is inconsistent until commit is called.
index c787a80ef6d001605dec9c6ef62c8e4988122bf7..1f465054e2287283897de440294b58eebad59898 100644 (file)
@@ -22,7 +22,9 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
-#include "packetcache.hh"
+#include "auth-caches.hh"
+#include "auth-querycache.hh"
+#include "auth-packetcache.hh"
 #include "utility.hh"
 #include "dynhandler.hh"
 #include "statbag.hh"
@@ -121,14 +123,13 @@ string DLUptimeHandler(const vector<string>&parts, Utility::pid_t ppid)
 
 string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
 {
-  extern PacketCache PC;  
   DNSSECKeeper dk;
   ostringstream os;
   int ret=0;
 
   if(parts.size()>1) {
     for (vector<string>::const_iterator i=++parts.begin();i<parts.end();++i) {
-      ret+=PC.purge(*i);
+      ret+=purgeAuthCaches(*i);
       if(!boost::ends_with(*i, "$"))
        dk.clearCaches(DNSName(*i));
       else
@@ -136,7 +137,7 @@ string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
     }
   }
   else {
-    ret=PC.purge();
+    ret = purgeAuthCaches();
     dk.clearAllCaches();
   }
 
@@ -146,11 +147,13 @@ string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
 
 string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid)
 {
-  extern PacketCache PC;  
-  map<char,int> counts=PC.getCounts();
+  extern AuthPacketCache PC;
+  extern AuthQueryCache QC;
+  map<char,uint64_t> counts=QC.getCounts();
+  uint64_t packetEntries = PC.size();
   ostringstream os;
   bool first=true;
-  for(map<char,int>::const_iterator i=counts.begin();i!=counts.end();++i) {
+  for(map<char,uint64_t>::const_iterator i=counts.begin();i!=counts.end();++i) {
     if(!first) 
       os<<", ";
     first=false;
@@ -159,13 +162,12 @@ string DLCCHandler(const vector<string>&parts, Utility::pid_t ppid)
       os<<"negative queries: ";
     else if(i->first=='Q')
       os<<"queries: ";
-    else if(i->first=='p')
-      os<<"packets: ";
     else 
       os<<"unknown: ";
 
     os<<i->second;
   }
+  os<<"packets: "<<packetEntries;
 
   return os.str();
 }
index 1e5e56f2bf04d50d6a2a87debf920ce2c28a3982..eade5df5fffaf2d007b74fe143d686e781ba988c 100644 (file)
@@ -66,6 +66,45 @@ int getEDNSOption(char* optRR, const size_t len, uint16_t wantedOption, char **
   return ENOENT;
 }
 
+/* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOptions(const char* optRR, const size_t len, std::map<uint16_t, EDNSOptionView>& options)
+{
+  assert(optRR != NULL);
+  size_t pos = 0;
+  if (len < DNS_RDLENGTH_SIZE)
+    return EINVAL;
+
+  const uint16_t rdLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+  size_t rdPos = 0;
+  pos += DNS_RDLENGTH_SIZE;
+  if ((pos + rdLen) > len) {
+    return EINVAL;
+  }
+
+  while(len >= (pos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE) &&
+        rdLen >= (rdPos + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+    const uint16_t optionCode = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+    pos += EDNS_OPTION_CODE_SIZE;
+    rdPos += EDNS_OPTION_CODE_SIZE;
+    const uint16_t optionLen = (((unsigned char) optRR[pos]) * 256) + ((unsigned char) optRR[pos+1]);
+    pos += EDNS_OPTION_LENGTH_SIZE;
+    rdPos += EDNS_OPTION_LENGTH_SIZE;
+    if (optionLen > (rdLen - rdPos) || optionLen > (len - pos))
+      return EINVAL;
+
+    EDNSOptionView view;
+    view.content = optRR + pos;
+    view.size = optionLen;
+    options[optionCode] = view;
+
+    /* skip this option */
+    pos += optionLen;
+    rdPos += optionLen;
+  }
+
+  return 0;
+}
+
 void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res)
 {
   const uint16_t ednsOptionCode = htons(optionCode);
index f9cb17093bb30c75528be422c0a9f69f94f2ff6c..ea0279a3e069d6bee96018b4c0bf6852afddf053 100644 (file)
@@ -31,6 +31,16 @@ struct EDNSOptionCode
 
 /* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
 int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize);
+
+struct EDNSOptionView
+{
+  const char* content{nullptr};
+  uint16_t size{0};
+};
+
+/* extract all EDNS0 options from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOptions(const char* optRR, size_t len, std::map<uint16_t, EDNSOptionView>& options);
+
 void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res);
 
 #endif
index 88e5320a8206e8adc62d066da9d4b3b101078ae5..d87c55ccc02020713060f5a96af2857ca73756c6 100644 (file)
@@ -156,9 +156,21 @@ void DNSFilterEngine::clear(size_t zone)
   z.qpolAddr.clear();
   z.postpolAddr.clear();
   z.propolName.clear();
+  z.propolNSAddr.clear();
   z.qpolName.clear();
 }
 
+void DNSFilterEngine::clear()
+{
+  for(auto& z : d_zones) {
+    z.qpolAddr.clear();
+    z.postpolAddr.clear();
+    z.propolName.clear();
+    z.propolNSAddr.clear();
+    z.qpolName.clear();
+  }
+}
+
 void DNSFilterEngine::addClientTrigger(const Netmask& nm, Policy pol, size_t zone)
 {
   assureZones(zone);
index f06e08e0b4e258f579632dbc742371020d2a4e6b..8b8601966d2f73bf94faa29c96d5482fb7a932f2 100644 (file)
@@ -401,3 +401,45 @@ bool sendSizeAndMsgWithTimeout(int sock, uint16_t bufferLen, const char* buffer,
 
   return false;
 }
+
+/* requires a non-blocking socket.
+   On Linux, we could use MSG_DONTWAIT on a blocking socket
+   but this is not portable.
+*/
+bool isTCPSocketUsable(int sock)
+{
+  int err = 0;
+  char buf = '\0';
+  size_t buf_size = sizeof(buf);
+
+  do {
+    ssize_t got = recv(sock, &buf, buf_size, MSG_PEEK);
+
+    if (got > 0) {
+      /* socket is usable, some data is even waiting to be read */
+      return true;
+    }
+    else if (got == 0) {
+      /* other end has closed the socket */
+      return false;
+    }
+    else {
+      int err = errno;
+
+      if (err == EAGAIN || err == EWOULDBLOCK) {
+        /* socket is usable, no data waiting */
+        return true;
+      }
+      else {
+        if (err != EINTR) {
+          /* something is wrong, could be ECONNRESET,
+             ENOTCONN, EPIPE, but anyway this socket is
+             not usable. */
+          return false;
+        }
+      }
+    }
+  } while (err == EINTR);
+
+  return false;
+}
index 15be20d832a096df3318b615004612798dfdbcaa..5c53fa6d95a28446cf35281020eafb4c2254271b 100644 (file)
@@ -911,6 +911,8 @@ void fillMSGHdr(struct msghdr* msgh, struct iovec* iov, char* cbuf, size_t cbufs
 ssize_t sendfromto(int sock, const char* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to);
 ssize_t sendMsgWithTimeout(int fd, const char* buffer, size_t len, int timeout, ComboAddress& dest, const ComboAddress& local, unsigned int localItf);
 bool sendSizeAndMsgWithTimeout(int sock, uint16_t bufferLen, const char* buffer, int idleTimeout, const ComboAddress* dest, const ComboAddress* local, unsigned int localItf, int totalTimeout, int flags);
+/* requires a non-blocking, connected TCP socket */
+bool isTCPSocketUsable(int sock);
 
 extern template class NetmaskTree<bool>;
 
index d0b1360142c0986c2fb8e60e598562204e2ffc1b..4a0c4a1161e789d139ecb820cdd405ace1489d6c 100644 (file)
@@ -33,6 +33,8 @@ class Lock
 {
   pthread_mutex_t *d_lock;
 public:
+  Lock(const Lock& rhs) = delete;
+  Lock& operator=(const Lock& rhs) = delete;
 
   Lock(pthread_mutex_t *lock) : d_lock(lock)
   {
@@ -68,9 +70,19 @@ public:
   {
     if(g_singleThreaded)
       return;
+    if(d_lock) // might have been moved
+      pthread_rwlock_unlock(d_lock);
+  }
 
-    pthread_rwlock_unlock(d_lock);
+  WriteLock(WriteLock&& rhs)
+  {
+    d_lock = rhs.d_lock;
+    rhs.d_lock=0;
   }
+  WriteLock(const WriteLock& rhs) = delete;
+  WriteLock& operator=(const WriteLock& rhs) = delete;
+
+
 };
 
 class TryWriteLock
@@ -78,6 +90,8 @@ class TryWriteLock
   pthread_rwlock_t *d_lock;
   bool d_havelock;
 public:
+  TryWriteLock(const TryWriteLock& rhs) = delete;
+  TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
 
   TryWriteLock(pthread_rwlock_t *lock) : d_lock(lock)
   {
@@ -91,12 +105,20 @@ public:
       throw PDNSException("error acquiring rwlock tryrwlock: "+stringerror());
     d_havelock=(errno==0);
   }
+
+  TryWriteLock(TryWriteLock&& rhs)
+  {
+    d_lock = rhs.d_lock;
+    rhs.d_lock=0;
+  }
+
+  
   ~TryWriteLock()
   {
     if(g_singleThreaded)
       return;
 
-    if(d_havelock)
+    if(d_havelock && d_lock) // we might be moved
       pthread_rwlock_unlock(d_lock);
   }
   bool gotIt()
@@ -113,6 +135,8 @@ class TryReadLock
   pthread_rwlock_t *d_lock;
   bool d_havelock;
 public:
+  TryReadLock(const TryReadLock& rhs) = delete;
+  TryReadLock& operator=(const TryReadLock& rhs) = delete;
 
   TryReadLock(pthread_rwlock_t *lock) : d_lock(lock)
   {
@@ -125,12 +149,18 @@ public:
       throw PDNSException("error acquiring rwlock tryrdlock: "+stringerror());
     d_havelock=(errno==0);
   }
+  TryReadLock(TryReadLock&& rhs)
+  {
+    d_lock = rhs.d_lock;
+    rhs.d_lock=0;
+  }
+
   ~TryReadLock()
   {
     if(g_singleThreaded)
       return;
 
-    if(d_havelock)
+    if(d_havelock && d_lock)
       pthread_rwlock_unlock(d_lock);
   }
   bool gotIt()
@@ -154,23 +184,22 @@ public:
       return;
 
     if((errno=pthread_rwlock_rdlock(d_lock)))
-      throw PDNSException("error acquiring rwlock tryrwlock: "+stringerror());
+      throw PDNSException("error acquiring rwlock readlock: "+stringerror());
   }
   ~ReadLock()
   {
     if(g_singleThreaded)
       return;
-
-    pthread_rwlock_unlock(d_lock);
+    if(d_lock) // may have been moved
+      pthread_rwlock_unlock(d_lock);
   }
-  
-  void upgrade()
-  {
-    if(g_singleThreaded)
-      return;
 
-    pthread_rwlock_unlock(d_lock);
-    pthread_rwlock_wrlock(d_lock);
+  ReadLock(ReadLock&& rhs)
+  {
+    d_lock = rhs.d_lock;
+    rhs.d_lock=0;
   }
+  ReadLock(const ReadLock& rhs) = delete;
+  ReadLock& operator=(const ReadLock& rhs) = delete;
 };
 #endif
index 2fac53682fccd93a5af1bd2ee9f6c033abf65e7f..6445db8c6ecd52575803fdd1508153a7470c9d0e 100644 (file)
@@ -32,9 +32,6 @@
 #include "rec-snmp.hh"
 #include <unordered_set>
 
-#undef L
-#include "ext/luawrapper/include/LuaContext.hpp"
-
 static int followCNAMERecords(vector<DNSRecord>& ret, const QType& qtype)
 {
   vector<DNSRecord> resolved;
@@ -387,7 +384,10 @@ RecursorLua4::RecursorLua4(const std::string& fname)
   d_lw->registerMember("type", &DNSRecord::d_type);
   d_lw->registerMember("ttl", &DNSRecord::d_ttl);
   d_lw->registerMember("place", &DNSRecord::d_place);
-  
+
+  d_lw->registerMember("size", &EDNSOptionView::size);
+  d_lw->registerFunction<std::string(EDNSOptionView::*)()>("getContent", [](const EDNSOptionView& option) { return std::string(option.content, option.size); });
+
   d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.d_content->getZoneRepresentation(); });
   d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) { 
       boost::optional<ComboAddress> ret;
@@ -587,10 +587,10 @@ bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& loca
   return false; // don't block
 }
 
-unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::unordered_map<string,string>& data)
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const std::map<uint16_t, EDNSOptionView>& ednsOptions)
 {
   if(d_gettag) {
-    auto ret = d_gettag(remote, ednssubnet, local, qname, qtype);
+    auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions);
 
     if (policyTags) {
       const auto& tags = std::get<1>(ret);
@@ -600,7 +600,7 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
         }
       }
     }
-    const auto& dataret = std::get<2>(ret);
+    const auto dataret = std::get<2>(ret);
     if (dataret) {
       data = *dataret;
     }
index 80a829622bfbc3ecb027cb5817569e6647a053a2..7305b559d5ee97cdd81dc425b05f6d86a1091d12 100644 (file)
 #include "namespaces.hh"
 #include "dnsrecords.hh"
 #include "filterpo.hh"
+#include "ednsoptions.hh"
+
 #include <unordered_map>
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -35,6 +38,12 @@ unsigned int getRecursorThreadId();
 
 class LuaContext;
 
+#if defined(HAVE_LUA)
+#undef L
+#include "ext/luawrapper/include/LuaContext.hpp"
+#define L theL()
+#endif
+
 class RecursorLua4 : public boost::noncopyable
 {
 private:
@@ -88,11 +97,11 @@ public:
     string udpAnswer;
     string udpCallback;
     
-    std::unordered_map<string,string> data;
+    LuaContext::LuaObject data;
     DNSName followupName;
   };
 
-  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::unordered_map<string,string>& data);
+  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const std::map<uint16_t, EDNSOptionView>&);
 
   bool prerpz(DNSQuestion& dq, int& ret);
   bool preresolve(DNSQuestion& dq, int& ret);
@@ -112,8 +121,7 @@ public:
             d_postresolve);
   }
 
-  typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<std::unordered_map<string,string> > >(ComboAddress, Netmask, ComboAddress, DNSName, 
-uint16_t)> gettag_t;
+  typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const std::map<uint16_t, EDNSOptionView>&)> gettag_t;
   gettag_t d_gettag; // public so you can query if we have this hooked
 
 private:
index 310b11bec661af684cc2f719d4a0670ca1ce3464..667cb76486be5f628674ddfb4d8a10545c24e83c 100644 (file)
@@ -22,7 +22,7 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
-#include "packetcache.hh"
+#include "auth-caches.hh"
 #include "utility.hh"
 #include <errno.h>
 #include "communicator.hh"
@@ -149,8 +149,7 @@ void CommunicatorClass::masterUpdateCheck(PacketHandler *P)
   // do this via the FindNS class, d_fns
   
   for(auto& di : cmdomains) {
-    extern PacketCache PC;
-    PC.purgeExact(di.zone);
+    purgeAuthCachesExact(di.zone);
     queueNotifyDomain(di, B);
     di.backend->setNotified(di.id, di.serial);
   }
index 95493279fdcfafda802f7fbd4cab81e9c6e1338b..fe5f796fdcbad87f08260158e3ace748f86edd8f 100644 (file)
@@ -235,13 +235,6 @@ inline bool dns_isspace(char c)
   return c==' ' || c=='\t' || c=='\r' || c=='\n';
 }
 
-inline unsigned char dns_tolower(unsigned char c)
-{
-  if(c>='A' && c<='Z')
-    c+='a'-'A';
-  return c;
-}
-
 inline unsigned char dns_toupper(unsigned char c)
 {
   if(c>='a' && c<='z')
diff --git a/pdns/packetcache.cc b/pdns/packetcache.cc
deleted file mode 100644 (file)
index 295f4fc..0000000
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * This file is part of PowerDNS or dnsdist.
- * Copyright -- PowerDNS.COM B.V. and its contributors
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of version 2 of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * In addition, for the avoidance of any doubt, permission is granted to
- * link this program with OpenSSL and to (re)distribute the binaries
- * produced as the result of such linking.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#include "utility.hh"
-#include "packetcache.hh"
-#include "logger.hh"
-#include "arguments.hh"
-#include "statbag.hh"
-#include <map>
-#include <boost/algorithm/string.hpp>
-
-const unsigned int PacketCache::s_mincleaninterval, PacketCache::s_maxcleaninterval;
-
-extern StatBag S;
-
-PacketCache::PacketCache()
-{
-  d_ops=0;
-  d_maps.resize(1024);
-  for(auto& mc : d_maps) {
-    pthread_rwlock_init(&mc.d_mut, 0);
-  }
-
-  d_ttl=-1;
-
-  d_lastclean=time(0);
-  d_cleanskipped=false;
-  d_nextclean=d_cleaninterval=4096;
-
-  S.declare("packetcache-hit");
-  S.declare("packetcache-miss");
-  S.declare("packetcache-size");
-
-  d_statnumhit=S.getPointer("packetcache-hit");
-  d_statnummiss=S.getPointer("packetcache-miss");
-  d_statnumentries=S.getPointer("packetcache-size");
-}
-
-PacketCache::~PacketCache()
-{
-  try {
-    //  WriteLock l(&d_mut);
-    vector<WriteLock*> locks;
-    for(auto& mc : d_maps) {
-      locks.push_back(new WriteLock(&mc.d_mut));
-    }
-    for(auto wl : locks) {
-      delete wl;
-    }
-  }
-  catch(...) {
-  }
-}
-
-
-
-int PacketCache::get(DNSPacket *p, DNSPacket *cached)
-{
-  extern StatBag S;
-
-  if(d_ttl<0) 
-    getTTLS();
-
-  cleanupIfNeeded();
-
-  if(!d_ttl) {
-    (*d_statnummiss)++;
-    return 0;
-  }
-    
-  if(ntohs(p->d.qdcount)!=1) // we get confused by packets with more than one question
-    return 0;
-
-  string value;
-  bool haveSomething;
-  {
-    auto& mc=getMap(p->qdomain);
-    TryReadLock l(&mc.d_mut); // take a readlock here
-    if(!l.gotIt()) {
-      S.inc("deferred-cache-lookup");
-      return 0;
-    }
-
-    uint16_t maxReplyLen = p->d_tcp ? 0xffff : p->getMaxReplyLen();
-    haveSomething=getEntryLocked(p->qdomain, p->qtype, PacketCache::PACKETCACHE, value, -1, maxReplyLen, p->d_dnssecOk, p->hasEDNS());
-  }
-  if(haveSomething) {
-    (*d_statnumhit)++;
-    if(cached->noparse(value.c_str(), value.size()) < 0)
-      return 0;
-    cached->spoofQuestion(p); // for correct case
-    cached->qdomain=p->qdomain;
-    cached->qtype=p->qtype;
-    return 1;
-  }
-
-  //  cerr<<"Packet cache miss for '"<<p->qdomain<<"'"<<endl;
-  (*d_statnummiss)++;
-  return 0; // bummer
-}
-
-void PacketCache::getTTLS()
-{
-  d_ttl=::arg().asNum("cache-ttl");
-}
-
-
-void PacketCache::insert(DNSPacket *q, DNSPacket *r, unsigned int maxttl)
-{
-  if(d_ttl < 0)
-    getTTLS();
-  
-  if(ntohs(q->d.qdcount)!=1) {
-    return; // do not try to cache packets with multiple questions
-  }
-
-  if(q->qclass != QClass::IN) // we only cache the INternet
-    return;
-
-  uint16_t maxReplyLen = q->d_tcp ? 0xffff : q->getMaxReplyLen();
-  unsigned int ourttl = d_ttl;
-  if(maxttl<ourttl)
-    ourttl=maxttl;
-  insert(q->qdomain, q->qtype, PacketCache::PACKETCACHE, r->getString(), ourttl, -1,
-    maxReplyLen, q->d_dnssecOk, q->hasEDNS());
-}
-
-// universal key appears to be: qname, qtype, kind (packet, query cache), optionally zoneid
-void PacketCache::insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID, 
-  unsigned int maxReplyLen, bool dnssecOk, bool EDNS)
-{
-  cleanupIfNeeded();
-
-  if(!ttl)
-    return;
-  
-  //cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", qtype: "<<qtype.getName()<<", ttl: "<<ttl<<", maxreplylen: "<<maxReplyLen<<", hasEDNS: "<<EDNS<<endl;
-  CacheEntry val;
-  val.created=time(0);
-  val.ttd=val.created+ttl;
-  val.qname=qname;
-  val.qtype=qtype.getCode();
-  val.value=value;
-  val.ctype=cet;
-  val.maxReplyLen = maxReplyLen;
-  val.dnssecOk = dnssecOk;
-  val.zoneID = zoneID;
-  val.hasEDNS = EDNS;
-  
-  auto& mc = getMap(val.qname);
-
-  TryWriteLock l(&mc.d_mut);
-  if(l.gotIt()) { 
-    bool success;
-    cmap_t::iterator place;
-    tie(place, success)=mc.d_map.insert(val);
-
-    if(!success)
-      mc.d_map.replace(place, val);
-    else
-      (*d_statnumentries)++;
-  }
-  else 
-    S.inc("deferred-cache-inserts"); 
-}
-
-void PacketCache::insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const vector<DNSZoneRecord>& value, unsigned int ttl, int zoneID)
-{
-  cleanupIfNeeded();
-
-  if(!ttl)
-    return;
-  
-  //cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", qtype: "<<qtype.getName()<<", ttl: "<<ttl<<", maxreplylen: "<<maxReplyLen<<", hasEDNS: "<<EDNS<<endl;
-  CacheEntry val;
-  val.created=time(0);
-  val.ttd=val.created+ttl;
-  val.qname=qname;
-  val.qtype=qtype.getCode();
-  val.drs=value;
-  val.ctype=cet;
-  val.maxReplyLen = 0;
-  val.dnssecOk = false;
-  val.zoneID = zoneID;
-  val.hasEDNS = false;
-  
-  auto& mc = getMap(val.qname);
-
-  TryWriteLock l(&mc.d_mut);
-  if(l.gotIt()) { 
-    bool success;
-    cmap_t::iterator place;
-    tie(place, success)=mc.d_map.insert(val);
-
-    if(!success)
-      mc.d_map.replace(place, val);
-    else
-      (*d_statnumentries)++;
-  }
-  else 
-    S.inc("deferred-cache-inserts"); 
-}
-
-
-/* clears the entire packetcache. */
-int PacketCache::purge()
-{
-  int delcount=0;
-  for(auto& mc : d_maps) {
-    WriteLock l(&mc.d_mut);
-    delcount+=mc.d_map.size();
-    mc.d_map.clear();
-  }
-  d_statnumentries->store(0);
-  return delcount;
-}
-
-int PacketCache::purgeExact(const DNSName& qname)
-{
-  int delcount=0;
-  auto& mc = getMap(qname);
-
-  WriteLock l(&mc.d_mut);
-  auto range = mc.d_map.equal_range(tie(qname));
-  if(range.first != range.second) {
-    delcount+=distance(range.first, range.second);
-    mc.d_map.erase(range.first, range.second);
-  }
-  *d_statnumentries-=delcount;
-  return delcount;
-}
-
-/* purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
-int PacketCache::purge(const string &match)
-{
-  if(ends_with(match, "$")) {
-    int delcount=0;
-    string prefix(match);
-    prefix.resize(prefix.size()-1);
-    DNSName dprefix(prefix);
-    for(auto& mc : d_maps) {
-      WriteLock l(&mc.d_mut);
-      cmap_t::const_iterator iter = mc.d_map.lower_bound(tie(dprefix));
-      auto start=iter;
-
-      for(; iter != mc.d_map.end(); ++iter) {
-       if(!iter->qname.isPartOf(dprefix)) {
-         break;
-       }
-       delcount++;
-      }
-      mc.d_map.erase(start, iter);
-    }
-    *d_statnumentries-=delcount;
-    return delcount;
-  }
-  else {
-    return purgeExact(DNSName(match));
-  }
-}
-// called from ueberbackend
-bool PacketCache::getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& value, int zoneID)
-{
-  if(d_ttl<0) 
-    getTTLS();
-
-  cleanupIfNeeded();
-
-  auto& mc=getMap(qname);
-
-  TryReadLock l(&mc.d_mut); // take a readlock here
-  if(!l.gotIt()) {
-    S.inc( "deferred-cache-lookup");
-    return false;
-  }
-
-  return getEntryLocked(qname, qtype, cet, value, zoneID);
-}
-
-
-bool PacketCache::getEntryLocked(const DNSName &qname, const QType& qtype, CacheEntryType cet, string& value, int zoneID,
-  unsigned int maxReplyLen, bool dnssecOK, bool hasEDNS)
-{
-  uint16_t qt = qtype.getCode();
-  //cerr<<"Lookup for maxReplyLen: "<<maxReplyLen<<endl;
-  auto& mc=getMap(qname);
-  //  cmap_t::const_iterator i=mc.d_map.find(tie(qname, qt, cet, zoneID, maxReplyLen, dnssecOK, hasEDNS));
-
-  auto& idx = boost::multi_index::get<UnorderedNameTag>(mc.d_map);
-  auto range=idx.equal_range(tie(qname, qt, cet, zoneID));
-
-  if(range.first == range.second)
-    return false;
-  time_t now=time(0);
-  for(auto iter = range.first ; iter != range.second; ++iter) {
-    if(maxReplyLen == iter->maxReplyLen && dnssecOK == iter->dnssecOk && hasEDNS == iter->hasEDNS ) {
-      if(iter->ttd > now) {
-        value = iter->value;
-        return true;
-      }
-    }
-  }
-  
-  return false;
-}
-                          
-bool PacketCache::getEntryLocked(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& value, int zoneID)
-{
-  uint16_t qt = qtype.getCode();
-  //cerr<<"Lookup for maxReplyLen: "<<maxReplyLen<<endl;
-  auto& mc=getMap(qname);
-  auto& idx = boost::multi_index::get<UnorderedNameTag>(mc.d_map);
-  auto i=idx.find(tie(qname, qt, cet, zoneID));
-  if(i==idx.end())
-    return false;
-
-  time_t now=time(0);
-  if(i->ttd > now) {
-    value = i->drs;
-    return true;
-  }
-  return false;
-}
-
-
-map<char,int> PacketCache::getCounts()
-{
-  int packets=0, queryCacheEntries=0, negQueryCacheEntries=0;
-
-  for(auto& mc : d_maps) {
-    ReadLock l(&mc.d_mut);
-    
-    for(cmap_t::const_iterator iter = mc.d_map.begin() ; iter != mc.d_map.end(); ++iter) {
-      if(iter->ctype == PACKETCACHE)
-         packets++;
-      else if(iter->ctype == QUERYCACHE) {
-       if(iter->value.empty())
-         negQueryCacheEntries++;
-       else
-         queryCacheEntries++;
-      }
-    }
-  }
-  map<char,int> ret;
-
-  ret['!']=negQueryCacheEntries;
-  ret['Q']=queryCacheEntries;
-  ret['p']=packets;
-  return ret;
-}
-
-
-void PacketCache::cleanup()
-{
-  unsigned int maxCached = ::arg().asNum("max-cache-entries");
-  unsigned long cacheSize = *d_statnumentries;
-
-  // two modes - if toTrim is 0, just look through 10%  of the cache and nuke everything that is expired
-  // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
-  unsigned int toTrim = 0, lookAt = 0;
-  if(maxCached && cacheSize > maxCached) {
-    toTrim = cacheSize - maxCached;
-    lookAt = 5 * toTrim;
-  } else {
-    lookAt = cacheSize / 10;
-  }
-
-  DLOG(L<<"Starting cache clean, cacheSize: "<<cacheSize<<", lookAt: "<<lookAt<<", toTrim: "<<toTrim<<endl);
-
-  time_t now = time(0);
-  unsigned int totErased = 0;
-  for(auto& mc : d_maps) {
-    WriteLock wl(&mc.d_mut);
-    auto& sidx = boost::multi_index::get<SequenceTag>(mc.d_map);
-    unsigned int erased = 0, lookedAt = 0;
-    for(auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
-      if(i->ttd < now) {
-        i = sidx.erase(i);
-        erased++;
-      } else {
-        ++i;
-      }
-
-      if(toTrim && erased > toTrim / d_maps.size())
-        break;
-
-      if(lookedAt > lookAt / d_maps.size())
-        break;
-    }
-    totErased += erased;
-  }
-  *d_statnumentries -= totErased;
-
-  DLOG(L<<"Done with cache clean, cacheSize: "<<*d_statnumentries<<", totErased"<<totErased<<endl);
-}
-
-/* the logic:
-   after d_nextclean operations, we clean. We also adjust the cleaninterval
-   a bit so we slowly move it to a value where we clean roughly every 30 seconds.
-
-   If d_nextclean has reached its maximum value, we also test if we were called
-   within 30 seconds, and if so, we skip cleaning. This means that under high load,
-   we will not clean more often than every 30 seconds anyhow.
-*/
-
-void PacketCache::cleanupIfNeeded()
-{
-  if (d_ops++ == d_nextclean) {
-    int timediff = max((int)(time(0) - d_lastclean), 1);
-
-    DLOG(L<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl);
-
-    if (d_cleaninterval == s_maxcleaninterval && timediff < 30) {
-      d_cleanskipped = true;
-      d_nextclean += d_cleaninterval;
-
-      DLOG(L<<"cleaning skipped, timediff: "<<timediff<<endl);
-
-      return;
-    }
-
-    if(!d_cleanskipped) {
-      d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff));
-      d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval);
-      d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval);
-
-      DLOG(L<<"new cleaninterval: "<<d_cleaninterval<<endl);
-    } else {
-      d_cleanskipped = false;
-    }
-
-    d_nextclean += d_cleaninterval;
-    d_lastclean=time(0);
-    cleanup();
-  }
-}
index 357acedd1aa2f2ea0f5dc38f6fc1c0d9bd1e4be1..b0785fd9d96dfbf8c5b9bd585bb8faef5ffc9a2a 100644 (file)
 #ifndef PACKETCACHE_HH
 #define PACKETCACHE_HH
 
-#include <string>
-#include <utility>
-#include <map>
-#include <map>
-#include "dns.hh"
-#include <boost/version.hpp>
-#include "namespaces.hh"
-using namespace ::boost::multi_index;
-
-#include "namespaces.hh"
-#include <boost/multi_index/hashed_index.hpp> 
-#include "dnspacket.hh"
-#include "lock.hh"
-#include "statbag.hh"
-
-/** This class performs 'whole packet caching'. Feed it a question packet and it will
-    try to find an answer. If you have an answer, insert it to have it cached for later use. 
-    Take care not to replace existing cache entries. While this works, it is wasteful. Only
-    insert packets that where not found by get()
-
-    Locking! 
-
-    The cache itself is protected by a read/write lock. Because deleting is a two step process, which 
-    first marks and then sweeps, a second lock is present to prevent simultaneous inserts and deletes.
-*/
+#include "ednsoptions.hh"
+#include "misc.hh"
+#include "iputils.hh"
 
 class PacketCache : public boost::noncopyable
 {
-public:
-  PacketCache();
-  ~PacketCache();
-  enum CacheEntryType { PACKETCACHE, QUERYCACHE};
-
-  void insert(DNSPacket *q, DNSPacket *r, unsigned int maxttl=UINT_MAX);  //!< We copy the contents of *p into our cache. Do not needlessly call this to insert questions already in the cache as it wastes resources
-
-  void insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID=-1,
-    unsigned int maxReplyLen=512, bool dnssecOk=false, bool EDNS=false);
-
-  void insert(const DNSName &qname, const QType& qtype, CacheEntryType cet, const vector<DNSZoneRecord>& content, unsigned int ttl, int zoneID=-1);
-
-  int get(DNSPacket *p, DNSPacket *q); //!< We return a dynamically allocated copy out of our cache. You need to delete it. You also need to spoof in the right ID with the DNSPacket.spoofID() method.
-  bool getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, string& entry, int zoneID=-1,
-    unsigned int maxReplyLen=512, bool dnssecOk=false, bool hasEDNS=false);
-  bool getEntry(const DNSName &qname, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& entry, int zoneID=-1);
-  
-
-  int size() { return *d_statnumentries; } //!< number of entries in the cache
-  void cleanupIfNeeded();
-  void cleanup(); //!< force the cache to preen itself from expired packets
-  int purge();
-  int purge(const std::string& match); // could be $ terminated. Is not a dnsname!
-  int purgeExact(const DNSName& qname); // no wildcard matching here
-
-  map<char,int> getCounts();
-private:
-  bool getEntryLocked(const DNSName &content, const QType& qtype, CacheEntryType cet, string& entry, int zoneID=-1,
-    unsigned int maxReplyLen=512, bool dnssecOk=false, bool hasEDNS=false);
-  bool getEntryLocked(const DNSName &content, const QType& qtype, CacheEntryType cet, vector<DNSZoneRecord>& entry, int zoneID=-1);
-
-
-  struct CacheEntry
+protected:
+  static uint32_t canHashPacket(const std::string& packet, bool skipECS=true)
   {
-    CacheEntry() { qtype = ctype = 0; zoneID = -1; dnssecOk=false; hasEDNS=false; created=0; ttd=0; maxReplyLen=512;}
-
-    DNSName qname;
-    string value;
-    vector<DNSZoneRecord> drs;
-    time_t created;
-    time_t ttd;
-
-    uint16_t qtype;
-    uint16_t ctype;
-    int zoneID;
-    unsigned int maxReplyLen;
-
-    bool dnssecOk;
-    bool hasEDNS;
-  };
-
-  void getTTLS();
-
-  struct UnorderedNameTag{};
-  struct SequenceTag{};
-  typedef multi_index_container<
-    CacheEntry,
-    indexed_by <
-                ordered_unique<
-                      composite_key< 
-                        CacheEntry,
-                        member<CacheEntry,DNSName,&CacheEntry::qname>,
-                        member<CacheEntry,uint16_t,&CacheEntry::qtype>,
-                        member<CacheEntry,uint16_t, &CacheEntry::ctype>,
-                        member<CacheEntry,int, &CacheEntry::zoneID>,
-                        member<CacheEntry,unsigned int, &CacheEntry::maxReplyLen>,
-                        member<CacheEntry,bool, &CacheEntry::dnssecOk>,
-                        member<CacheEntry,bool, &CacheEntry::hasEDNS>
-                        >,
-                      composite_key_compare<CanonDNSNameCompare, std::less<uint16_t>, std::less<uint16_t>, std::less<int>,
-                          std::less<unsigned int>, std::less<bool>, std::less<bool> >
-                       >,
-      hashed_non_unique<tag<UnorderedNameTag>, composite_key<CacheEntry,
-                                                             member<CacheEntry,DNSName,&CacheEntry::qname>,
-                                                             member<CacheEntry,uint16_t,&CacheEntry::qtype>,
-                                                             member<CacheEntry,uint16_t, &CacheEntry::ctype>,
-                                                             member<CacheEntry,int, &CacheEntry::zoneID> > > ,
-      sequenced<tag<SequenceTag>>
-                           >
-  > cmap_t;
-
-
-  struct MapCombo
-  {
-    pthread_rwlock_t d_mut;    
-    cmap_t d_map;
-  };
-
-  vector<MapCombo> d_maps;
-  MapCombo& getMap(const DNSName& qname) 
-  {
-    return d_maps[qname.hash() % d_maps.size()];
+    uint32_t ret = 0;
+    ret=burtle((const unsigned char*)packet.c_str() + 2, 10, ret); // rest of dnsheader, skip id
+    size_t packetSize = packet.size();
+    size_t pos = 12;
+    const char* end = packet.c_str() + packetSize;
+    const char* p = packet.c_str() + pos;
+
+    for(; p < end && *p; ++p) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
+      const unsigned char l = dns_tolower(*p); // label lengths can safely be lower cased
+      ret=burtle(&l, 1, ret);
+    }                           // XXX the embedded 0 in the qname will break the subnet stripping
+
+    struct dnsheader* dh = (struct dnsheader*)packet.c_str();
+    const char* skipBegin = p;
+    const char* skipEnd = p;
+    /* we need at least 1 (final empty label) + 2 (QTYPE) + 2 (QCLASS)
+       + OPT root label (1), type (2), class (2) and ttl (4)
+       + the OPT RR rdlen (2)
+       = 16
+    */
+    if(skipECS && ntohs(dh->arcount)==1 && (pos+16) < packetSize) {
+      char* optionBegin = nullptr;
+      size_t optionLen = 0;
+      /* skip the final empty label (1), the qtype (2), qclass (2) */
+      /* root label (1), type (2), class (2) and ttl (4) */
+      int res = getEDNSOption((char*) p + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen);
+      if (res == 0) {
+        skipBegin = optionBegin;
+        skipEnd = optionBegin + optionLen;
+      }
+    }
+    if (skipBegin > p) {
+      // cerr << "Hashing from " << (p-packet.c_str()) << " for " << skipBegin-p << "bytes, end is at "<< end-packet.c_str() << endl;
+      ret = burtle((const unsigned char*)p, skipBegin-p, ret);
+    }
+    if (skipEnd < end) {
+      // cerr << "Hashing from " << (skipEnd-packet.c_str()) << " for " << end-skipEnd << "bytes, end is at " << end-packet.c_str() << endl;
+      ret = burtle((const unsigned char*) skipEnd, end-skipEnd, ret);
+    }
+
+    return ret;
   }
-
-  AtomicCounter d_ops;
-  time_t d_lastclean; // doesn't need to be atomic
-  unsigned long d_nextclean;
-  unsigned int d_cleaninterval;
-  bool d_cleanskipped;
-  AtomicCounter *d_statnumhit;
-  AtomicCounter *d_statnummiss;
-  AtomicCounter *d_statnumentries;
-
-  int d_ttl;
-
-  static const unsigned int s_mincleaninterval=1000, s_maxcleaninterval=300000;
 };
 
-
-
 #endif /* PACKETCACHE_HH */
index 05f206d4b9be6b9598eb3ec3d98be67c0c8e3266..3a9dc7a2510fa44ffcf76a1738296d2acf72bdff 100644 (file)
@@ -59,6 +59,7 @@ PacketHandler::PacketHandler():B(s_programname), d_dk(&B)
 {
   ++s_count;
   d_doDNAME=::arg().mustDo("dname-processing");
+  d_doExpandALIAS = ::arg().mustDo("expand-alias");
   d_logDNSDetails= ::arg().mustDo("log-dns-details");
   d_doIPv6AdditionalProcessing = ::arg().mustDo("do-ipv6-additional-processing");
   string fname= ::arg()["lua-prequery-script"];
@@ -1355,6 +1356,10 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
         weRedirected=1;
 
       if(DP && rr.dr.d_type == QType::ALIAS && (p->qtype.getCode() == QType::A || p->qtype.getCode() == QType::AAAA || p->qtype.getCode() == QType::ANY)) {
+        if (!d_doExpandALIAS) {
+          L<<Logger::Info<<"ALIAS record found for "<<target<<", but ALIAS expansion is disabled."<<endl;
+          continue;
+        }
         haveAlias=getRR<ALIASRecordContent>(rr.dr)->d_content;
       }
 
@@ -1484,7 +1489,7 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
     
     for(const auto& rr: r->getRRS()) {
       if(rr.scopeMask) {
-        noCache=1;
+        noCache=true;
         break;
       }
     }
@@ -1492,7 +1497,7 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
       addRRSigs(d_dk, B, authSet, r->getRRS());
       
     r->wrapup(); // needed for inserting in cache
-    if(!noCache)
+    if(!noCache && p->couldBeCached())
       PC.insert(p, r, r->getMinTTL()); // in the packet cache
   }
   catch(DBException &e) {
index 6cf5b06f9b866c1f89594e2cf16511375fe632c7..77a4c3788196025e1450b002986bf89bc2072cb2 100644 (file)
@@ -108,6 +108,7 @@ private:
   bool d_logDNSDetails;
   bool d_doIPv6AdditionalProcessing;
   bool d_doDNAME;
+  bool d_doExpandALIAS;
   std::unique_ptr<AuthLua> d_pdl;
   std::unique_ptr<AuthLua4> d_update_policy_lua;
 
index a5d8baf4b0a609e9bfbcc05f40e9fb5e5b507d0c..8db27972925ab477fbb8865ecdbf185a62f92d1f 100644 (file)
@@ -154,6 +154,7 @@ static bool g_lowercaseOutgoing;
 static bool g_weDistributeQueries; // if true, only 1 thread listens on the incoming query sockets
 static bool g_reusePort{false};
 static bool g_useOneSocketPerThread;
+static bool g_gettagNeedsEDNSOptions{false};
 
 std::unordered_set<DNSName> g_delegationOnly;
 RecursorControlChannel s_rcc; // only active in thread 0
@@ -216,7 +217,7 @@ struct DNSComboWriter {
   shared_ptr<TCPConnection> d_tcpConnection;
   vector<pair<uint16_t, string> > d_ednsOpts;
   std::vector<std::string> d_policyTags;
-  std::unordered_map<string,string> d_data;
+  LuaContext::LuaObject d_data;
 };
 
 
@@ -231,6 +232,11 @@ unsigned int getRecursorThreadId()
   return t_id;
 }
 
+int getMTaskerTID()
+{
+  return MT->getTid();
+}
+
 static void handleTCPClientWritable(int fd, FDMultiplexer::funcparam_t& var);
 
 // -1 is error, 0 is timeout, 1 is success
@@ -674,8 +680,8 @@ static void protobufLogResponse(const std::shared_ptr<RemoteLogger>& logger, con
 static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& sr, int& res, vector<DNSRecord>& ret)
 {
   if (spoofed.d_type == QType::CNAME) {
-    bool oldWantsRPZ = sr.d_wantsRPZ;
-    sr.d_wantsRPZ = false;
+    bool oldWantsRPZ = sr.getWantsRPZ();
+    sr.setWantsRPZ(false);
     vector<DNSRecord> ans;
     res = sr.beginResolve(DNSName(spoofed.d_content->getZoneRepresentation()), qtype, 1, ans);
     for (const auto& rec : ans) {
@@ -684,7 +690,7 @@ static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRe
       }
     }
     // Reset the RPZ state of the SyncRes
-    sr.d_wantsRPZ = oldWantsRPZ;
+    sr.setWantsRPZ(oldWantsRPZ);
   }
 }
 
@@ -756,7 +762,7 @@ static void startDoResolve(void *p)
     }
 
     if(g_dnssecmode != DNSSECMode::Off) {
-      sr.d_doDNSSEC=true;
+      sr.setDoDNSSEC(true);
 
       // Does the requestor want DNSSEC records?
       if(edo.d_Z & EDNSOpts::DNSSECOK) {
@@ -768,12 +774,12 @@ static void startDoResolve(void *p)
       pw.getHeader()->cd=0;
     }
 #ifdef HAVE_PROTOBUF
-    sr.d_initialRequestId = dc->d_uuid;
+    sr.setInitialRequestId(dc->d_uuid);
 #endif
     if (g_useIncomingECS) {
-      sr.d_incomingECSFound = dc->d_ecsFound;
+      sr.setIncomingECSFound(dc->d_ecsFound);
       if (dc->d_ecsFound) {
-        sr.d_incomingECS = dc->d_ednssubnet;
+        sr.setIncomingECS(dc->d_ednssubnet);
       }
     }
 
@@ -834,7 +840,7 @@ static void startDoResolve(void *p)
     // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
     if(!t_pdl->get() || !(*t_pdl)->preresolve(dq, res)) {
 
-      sr.d_wantsRPZ = wantsRPZ;
+      sr.setWantsRPZ(wantsRPZ);
       if(wantsRPZ) {
         switch(appliedPolicy.d_kind) {
           case DNSFilterEngine::PolicyKind::NoAction:
@@ -1220,7 +1226,7 @@ static void startDoResolve(void *p)
     }
 
     sr.d_outqueries ? t_RC->cacheMisses++ : t_RC->cacheHits++;
-    float spent=makeFloat(sr.d_now-dc->d_now);
+    float spent=makeFloat(sr.getNow()-dc->d_now);
     if(spent < 0.001)
       g_stats.answers0_1++;
     else if(spent < 0.010)
@@ -1299,7 +1305,7 @@ static void makeControlChannelSocket(int processNum=-1)
   }
 }
 
-static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet)
+static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options)
 {
   bool found = false;
   const struct dnsheader* dh = (struct dnsheader*)question.c_str();
@@ -1313,14 +1319,29 @@ static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uin
   if(ntohs(dh->arcount) == 1 && questionLen > pos + 11) { // this code can extract one (1) EDNS Subnet option
     /* OPT root label (1) followed by type (2) */
     if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT) {
-      char* ecsStart = nullptr;
-      size_t ecsLen = 0;
-      int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
-      if (res == 0 && ecsLen > 4) {
-        EDNSSubnetOpts eso;
-        if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
-          *ednssubnet=eso;
-          found = true;
+      if (!options) {
+        char* ecsStart = nullptr;
+        size_t ecsLen = 0;
+        int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+        if (res == 0 && ecsLen > 4) {
+          EDNSSubnetOpts eso;
+          if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
+            *ednssubnet=eso;
+            found = true;
+          }
+        }
+      }
+      else {
+        int res = getEDNSOptions((char*)question.c_str()+pos+9, questionLen - pos - 9, *options);
+        if (res == 0) {
+          const auto& it = options->find(EDNSOptionCode::ECS);
+          if (it != options->end() && it->second.content != nullptr && it->second.size > 0) {
+            EDNSSubnetOpts eso;
+            if(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso)) {
+              *ednssubnet=eso;
+              found = true;
+            }
+          }
         }
       }
     }
@@ -1405,12 +1426,13 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
 
         try {
+          std::map<uint16_t, EDNSOptionView> ednsOptions;
           dc->d_ecsParsed = true;
-          dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet);
+          dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
 
           if(t_pdl->get() && (*t_pdl)->d_gettag) {
             try {
-              dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data);
+              dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions);
             }
             catch(std::exception& e)  {
               if(g_logCommonErrors)
@@ -1542,7 +1564,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   uint32_t qhash = 0;
   bool needECS = false;
   std::vector<std::string> policyTags;
-  std::unordered_map<string,string> data;
+  LuaContext::LuaObject data;
 #ifdef HAVE_PROTOBUF
   boost::uuids::uuid uniqueId;
   auto luaconfsLocal = g_luaconfs.getLocal();
@@ -1576,13 +1598,14 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 
     if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
       try {
-        ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet);
+        std::map<uint16_t, EDNSOptionView> ednsOptions;
+        ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
         qnameParsed = true;
         ecsParsed = true;
 
         if(t_pdl->get() && (*t_pdl)->d_gettag) {
           try {
-            ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data);
+            ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data, ednsOptions);
           }
           catch(std::exception& e)  {
             if(g_logCommonErrors)
@@ -1846,6 +1869,17 @@ static void makeTCPServerSockets(unsigned int threadId)
     }
 #endif
 
+    if (::arg().asNum("tcp-fast-open") > 0) {
+#ifdef TCP_FASTOPEN
+      int fastOpenQueueSize = ::arg().asNum("tcp-fast-open");
+      if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &fastOpenQueueSize, sizeof fastOpenQueueSize) < 0) {
+        L<<Logger::Error<<"Failed to enable TCP Fast Open for listening socket: "<<strerror(errno)<<endl;
+      }
+#else
+      L<<Logger::Warning<<"TCP Fast Open configured but not supported for listening socket"<<endl;
+#endif
+    }
+
     sin.sin4.sin_port = htons(st.port);
     socklen_t socklen=sin.sin4.sin_family==AF_INET ? sizeof(sin.sin4) : sizeof(sin.sin6);
     if (::bind(fd, (struct sockaddr *)&sin, socklen )<0)
@@ -2030,7 +2064,7 @@ static void houseKeeping(void *)
       t_RC->doPrune(); // this function is local to a thread, so fine anyhow
       t_packetCache->doPruneTo(::arg().asNum("max-packetcache-entries") / g_numWorkerThreads);
 
-      pruneCollection(t_sstorage->negcache, ::arg().asNum("max-cache-entries") / (g_numWorkerThreads * 10), 200);
+      t_sstorage->negcache.prune(::arg().asNum("max-cache-entries") / (g_numWorkerThreads * 10));
 
       if(!((cleanCounter++)%40)) {  // this is a full scan!
        time_t limit=now.tv_sec-300;
@@ -2044,7 +2078,7 @@ static void houseKeeping(void *)
     }
 
     if(now.tv_sec - last_rootupdate > 7200) {
-      int res = getRootNS();
+      int res = SyncRes::getRootNS(g_now, nullptr);
       if (!res)
         last_rootupdate=now.tv_sec;
     }
@@ -2776,6 +2810,9 @@ static int serviceMain(int argc, char*argv[])
     SyncRes::s_serverID=tmp;
   }
 
+  SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
+  SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
+
   g_networkTimeoutMsec = ::arg().asNum("network-timeout");
 
   g_initialDomainMap = parseAuthAndForwards();
@@ -2793,6 +2830,8 @@ static int serviceMain(int argc, char*argv[])
   g_numThreads = g_numWorkerThreads + g_weDistributeQueries;
   g_maxMThreads = ::arg().asNum("max-mthreads");
 
+  g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+
 #ifdef SO_REUSEPORT
   g_reusePort = ::arg().mustDo("reuseport");
 #endif
@@ -2982,14 +3021,14 @@ try
 
   if(g_useOneSocketPerThread) {
     for (unsigned int threadId = 0; threadId < g_numWorkerThreads; threadId++) {
-      for(deferredAdd_t::const_iterator i = deferredAdds[threadId].begin(); i != deferredAdds[threadId].end(); ++i) {
+      for(deferredAdd_t::const_iterator i = deferredAdds[threadId].cbegin(); i != deferredAdds[threadId].cend(); ++i) {
         t_fdm->addReadFD(i->first, i->second);
       }
     }
   }
   else {
     if(!g_weDistributeQueries || !t_id) { // if we distribute queries, only t_id = 0 listens
-      for(deferredAdd_t::const_iterator i = deferredAdds[0].begin(); i != deferredAdds[0].end(); ++i) {
+      for(deferredAdd_t::const_iterator i = deferredAdds[0].cbegin(); i != deferredAdds[0].cend(); ++i) {
         t_fdm->addReadFD(i->first, i->second);
       }
     }
@@ -3173,6 +3212,7 @@ int main(int argc, char **argv)
     ::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes";
     ::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
     ::arg().setSwitch( "lowercase-outgoing","Force outgoing questions to lowercase")="no";
+    ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook")="no";
     ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1680";
     ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1680";
     ::arg().set("minimum-ttl-override", "Set under adverse conditions, a minimum TTL")="0";
@@ -3188,6 +3228,8 @@ int main(int argc, char **argv)
     ::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent")="no";
     ::arg().setSwitch("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP master")="";
 
+    ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
+
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print version string");
     ::arg().setCmd("config","Output blank configuration");
@@ -3266,43 +3308,3 @@ int main(int argc, char **argv)
 
   return ret;
 }
-
-int getRootNS(void) {
-  SyncRes sr(g_now);
-  sr.setDoEDNS0(true);
-  sr.setNoCache();
-  sr.d_doDNSSEC = (g_dnssecmode != DNSSECMode::Off);
-
-  vector<DNSRecord> ret;
-  int res=-1;
-  try {
-    res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
-    if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
-      ResolveContext ctx;
-      auto state = validateRecords(ctx, ret);
-      if (state == Bogus)
-        throw PDNSException("Got Bogus validation result for .|NS");
-    }
-    return res;
-  }
-  catch(PDNSException& e)
-  {
-    L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
-  }
-
-  catch(std::exception& e)
-  {
-    L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
-  }
-
-  catch(...)
-  {
-    L<<Logger::Error<<"Failed to update . records, got an exception"<<endl;
-  }
-  if(!res) {
-    L<<Logger::Notice<<"Refreshed . records"<<endl;
-  }
-  else
-    L<<Logger::Error<<"Failed to update . records, RCODE="<<res<<endl;
-  return res;
-}
index a623d195ead6752e438298470bf1a3ff819ab4a1..a399c23b6bdb5a5f3057c69c67ab3b9c1515e8e0 100644 (file)
@@ -13,7 +13,8 @@
 #include "dnsbackend.hh"
 #include "ueberbackend.hh"
 #include "arguments.hh"
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
 #include "zoneparser-tng.hh"
 #include "signingpipe.hh"
 #include "dns_random.hh"
@@ -29,7 +30,8 @@
 #endif
 
 StatBag S;
-PacketCache PC;
+AuthPacketCache PC;
+AuthQueryCache QC;
 
 namespace po = boost::program_options;
 po::variables_map g_vm;
@@ -101,12 +103,7 @@ void loadMainConfig(const std::string& configdir)
   //cerr<<"Backend: "<<::arg()["launch"]<<", '" << ::arg()["gmysql-dbname"] <<"'" <<endl;
 
   S.declare("qsize-q","Number of questions waiting for database attention");
-    
-  S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
-  S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
           
-  S.declare("query-cache-hit","Number of hits on the query cache");
-  S.declare("query-cache-miss","Number of misses on the query cache");
   ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
   ::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20";              
   ::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60";
@@ -1388,10 +1385,13 @@ int listAllZones(const string &type="") {
     }
   }
 
-  if (kindFilter != -1)
-    cout<<type<<" zonecount: "<<count<<endl;
-  else
-    cout<<"All zonecount: "<<count<<endl;
+  if (g_verbose) {
+    if (kindFilter != -1)
+      cout<<type<<" zonecount: "<<count<<endl;
+    else
+      cout<<"All zonecount: "<<count<<endl;
+  }
+
   return 0;
 }
 
@@ -2090,7 +2090,7 @@ try
     return 0;
   }
 
-loadMainConfig(g_vm["config-dir"].as<string>());
+  loadMainConfig(g_vm["config-dir"].as<string>());
 
 #ifdef HAVE_LIBSODIUM
   if (sodium_init() == -1) {
index fa40d1d03e85870ffaf3d3669d871faae767820c..42fb89f54ad411c6cab842e6cca29558fcd2f2a5 100644 (file)
@@ -11,6 +11,7 @@
 #include "misc.hh"
 #include "recursor_cache.hh"
 #include "syncres.hh"
+#include "negcache.hh"
 #include <boost/function.hpp>
 #include <boost/optional.hpp>
 #include <boost/tuple/tuple.hpp>
@@ -175,26 +176,17 @@ string doGetParameter(T begin, T end)
 }
 
 
-static uint64_t dumpNegCache(SyncRes::negcache_t& negcache, int fd)
+static uint64_t dumpNegCache(NegCache& negcache, int fd)
 {
   FILE* fp=fdopen(dup(fd), "w");
   if(!fp) { // dup probably failed
     return 0;
   }
+  uint64_t ret;
   fprintf(fp, "; negcache dump from thread follows\n;\n");
-  time_t now = time(0);
-  
-  typedef SyncRes::negcache_t::nth_index<1>::type sequence_t;
-  sequence_t& sidx=negcache.get<1>();
-
-  uint64_t count=0;
-  for(const NegCacheEntry& neg :  sidx)
-  {
-    ++count;
-    fprintf(fp, "%s IN %s %d VIA %s\n", neg.d_name.toString().c_str(), neg.d_qtype.getName().c_str(), (unsigned int) (neg.d_ttd - now), neg.d_qname.toString().c_str());
-  }
+  ret = negcache.dumpToFile(fp);
   fclose(fp);
-  return count;
+  return ret;
 }
 
 static uint64_t* pleaseDump(int fd)
@@ -282,24 +274,11 @@ uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree)
 
 uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree)
 {
-  if(!subtree) {
-    uint64_t res = t_sstorage->negcache.count(tie(canon));
-    auto range=t_sstorage->negcache.equal_range(tie(canon));
-    t_sstorage->negcache.erase(range.first, range.second);
-    return new uint64_t(res);
-  }
-  else {
-    unsigned int erased=0;
-    for(auto iter = t_sstorage->negcache.lower_bound(tie(canon)); iter != t_sstorage->negcache.end(); ) {
-      if(!iter->d_qname.isPartOf(canon))
-       break;
-      t_sstorage->negcache.erase(iter++);
-      erased++;
-    }
-    return new uint64_t(erased);
-  }
+  uint64_t ret = t_sstorage->negcache.wipe(canon, subtree);
+  return new uint64_t(ret);
 }
 
+
 template<typename T>
 string doWipeCache(T begin, T end)
 {
index 22c6cb8272feaf7bdb6ad07b179fadbdf6bb9b5c..6186caad14c1ecd3e5ef4dcbe9c156efbc63e560 100644 (file)
@@ -49,49 +49,6 @@ static bool qrMatch(const DNSName& qname, uint16_t qtype, uint16_t qclass, const
   return qname==rname && rtype == qtype && rclass == qclass;
 }
 
-uint32_t RecursorPacketCache::canHashPacket(const std::string& origPacket)
-{
-  //  return 42; // should still work if you do this!
-  uint32_t ret=0;
-  ret=burtle((const unsigned char*)origPacket.c_str() + 2, 10, ret); // rest of dnsheader, skip id
-  const char* end = origPacket.c_str() + origPacket.size();
-  const char* p = origPacket.c_str() + 12;
-
-  for(; p < end && *p; ++p) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
-    const unsigned char l = dns_tolower(*p); // label lengths can safely be lower cased
-    ret=burtle(&l, 1, ret);
-  }                           // XXX the embedded 0 in the qname will break the subnet stripping
-
-  struct dnsheader* dh = (struct dnsheader*)origPacket.c_str();
-  const char* skipBegin = p;
-  const char* skipEnd = p;
-  /* we need at least 1 (final empty label) + 2 (QTYPE) + 2 (QCLASS)
-     + OPT root label (1), type (2), class (2) and ttl (4)
-     + the OPT RR rdlen (2)
-     = 16
-  */
-  if(ntohs(dh->arcount)==1 && (p+16) < end) {
-    char* optionBegin = nullptr;
-    size_t optionLen = 0;
-    /* skip the final empty label (1), the qtype (2), qclass (2) */
-    /* root label (1), type (2), class (2) and ttl (4) */
-    int res = getEDNSOption((char*) p + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen);
-    if (res == 0) {
-      skipBegin = optionBegin;
-      skipEnd = optionBegin + optionLen;
-    }
-  }
-  if (skipBegin > p) {
-    //cout << "Hashing from " << (p-origPacket.c_str()) << " for " << skipBegin-p << "bytes, end is at "<< end-origPacket.c_str() << endl;
-    ret = burtle((const unsigned char*)p, skipBegin-p, ret);
-  }
-  if (skipEnd < end) {
-    //cout << "Hashing from " << (skipEnd-origPacket.c_str()) << " for " << end-skipEnd << "bytes, end is at " << end-origPacket.c_str() << endl;
-    ret = burtle((const unsigned char*) skipEnd, end-skipEnd, ret);
-  }
-  return ret;
-}
-
 bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<HashTag>::type::iterator, packetCache_t::index<HashTag>::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, RecProtoBufMessage* protobufMessage)
 {
   for(auto iter = range.first ; iter != range.second ; ++ iter) {
@@ -148,7 +105,7 @@ bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string&
 bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now,
                                             std::string* responsePacket, uint32_t* age, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
 {
-  *qhash = canHashPacket(queryPacket);
+  *qhash = canHashPacket(queryPacket, true);
   const auto& idx = d_packetCache.get<HashTag>();
   auto range = idx.equal_range(tie(tag,*qhash));
 
@@ -163,7 +120,7 @@ bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string&
 bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now,
                                             std::string* responsePacket, uint32_t* age, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
 {
-  *qhash = canHashPacket(queryPacket);
+  *qhash = canHashPacket(queryPacket, true);
   const auto& idx = d_packetCache.get<HashTag>();
   auto range = idx.equal_range(tie(tag,*qhash));
 
@@ -191,12 +148,9 @@ void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash,
   auto iter = range.first;
 
   for( ; iter != range.second ; ++iter)  {
-    if(iter->d_type != qtype || iter->d_class != qclass)
-      continue;
-    // this only happens on insert which is relatively rare and does not need to be super fast
-    DNSName respname(iter->d_packet.c_str(), iter->d_packet.length(), sizeof(dnsheader), false, 0, 0, 0);
-    if(qname != respname)
+    if(iter->d_type != qtype || iter->d_class != qclass || iter->d_name != qname)
       continue;
+
     moveCacheItemToBack(d_packetCache, iter);
     iter->d_packet = responsePacket;
     iter->d_ttd = now + ttl;
index ca8c5d38fa2e2689d002923aed458234ef4067e0..51f4d642aab0a2761cf66a4e9bc69c6d522e7f24 100644 (file)
@@ -33,6 +33,8 @@
 #include <boost/tuple/tuple_comparison.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
 
+#include "packetcache.hh"
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -46,7 +48,7 @@ using namespace ::boost::multi_index;
    you can use a query as a key too. But query and answer must compare as identical! 
    
    This precludes doing anything smart with EDNS directly from the packet */
-class RecursorPacketCache
+class RecursorPacketCache: public PacketCache
 {
 public:
   RecursorPacketCache();
@@ -88,7 +90,7 @@ private:
       return d_ttd;
     }
   };
-  uint32_t canHashPacket(const std::string& origPacket);
+
   typedef multi_index_container<
     Entry,
     indexed_by  <
index 35bf6defd5885a38b7b6c6e4982c3cd16b1a274f..f02a4a189f05fb05cfd25ec9e4861aaed1a37003 100644 (file)
@@ -41,3 +41,6 @@
 /pdns-recursor.service
 /pdns-recursor@.service
 /lua.hpp
+/test-suite.log
+/testrunner.log
+/testrunner.trs
index fec6d98efc792a19ff63bf1d4afff0f1e0f88133..03694b1cb5da642a6c1cdddc1e0a91b573d64736 100644 (file)
@@ -76,6 +76,7 @@ endif
 
 pdns_recursor_SOURCES = \
        arguments.cc \
+       ascii.hh \
        base32.cc base32.hh \
        base64.cc base64.hh \
        cachecleaner.hh \
@@ -110,8 +111,10 @@ pdns_recursor_SOURCES = \
        mtasker.hh \
        mtasker_context.cc mtasker_context.hh \
        namespaces.hh \
+       negcache.hh negcache.cc \
        nsecrecords.cc \
        opensslsigners.cc opensslsigners.hh \
+       packetcache.hh \
        pdns_recursor.cc \
        pdnsexception.hh \
        protobuf.cc protobuf.hh \
@@ -178,32 +181,40 @@ testrunner_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
        dns.cc dns.hh \
+       dns_random.cc dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.hh dnsparser.cc \
-       dns_random.cc dns_random.hh \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       ednscookies.cc ednscookies.hh \
+       ecs.cc \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
+       filterpo.cc filterpo.hh \
        gettime.cc gettime.hh \
        gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
        logger.cc logger.hh \
        misc.cc misc.hh \
+       negcache.hh negcache.cc \
        namespaces.hh \
        nsecrecords.cc \
        pdnsexception.hh \
        protobuf.cc protobuf.hh \
        qtype.cc qtype.hh \
+       randomhelper.cc \
        rcpgenerator.cc \
-       recpacketcache.cc recpacketcache.hh \
        rec-protobuf.cc rec-protobuf.hh \
+       recpacketcache.cc recpacketcache.hh \
+       recursor_cache.cc recursor_cache.hh \
        responsestats.cc \
+       root-dnssec.hh \
        sillyrecords.cc \
        sholder.hh \
        sstuff.hh \
+       syncres.cc syncres.hh \
        test-arguments_cc.cc \
        test-base32_cc.cc \
        test-base64_cc.cc \
@@ -212,14 +223,19 @@ testrunner_SOURCES = \
        test-dnsname_cc.cc \
        test-dnsparser_hh.cc \
        test-dnsrecords_cc.cc \
+       test-ednsoptions_cc.cc \
        test-iputils_hh.cc \
        test-misc_hh.cc \
        test-nmtree.cc \
+       test-negcache_cc.cc \
        test-rcpgenerator_cc.cc \
        test-recpacketcache_cc.cc \
+       test-syncres_cc.cc \
        test-tsig.cc \
        testrunner.cc \
        unix_utility.cc \
+       validate.cc validate.hh \
+       validate-recursor.cc validate-recursor.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
 testrunner_LDFLAGS = \
diff --git a/pdns/recursordist/ascii.hh b/pdns/recursordist/ascii.hh
new file mode 120000 (symlink)
index 0000000..6d5e3eb
--- /dev/null
@@ -0,0 +1 @@
+../ascii.hh
\ No newline at end of file
index fa9fbd8a31ddab95ce02a04e3584a603012d310e..6f411e4a4e381d2b1b55d76d8ee01d58b41f6c82 100644 (file)
@@ -208,6 +208,10 @@ AS_IF([test "x$LUAPC" != "x"],
     [AC_MSG_NOTICE([Lua/LuaJit: no])])
 ])
 AC_MSG_NOTICE([OpenSSL ECDSA: $libcrypto_ecdsa])
+AS_IF([test "x$LIBSODIUM_LIBS" != "x"],
+  [AC_MSG_NOTICE([libsodium ed25519: yes])],
+  [AC_MSG_NOTICE([libsodium ed25519: no])]
+)
 AS_IF([test "x$PROTOBUF_LIBS" != "x" -a x"$PROTOC" != "x"],
   [AC_MSG_NOTICE([Protobuf: yes])],
   [AC_MSG_NOTICE([Protobuf: no])]
diff --git a/pdns/recursordist/contrib/syncres.dot b/pdns/recursordist/contrib/syncres.dot
new file mode 100644 (file)
index 0000000..b931eea
--- /dev/null
@@ -0,0 +1,368 @@
+digraph {
+  graph [fontname = "monospace"];
+  node [fontname = "monospace"];
+  edge [fontname = "monospace"];
+
+  subgraph cluster_beginResolve {
+    label="SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret)\nreturns the RCODE\nret is filled with all relevant records";
+
+    beginResolve_doResolve [label="SyncRes::doResolve()", color=red];
+    beginResolve_doSpecialNamesResolve [label="SyncRes::doSpecialNamesResolve()", color=red]
+
+    "Is this an AXFR request?";
+    "Is this an AXFR request?" -> beginResolve_return_minus_1 [label=yes];
+    "Is this an AXFR request?" -> beginResolve_doSpecialNamesResolve [label=no];
+
+    beginResolve_doSpecialNamesResolve -> "Is the qlass IN?" [label="Was not a special name"];
+    beginResolve_doSpecialNamesResolve -> beginResolve_return_0 [label="Was handled!"];
+
+    "Is the qlass IN?" -> beginResolve_return_minus_1 [label=no];
+    "Is the qlass IN?" -> beginResolve_doResolve [label=yes];
+    beginResolve_doResolve -> beginResolve_return_doResolve;
+    beginResolve_return_doResolve [label="return result from doResolve", color=green];
+    beginResolve_return_0 [label="return 0", color=green];
+    beginResolve_return_minus_1 [label="return -1", color=green];
+  }
+
+  subgraph cluster_doResolve {
+    label="SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere)";
+
+    doResolve_doOOBResolve [label="SyncRes::doOOBResolve()", color=red];
+    doResolve_doCNAMECacheCheck [label="SyncRes::doCNAMECacheCheck()", color=red];
+    doResolve_asyncresolveWrapper [label="SyncRes::asyncresolveWrapper()", color=red];
+    doResolve_doCacheCheck [label="SyncRes::doCacheCheck()", color=red];
+    doResolve_getBestNSNamesFromCache [label="SyncRes::getBestNSNamesFromCache()", color=red];
+    doResolve_doResolveAt [label="SyncRes::doResolveAt()", color=red];
+
+    doResolve_return_res [label="return res", color=green];
+    doResolve_return_servfail [label="return SERVFAIL", color=green];
+
+    "SERVFAIL if too deep" -> "Check if called from getRootNS()";
+    "Check if called from getRootNS()" -> "Check if RD-bit was not set (d_cacheonly)" [label=yes];
+    "Check if called from getRootNS()" -> doResolve_getBestNSNamesFromCache [label=no];
+    "Check if RD-bit was not set (d_cacheonly)" -> doResolve_doCNAMECacheCheck [label=no];
+    "Check if RD-bit was not set (d_cacheonly)" -> "Check if there is a forward or auth-zone" [label=yes];
+    "Check if there is a forward or auth-zone" -> doResolve_doCNAMECacheCheck [label=no];
+    "Check if there is a forward or auth-zone" -> "Check if we are auth" [label=yes];
+    "Check if we are auth" -> doResolve_asyncresolveWrapper [label="no, so forward"];
+    "Check if we are auth" -> doResolve_doOOBResolve [label=yes];
+    doResolve_doOOBResolve -> "return res from doOOBResolve()";
+    "return res from doOOBResolve()" [color=green];
+    doResolve_asyncresolveWrapper -> "return result from asyncresolveWrapper()";
+    "return result from asyncresolveWrapper()" [color=green];
+
+    doResolve_doCNAMECacheCheck -> doResolve_doCacheCheck [label="returned false"];
+    doResolve_doCNAMECacheCheck -> doResolve_return_res [label="returned true"];
+
+    doResolve_doCacheCheck -> doResolve_getBestNSNamesFromCache [label="returned false"];
+    doResolve_doCacheCheck -> doResolve_return_res [label="returned true"];
+
+    doResolve_getBestNSNamesFromCache -> doResolve_doResolveAt;
+    doResolve_doResolveAt -> doResolve_return_res [label="res == -2"];
+    doResolve_doResolveAt -> doResolve_return_servfail [label="res < 0 &&\nres != -2"];
+    doResolve_doResolveAt -> doResolve_return_res [label="res >= 0"];
+  }
+
+  subgraph cluster_doCacheCheck {
+    label="SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res)";
+
+    doCacheCheck_return_false [label="return false", color=green];
+    "Did we have a positive or negative answer?" -> doCacheCheck_return_true [label=yes];
+    "Did we have a positive or negative answer?" -> doCacheCheck_return_false [label=no];
+    doCacheCheck_return_true [label="return true", color=green];
+
+    "Allocate return qname, qtype and qttl vars (RVARS) as qname, qtype" -> "Is the last label of qname negatively cached by root and is root-nx-trust enabled?";
+
+    "Is the last label of qname negatively cached by root and is root-nx-trust enabled?" -> "Set res to NXDOMAIN, set RVARS to to this label|SOA" [label=yes];
+    "Is the last label of qname negatively cached by root and is root-nx-trust enabled?" -> "Do we have a negative entry from an auth?" [label=no];
+    "Do we have a negative entry from an auth?" -> "Set RCODE (NOERROR or NXDOMAIN), set RVARS to (smaller) qname|SOA" [label=yes];
+    "Set RCODE (NOERROR or NXDOMAIN), set RVARS to (smaller) qname|SOA" -> "Add DNSSEC proof from cache";
+    "Set res to NXDOMAIN, set RVARS to to this label|SOA" -> "Do we have a positive for RVARS from an auth?";
+
+    "Do we have a negative entry from an auth?" -> "Do we have a positive for RVARS from an auth?" [label=no];
+
+    "Do we have a positive for RVARS from an auth?" -> "Did we have a positive or negative answer?" [label=no];
+    "Add DNSSEC proof from cache" -> "Do we have a positive for RVARS from an auth?";
+
+    "Do we have a positive for RVARS from an auth?" -> "Add positive answers to ret if not expired" [label=yes];
+    "Add positive answers to ret if not expired" -> "Add DNSSEC data to ret";
+    "Add DNSSEC data to ret" -> "Did we have a positive or negative answer?";
+  }
+
+  subgraph cluster_getBestNSFromCache {
+    label="SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere)\nFills the bestns vector with the 'closest' nameservers for qname\nflawedNSSet will be true if the NSSet has no glue.\nbeenthere contains the list of nameservers already visited during this recursion.";
+
+    getBestNSFromCache_return [label="return", color=green];
+
+    getBestNSFromCache_chopoff_continue -> "Get NS-records for domain from cache" -> "Get one record from the records" -> "Has the TTL expired?";
+    "Get one record from the records" -> "Are there records in bestns?" [label="Checked all records"];
+
+    "Has the TTL expired?" -> "Get one record from the records" [label=yes];
+    "Has the TTL expired?" -> "Is the NS RDATA part of the domain &&\nDo we have A and/or AAAA records for it?" [label=no];
+    "Is the NS RDATA part of the domain &&\nDo we have A and/or AAAA records for it?" -> "Set flawednsset=true" [label=no];
+
+    "Is the NS RDATA part of the domain &&\nDo we have A and/or AAAA records for it?" -> "Add the NS-record to bestns" [label=yes];
+    "Add the NS-record to bestns" -> "Get one record from the records";
+
+    "Set flawednsset=true" -> "Get one record from the records";
+
+    "Are there records in bestns?" -> getBestNSFromCache_chopoff_continue [label=no];
+    "Are there records in bestns?" -> "Is any of the NS records in bestns in beenthere?" [label=yes];
+
+
+    "Is any of the NS records in bestns in beenthere?" -> "Add these records to beenthere" [label=no];
+    "Add these records to beenthere" -> getBestNSFromCache_return;
+
+    "Is any of the NS records in bestns in beenthere?" -> "Clear bestns" [label=yes];
+    "Clear bestns" -> "Was this the root domain?";
+    "Was this the root domain?" -> getBestNSFromCache_chopoff_continue [label=no];
+    "Was this the root domain?" -> "Re-prime the root" [label=yes];
+    "Re-prime the root" -> getBestNSFromCache_return;
+    getBestNSFromCache_chopoff_continue [label="chopoff left-most label"];
+
+    {rank=sink; getBestNSFromCache_chopoff_continue; getBestNSFromCache_return}
+  }
+
+  subgraph cluster_doOOBResolve {
+    label="SyncRes::doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int& res)\nReturns true if data came from local auth-store.\nvector<DNSRecord> ret is filled with answers";
+
+    doOOBResolve_getBestAuthZone [label="SyncRes::getBestAuthZone()", color=red];
+    doOOBResolve_return_false [label="return false", color=green];
+    doOOBResolve_return_true [label="return true", color=green];
+
+    doOOBResolve_getBestAuthZone -> doOOBResolve_return_false [label="returned iterator to end of authstorage"];
+    doOOBResolve_getBestAuthZone -> "Add auth-records matching qname+qtype || CNAME || NS to ret" [label="returned iterator in the authstorage"];
+    "Add auth-records matching qname+qtype || CNAME || NS to ret" -> doOOBResolve_return_true [label="records were added to ret"]
+    "Add auth-records matching qname+qtype || CNAME || NS to ret" -> "Did we have any data for the qname?" [label="no records were added to ret"];
+
+    "Did we have any data for the qname?" -> "Add SOA to AUTHORITY in ret" [label="yes (empty NOERROR)"];
+    "Add SOA to AUTHORITY in ret" -> "Set res to NOERROR" -> doOOBResolve_return_true;
+
+    "Did we have any data for the qname?" -> "Is there a wildcard match?" [label=no];
+    "Is there a wildcard match?" -> "Add auth-records from wildcard to ret" [label=yes];
+    "Add auth-records from wildcard to ret" -> "Set res to NOERROR";
+
+    "Is there a wildcard match?" -> "Add NS-records from auth-zone" [label=no];
+
+    "Add NS-records from auth-zone" -> "Set res to NOERROR" [label="NS record were added"];
+    "Add NS-records from auth-zone" -> "Try to add SOA" [label="No NS record were added"];
+
+    "Try to add SOA" -> "Set res to NXDOMAIN" -> doOOBResolve_return_true;
+  }
+
+  subgraph cluster_asyncresolveWrapper {
+    label="SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res\nWraps asyncresolve() from lwres.cc to do EDNS probing.";
+
+    {rank=min; "Get current EDNSStatus for ip"}
+
+    asyncresolveWrapper_asyncresolve [label="asyncresolve()", color=red];
+    asyncresolveWrapper_return_minus_1 [label="return -1 (transport error)", color=green];
+    asyncresolveWrapper_return_minus_2 [label="return -2 (OS limits error)", color=green];
+    asyncresolveWrapper_return_0 [label="return 0 (timeout)", color=green];
+    asyncresolveWrapper_return_1 [label="return 1 (success)", color=green];
+    asyncresolveWrapper_setEDNSLevel_0 [label="Set EDNSLevel=0"]
+    asyncresolveWrapper_setEDNSLevel_0 -> "Is EDNSStatus NOEDNS?";
+
+    "Get current EDNSStatus for ip" -> "Is the current EDNSStatus older than an hour?";
+    "Is the current EDNSStatus older than an hour?" -> "Set EDNSStatus to Unknown" [label=yes];
+    "Set EDNSStatus to Unknown" -> asyncresolveWrapper_setEDNSLevel_0;
+    "Is the current EDNSStatus older than an hour?" -> asyncresolveWrapper_setEDNSLevel_0 [label=no];
+
+    "Is EDNSStatus NOEDNS?" -> "Set EDNSLevel=0" [label=yes]
+    "Set EDNSLevel=0" -> asyncresolveWrapper_asyncresolve;
+
+    "Is EDNSStatus NOEDNS?" -> "Is EDNSStatus UNKNOWN, EDNSOK, EDSIGNORANT or is EDNS mandatory?" [label=no]
+    "Is EDNSStatus UNKNOWN, EDNSOK, EDSIGNORANT or is EDNS mandatory?" -> "Set EDNSLevel=1" [label=yes]
+    "Set EDNSLevel=1" -> asyncresolveWrapper_asyncresolve;
+    "Is EDNSStatus UNKNOWN, EDNSOK, EDSIGNORANT or is EDNS mandatory?" ->  asyncresolveWrapper_asyncresolve [label=no];
+
+    asyncresolveWrapper_asyncresolve -> asyncresolveWrapper_return_minus_1 [label="transport error"];
+    asyncresolveWrapper_asyncresolve -> asyncresolveWrapper_return_minus_2 [label="OS limits error"];
+    asyncresolveWrapper_asyncresolve -> asyncresolveWrapper_return_0 [label="timeout error"];
+    asyncresolveWrapper_asyncresolve -> "Is the EDNSStatus UNKNOWN||EDNSOK||EDNSIGNORANT?" [label="resolve OK!"];
+
+    "Is the EDNSStatus UNKNOWN||EDNSOK||EDNSIGNORANT?" -> "Was the RCODE FORMERR||NOTIMP?" [label=yes];
+    "Was the RCODE FORMERR||NOTIMP?" -> "set EDNSStatus to NOEDNS" [label=yes];
+    "set EDNSStatus to NOEDNS" -> "Is EDNSStatus NOEDNS?";
+
+    "Was the RCODE FORMERR||NOTIMP?" -> "Did the remote server respond with EDNS?" [label=no];
+    "Did the remote server respond with EDNS?" -> "Set EDNSStatus to EDNSOK" [label=yes];
+    "Set EDNSStatus to EDNSOK" -> "Is the original EDNSStatus different from the new?";
+
+    "Did the remote server respond with EDNS?" -> "Set EDNSStatus to EDNSIGNORANT" [label=no];
+    "Set EDNSStatus to EDNSIGNORANT" -> "Is the original EDNSStatus different from the new?";
+
+    "Is the EDNSStatus UNKNOWN||EDNSOK||EDNSIGNORANT?" -> "Is the original EDNSStatus different from the new?" [label=no];
+    "Is the original EDNSStatus different from the new?" -> "Save new EDNSStatus" [label=yes];
+    "Is the original EDNSStatus different from the new?" -> asyncresolveWrapper_return_1 [label=no];
+    "Save new EDNSStatus" -> asyncresolveWrapper_return_1;
+  }
+
+  subgraph cluster_doResolveAt {
+    label="SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>&beenthere)\nreturns -1 in case of no results, -2 when a FilterEngine Policy was hit, rcode otherwise";
+
+    doResolveAt_nameServersBlockedByRPZ [label="SyncRes::nameserversBlockedByRPZ()", color=red];
+    doResolveAt_doOOBResolve_for_NS [label="SyncRes::doOOBResolve()", color=red];
+    doResolveAt_retrieveAddressesForNS [label="SyncRes::retrieveAddressesForNS()", color=red];
+    doResolveAt_nameserverIPBlockedByRPZ [label="SyncRes::nameserverIPBlockedByRPZ()", color=red];
+    doResolveAt_Lua_preoutquery [label="Lua preoutquery", color=red];
+    doResolveAt_asyncresolveWrapper [label="SyncRes::asyncresolveWrapper()", color=red];
+    doResolveAt_processRecords [label="SyncRes::processRecords()", color=red];
+    doResolveAt_doResolve [label="SyncRes::doResolve()", color=red];
+
+    doResolveAt_return_minus_2 [label="return -2", color=green];
+    doResolveAt_return_minus_1 [label="return -1", color=green];
+    doResolveAt_return_0 [label="return 0", color=green];
+    doResolveAt_return_rcode [label="return rcode", color=green];
+    doResolveAt_return_servfail [label="return SERVFAIL", color=green];
+    doResolveAt_return_nxdomain [label="return NXDOMAIN", color=green];
+
+    doResolveAt_mainloop_continue [label="continue"];
+    doResolveAt_mainloop_continue -> "Get nameserver from nameservers";
+
+    doResolveAt_nsiploop_continue [label="continue"];
+    doResolveAt_nsiploop_continue -> "Get IP from IPs";
+
+    doResolveAt_nameServersBlockedByRPZ -> doResolveAt_return_minus_2 [label="RPZ NSDNAME hit"];
+    doResolveAt_nameServersBlockedByRPZ ->  "Get nameserver from nameservers" [lhead=cluster_doResolveAt_mainloop, label="RPZ NSDNAME not hit"];
+
+    doResolveAt_ImmediateServFailException [label="throw ImmediateServFailException", color=green];
+
+    "Get nameserver from nameservers" -> doResolveAt_mainloop_continue [label="qname == nsname"];
+    "Get nameserver from nameservers" -> doResolveAt_return_minus_1 [label="All nameservers tried"];
+    "Get nameserver from nameservers" -> "Is the nameserver out of band?";
+    "Is the nameserver out of band?" -> doResolveAt_doOOBResolve_for_NS [label=yes];
+    doResolveAt_doOOBResolve_for_NS -> "updateCacheFromRecords()";
+    "Is the nameserver out of band?" -> doResolveAt_retrieveAddressesForNS [label=no];
+    doResolveAt_retrieveAddressesForNS -> doResolveAt_mainloop_continue [label="No IPs returned"];
+    doResolveAt_retrieveAddressesForNS -> doResolveAt_nameserverIPBlockedByRPZ [label="IPs returned"];
+    doResolveAt_nameserverIPBlockedByRPZ -> doResolveAt_return_minus_2 [label="RPZ NSIP hit"];
+    doResolveAt_nameserverIPBlockedByRPZ -> "Get IP from IPs" [label="RPZ NSIP not hit"];
+
+    "Get IP from IPs" -> doResolveAt_nsiploop_continue [label="IP is throttled"];
+    "Get IP from IPs" -> doResolveAt_ImmediateServFailException [label="Too many queries sent while resolving"];
+    "Get IP from IPs" -> doResolveAt_ImmediateServFailException [label="Resolving took too long"];
+    "Get IP from IPs" -> doResolveAt_mainloop_continue [label="No IP address worked"];
+    "Get IP from IPs" -> doResolveAt_Lua_preoutquery;
+
+    doResolveAt_Lua_preoutquery -> "Check resolveret" [label="true"];
+    doResolveAt_Lua_preoutquery -> doResolveAt_getEDNSSubnetMask [label="false"];
+    doResolveAt_getEDNSSubnetMask -> doResolveAt_asyncresolveWrapper;
+    doResolveAt_asyncresolveWrapper ->  "Check resolveret";
+    "Check resolveret" -> doResolveAt_ImmediateServFailException [label="resolveret == -3\n(kill query)"];
+    "Check resolveret" -> "resolveret == 1";
+    "resolveret == 1" -> doResolveAt_nsiploop_continue [label="nameserver returned\nSERVFAIL || REFUSED"];
+    "resolveret == 1" -> "updateCacheFromRecords()";
+    "updateCacheFromRecords()" -> doResolveAt_return_rcode [label="rcode != NOERROR"]; // line 1473
+    "updateCacheFromRecords()" -> doResolveAt_processRecords; // line 1484
+    doResolveAt_processRecords -> doResolveAt_return_0 [label="done == true"];
+    doResolveAt_processRecords -> "Have newtarget?";
+
+    "Have newtarget?" -> "qname == newtarget || depth > 10?" [label=yes];
+    "qname == newtarget || depth > 10?" -> doResolveAt_return_servfail [label=yes];
+    "qname == newtarget || depth > 10?" -> doResolveAt_doResolve [label=no];
+    doResolveAt_doResolve -> doResolveAt_return_rcode;
+
+    "Have newtarget?" -> "Have NXDOMAIN from upstream?" [label=no];
+    "Have NXDOMAIN from upstream?" -> "Add NSECs if needed" [label=yes];
+    "Add NSECs if needed" -> doResolveAt_return_nxdomain;
+
+    "Have NXDOMAIN from upstream?" -> "Have empty NOERROR?" [label=no];
+    "Have empty NOERROR?" -> "Add NSEC records if needed" [label=yes];
+    "Add NSEC records if needed" -> doResolveAt_return_0;
+
+    "Have empty NOERROR?" -> "Have realreferral?" [label=no];
+    "Have realreferral?" -> "Was a server in nsset blocker by RPZNSDNAME?" [label=yes];
+    "Was a server in nsset blocker by RPZNSDNAME?" -> doResolveAt_return_minus_2 [label=yes];
+    "Was a server in nsset blocker by RPZNSDNAME?" -> "Get nameserver from nameservers" [label=no];
+
+    "Have realreferral?" -> "Was this an OOB nameserver?" [label=no];
+    "Was this an OOB nameserver?" -> "Get nameserver from nameservers" [label="no, nameserver was lame"];
+    "Was this an OOB nameserver?" -> "Get nameserver from nameservers" [label=yes];
+  }
+
+  subgraph cluster_processRecords {
+    label="SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, bool& sawDS)\nreturns true is this level of recursion is done";
+
+//    { rank=same; "Get record from lwr.d_records" processRecords_return_done}
+
+//    { rank=same; "Is this a proper CNAME referral?" "Is this a DNSSEC record in the ANSWER section?" "Is this the actual answer?" "Is this an NS record in the AUTHORITY?" "Is this a DS in the AUTHORITY?" "Is this a proper NXDOMAIN?" "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?"}
+
+    "Get record from lwr.d_records";
+    "Get record from lwr.d_records" -> "Is this a proper NXDOMAIN?"; // line 1177
+    "Get record from lwr.d_records" -> processRecords_return_done [label="All records checked"];
+    "Get record from lwr.d_records" -> "Get record from lwr.d_records" [label="type != OPT &&\nclass != IN"];
+
+    "Is this a proper NXDOMAIN?" -> "is newtarget empty?" [label=yes]; // note, we have a CNAME chasing bug here issue #679
+    "is newtarget empty?" -> processRecords_wasVariable [label=no];
+    "is newtarget empty?" -> "Add this SOA to ret" [label=yes];
+    processRecords_wasVariable [label="SyncRes::wasVariable", color=red]
+    "Add this SOA to ret" -> processRecords_wasVariable;
+    processRecords_wasVariable -> "Set negindic to true" [label="was indeed variable"];
+    processRecords_wasVariable -> "Add to negative cache" [label="was not variable"];
+    "Add to negative cache" -> "If s_rootNXTrust && auth.isRoot()";
+    "If s_rootNXTrust && auth.isRoot()" -> "Set negindic to true" [label=no];
+    "If s_rootNXTrust && auth.isRoot()" -> "Add tld label to negative cache" [label=yes];
+    "Add tld label to negative cache" -> "Set negindic to true";
+    "Set negindic to true" -> "Get record from lwr.d_records";
+
+    "Is this a proper NXDOMAIN?" -> "Is this a proper CNAME referral?" [label=no];
+    "Is this a proper CNAME referral?" -> "Add CNAME record to ret" [label=yes];
+    "Add CNAME record to ret" -> "Set newtarget to this CNAME" -> "Get record from lwr.d_records";
+
+    "Is this a proper CNAME referral?" -> "Is this a DNSSEC record in the ANSWER section?" [label=no];
+    "Is this a DNSSEC record in the ANSWER section?" -> "Is the record.qtype not RRSIG and is the record's qname the qname we want?"[label=yes];
+    "Is the record.qtype not RRSIG and is the record's qname the qname we want?" -> "Add this record to ret" [label=yes];
+    "Add this record to ret" -> "Get record from lwr.d_records";
+    "Is the record.qtype not RRSIG and is the record's qname the qname we want?" -> "Get record from lwr.d_records" [label=no];
+
+    "Is this a DNSSEC record in the ANSWER section?" -> "Is this the actual answer?" [label=no];
+    "Is this the actual answer?" -> "Set done=true" [label=yes];
+    "Set done=true" -> "Add answer record to ret" -> "Get record from lwr.d_records";
+
+    "Is this the actual answer?" -> "Is this an NS record in the AUTHORITY?" [label=no];
+    "Is this an NS record in the AUTHORITY?" -> "Is record.d_name more specific than the our current auth?" [label=yes];
+    "Is record.d_name more specific than the our current auth?" -> "set newauth to record.d_name" [label=yes];
+    "set newauth to record.d_name" -> "set realreferral=true" -> "add NS record to ret";
+    "Is record.d_name more specific than the our current auth?" -> "add NS record to ret" [label=no];
+    "add NS record to ret" -> "Get record from lwr.d_records";
+
+    "Is this an NS record in the AUTHORITY?" -> "Is this a DS in the AUTHORITY?" [label=no];
+    "Is this a DS in the AUTHORITY?" -> "set sawDS=true" [label=yes];
+    "set sawDS=true" -> "Get record from lwr.d_records";
+
+    "Is this a DS in the AUTHORITY?" -> "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?" [label=no];
+    "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?" -> "is newtarget empty?" [label=yes];
+    "is newtarget empty?" -> "Harvest DNSSEC data and add to negative cache" [label=yes];
+    "is newtarget empty?" -> "Get record from lwr.d_records";
+    "Harvest DNSSEC data and add to negative cache" -> "Set negindic to true" -> "Get record from lwr.d_records";
+
+    "Are we not done && is this a NOERROR && is this a SOA in the AUTHORITY?" -> "Get record from lwr.d_records" [label=no];
+
+    processRecords_return_done [label="return done", color=green];
+  }
+
+  subgraph cluster_doCNAMECacheCheck {
+    label="SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res)\nreturns true if this function handled the query";
+
+    doCNAMECacheCheck_return_true [label="return true", color=green];
+    doCNAMECacheCheck_return_false [label="return false", color=green];
+
+    doCNAMECacheCheck_servfail [label="Set res to SERVFAIL"];
+    doCNAMECacheCheck_servfail -> doCNAMECacheCheck_return_true;
+
+    doCNAMECacheCheck_doResolve [label="SyncRes::doResolve", color=red];
+
+    "Too deep recursion or CNAMEs?" -> doCNAMECacheCheck_servfail [label=yes];
+    "Too deep recursion or CNAMEs?" -> "Get cache entries for qname|CNAME" [label=no];
+    "Get cache entries for qname|CNAME" -> "get a record from the cache entries" -> "Is the TTL not expired?";
+    "Is the TTL not expired?" -> "get a record from the cache entries" [label=yes];
+    "Is the TTL not expired?" -> "Add record and RRSIGS to ret" [label=no];
+    "Add record and RRSIGS to ret" -> "is qtype CNAME?";
+    "is qtype CNAME?" -> doCNAMECacheCheck_doResolve [label=no];
+    doCNAMECacheCheck_doResolve -> "Set res to the result of doResolve" -> doCNAMECacheCheck_return_true;
+    "is qtype CNAME?" -> "Set res=0" [label=yes];
+    "Set res=0" -> doCNAMECacheCheck_return_true;
+    "Get cache entries for qname|CNAME" -> doCNAMECacheCheck_return_false [label="No cache entries"];
+  }
+}
index f70e732729a9cf2f73318d94a3ab5ae057a2bbf3..7e9b7178bf7ff0923c2ef2c8e469f0eeb9033321 100644 (file)
@@ -5,41 +5,6 @@ NetmaskGroup g_ednssubnets;
 SuffixMatchNode g_ednsdomains;
 bool g_useIncomingECS;
 
-boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional<const EDNSSubnetOpts&> incomingECS)
-{
-  static uint8_t l_ipv4limit, l_ipv6limit;
-  if(!l_ipv4limit) {
-    l_ipv4limit = ::arg().asNum("ecs-ipv4-bits");
-    l_ipv6limit = ::arg().asNum("ecs-ipv6-bits");
-  }
-  boost::optional<Netmask> result;
-  ComboAddress trunc;
-  uint8_t bits;
-  if(incomingECS) {
-    if (incomingECS->source.getBits() == 0) {
-      /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
-      return result;
-    }
-    trunc = incomingECS->source.getMaskedNetwork();
-    bits = incomingECS->source.getBits();
-  }
-  else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
-    trunc = local;
-    bits = local.isIPv4() ? 32 : 128;
-  }
-  else {
-    /* nothing usable */
-    return result;
-  }
-
-  if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
-    bits = std::min(bits, (trunc.isIPv4() ? l_ipv4limit : l_ipv6limit));
-    trunc.truncate(bits);
-    return boost::optional<Netmask>(Netmask(trunc, bits));
-  }
-  return result;
-}
-
 void  parseEDNSSubnetWhitelist(const std::string& wlist)
 {
   vector<string> parts;
diff --git a/pdns/recursordist/ednscookies.cc b/pdns/recursordist/ednscookies.cc
new file mode 120000 (symlink)
index 0000000..e8c4721
--- /dev/null
@@ -0,0 +1 @@
+../ednscookies.cc
\ No newline at end of file
diff --git a/pdns/recursordist/ednscookies.hh b/pdns/recursordist/ednscookies.hh
new file mode 120000 (symlink)
index 0000000..f29d488
--- /dev/null
@@ -0,0 +1 @@
+../ednscookies.hh
\ No newline at end of file
diff --git a/pdns/recursordist/negcache.cc b/pdns/recursordist/negcache.cc
new file mode 100644 (file)
index 0000000..c719eae
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "negcache.hh"
+#include "misc.hh"
+#include "cachecleaner.hh"
+
+/*!
+ * Set ne to the NegCacheEntry for the last label in qname and return true if there
+ * was one.
+ *
+ * \param qname    The name to look up (only the last label is used)
+ * \param now      A timeval with the current time, to check if an entry is expired
+ * \param ne       A NegCacheEntry that is filled when there is a cache entry
+ * \return         true if ne was filled out, false otherwise
+ */
+bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne) {
+  // Never deny the root.
+  if (qname.isRoot())
+    return false;
+
+  // An 'ENT' QType entry, used as "whole name" in the neg-cache context.
+  static const QType qtnull(0);
+  DNSName lastLabel = qname.getLastLabel();
+  negcache_t::const_iterator ni = d_negcache.find(tie(lastLabel, qtnull));
+
+  while (ni != d_negcache.end() &&
+         ni->d_name == lastLabel &&
+         ni->d_auth.isRoot() &&
+         ni->d_qtype == qtnull) {
+    // We have something
+    if ((uint32_t)now.tv_sec < ni->d_ttd) {
+      ne = *ni;
+      moveCacheItemToBack(d_negcache, ni);
+      return true;
+    }
+    moveCacheItemToFront(d_negcache, ni);
+    ni++;
+  }
+  return false;
+}
+
+/*!
+ * Set ne to the NegCacheEntry for the qname|qtype tuple and return true
+ *
+ * \param qname    The name to look up
+ * \param qtype    The qtype to look up
+ * \param now      A timeval with the current time, to check if an entry is expired
+ * \param ne       A NegCacheEntry that is filled when there is a cache entry
+ * \return         true if ne was filled out, false otherwise
+ */
+bool NegCache::get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne) {
+  auto range = d_negcache.equal_range(tie(qname));
+  negcache_t::iterator ni = range.first;
+
+  while (ni != range.second) {
+    // We have an entry
+    if (ni->d_qtype.getCode() == 0 || ni->d_qtype == qtype) {
+      // We match the QType or the whole name is denied
+      if((uint32_t) now.tv_sec < ni->d_ttd) {
+        // Not expired
+        ne = *ni;
+        moveCacheItemToBack(d_negcache, ni);
+        return true;
+      }
+      // expired
+      moveCacheItemToFront(d_negcache, ni);
+    }
+    ni++;
+  }
+  return false;
+}
+
+/*!
+ * Places ne into the negative cache, possibly overriding an existing entry.
+ *
+ * \param ne The NegCacheEntry to add to the cache
+ */
+void NegCache::add(const NegCacheEntry& ne) {
+  replacing_insert(d_negcache, ne);
+}
+
+/*!
+ * Returns the amount of entries in the cache
+ *
+ * \param qname The name of the entries to be counted
+ */
+uint64_t NegCache::count(const DNSName& qname) const {
+  return d_negcache.count(tie(qname));
+}
+
+/*!
+ * Returns the amount of entries in the cache for qname+qtype
+ *
+ * \param qname The name of the entries to be counted
+ * \param qtype The type of the entries to be counted
+ */
+uint64_t NegCache::count(const DNSName& qname, const QType qtype) const {
+  return d_negcache.count(tie(qname, qtype));
+}
+
+/*!
+ * Remove all entries for name from the cache. If subtree is true, wipe all names
+ * underneath it.
+ *
+ * \param name    The DNSName of the entries to wipe
+ * \param subtree Should all entries under name be removed?
+ */
+uint64_t NegCache::wipe(const DNSName& name, bool subtree) {
+  uint64_t ret(0);
+  if (subtree) {
+    for (auto i = d_negcache.lower_bound(tie(name)); i != d_negcache.end();) {
+      if(!i->d_name.isPartOf(name))
+        break;
+      i = d_negcache.erase(i);
+      ret++;
+    }
+    return ret;
+  }
+
+  ret = count(name);
+  auto range = d_negcache.equal_range(tie(name));
+  d_negcache.erase(range.first, range.second);
+  return ret;
+}
+
+/*!
+ * Clear the negative cache
+ */
+void NegCache::clear() {
+  d_negcache.clear();
+}
+
+/*!
+ * Perform some cleanup in the cache, removing stale entries
+ *
+ * \param maxEntries The maximum number of entries that may exist in the cache.
+ */
+void NegCache::prune(unsigned int maxEntries) {
+  pruneCollection(d_negcache, maxEntries, 200);
+}
+
+/*!
+ * Writes the whole negative cache to fp
+ *
+ * \param fp A pointer to an open FILE object
+ */
+uint64_t NegCache::dumpToFile(FILE* fp) {
+  uint64_t ret(0);
+  time_t now = time(0);
+  negcache_sequence_t& sidx = d_negcache.get<1>();
+  for(const NegCacheEntry& ne : sidx) {
+    ret++;
+    fprintf(fp, "%s %d IN %s VIA %s\n", ne.d_name.toString().c_str(), (unsigned int) (ne.d_ttd - now), ne.d_qtype.getName().c_str(), ne.d_auth.toString().c_str());
+  }
+  return ret;
+}
diff --git a/pdns/recursordist/negcache.hh b/pdns/recursordist/negcache.hh
new file mode 100644 (file)
index 0000000..d721fe7
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <boost/multi_index_container.hpp>
+#include "dnsparser.hh"
+#include "dnsname.hh"
+#include "dns.hh"
+
+using namespace ::boost::multi_index;
+
+/* FIXME should become part of the normal cache (I think) and shoudl become more like
+ * struct {
+ *   vector<DNSRecord> records;
+ *   vector<DNSRecord> signatures;
+ * } recsig_t;
+ *
+ * typedef vector<recsig_t> recordsAndSignatures;
+ */
+typedef struct {
+  vector<DNSRecord> records;
+  vector<DNSRecord> signatures;
+} recordsAndSignatures;
+
+class NegCache : public boost::noncopyable {
+  public:
+    struct NegCacheEntry {
+      DNSName d_name;                     // The denied name
+      QType d_qtype;                      // The denied type
+      DNSName d_auth;                     // The denying name (aka auth)
+      uint32_t d_ttd;                     // Timestamp when this entry should die
+      recordsAndSignatures authoritySOA;  // The upstream SOA record and RRSIGs
+      recordsAndSignatures DNSSECRecords; // The upstream NSEC(3) and RRSIGs
+      uint32_t getTTD() const {
+        return d_ttd;
+      };
+    };
+
+    void add(const NegCacheEntry& ne);
+    bool get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne);
+    bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne);
+    uint64_t count(const DNSName& qname) const;
+    uint64_t count(const DNSName& qname, const QType qtype) const;
+    void prune(unsigned int maxEntries);
+    void clear();
+    uint64_t dumpToFile(FILE* fd);
+    uint64_t wipe(const DNSName& name, bool subtree = false);
+
+    uint64_t size() {
+      return d_negcache.size();
+    };
+
+  private:
+    typedef boost::multi_index_container <
+      NegCacheEntry,
+      indexed_by <
+        ordered_unique <
+          composite_key <
+            NegCacheEntry,
+            member<NegCacheEntry, DNSName, &NegCacheEntry::d_name>,
+            member<NegCacheEntry, QType, &NegCacheEntry::d_qtype>
+          >,
+          composite_key_compare <
+            CanonDNSNameCompare, std::less<QType>
+          >
+        >,
+        sequenced<>
+      >
+    > negcache_t;
+
+    // Required for the cachecleaner
+    typedef negcache_t::nth_index<1>::type negcache_sequence_t;
+
+    // Stores the negative cache entries
+    negcache_t d_negcache;
+};
diff --git a/pdns/recursordist/packetcache.hh b/pdns/recursordist/packetcache.hh
new file mode 120000 (symlink)
index 0000000..b50f901
--- /dev/null
@@ -0,0 +1 @@
+../packetcache.hh
\ No newline at end of file
diff --git a/pdns/recursordist/test-ednsoptions_cc.cc b/pdns/recursordist/test-ednsoptions_cc.cc
new file mode 100644 (file)
index 0000000..4f48e45
--- /dev/null
@@ -0,0 +1,115 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#include <utility>
+
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednscookies.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+#include "iputils.hh"
+
+/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
+int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize);
+
+BOOST_AUTO_TEST_SUITE(ednsoptions_cc)
+
+static void getRawQueryWithECSAndCookie(const DNSName& name, const Netmask& ecs, const std::string& clientCookie, const std::string& serverCookie, std::vector<uint8_t>& query)
+{
+  DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+  pw.commit();
+
+  EDNSCookiesOpt cookiesOpt;
+  cookiesOpt.client = clientCookie;
+  cookiesOpt.server = serverCookie;
+  string cookiesOptionStr = makeEDNSCookiesOptString(cookiesOpt);
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = ecs;
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  DNSPacketWriter::optvect_t opts;
+  opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
+  opts.push_back(make_pair(EDNSOptionCode::ECS, origECSOptionStr));
+  opts.push_back(make_pair(EDNSOptionCode::COOKIE, cookiesOptionStr));
+  pw.addOpt(512, 0, 0, opts);
+  pw.commit();
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOption) {
+  DNSName name("www.powerdns.com.");
+  Netmask ecs("127.0.0.1/32");
+  vector<uint8_t> query;
+
+  getRawQueryWithECSAndCookie(name, ecs, "deadbeef", "deadbeef", query);
+
+  const struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
+  size_t questionLen = query.size();
+  unsigned int consumed = 0;
+  DNSName dnsname = DNSName(reinterpret_cast<const char*>(query.data()), questionLen, sizeof(dnsheader), false, nullptr, nullptr, &consumed);
+
+  size_t pos = sizeof(dnsheader) + consumed + 4;
+  /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2) = 11 */
+  BOOST_REQUIRE_EQUAL(ntohs(dh->arcount), 1);
+  BOOST_REQUIRE(questionLen > pos + 11);
+  /* OPT root label (1) followed by type (2) */
+  BOOST_REQUIRE_EQUAL(query.at(pos), 0);
+  BOOST_REQUIRE(query.at(pos+2) == QType::OPT);
+
+  char* ecsStart = nullptr;
+  size_t ecsLen = 0;
+  int res = getEDNSOption(reinterpret_cast<char*>(query.data())+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  EDNSSubnetOpts eso;
+  BOOST_REQUIRE(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso));
+
+  BOOST_CHECK(eso.source == ecs);
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOptions) {
+  DNSName name("www.powerdns.com.");
+  Netmask ecs("127.0.0.1/32");
+  vector<uint8_t> query;
+
+  getRawQueryWithECSAndCookie(name, ecs, "deadbeef", "deadbeef", query);
+
+  const struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
+  size_t questionLen = query.size();
+  unsigned int consumed = 0;
+  DNSName dnsname = DNSName(reinterpret_cast<const char*>(query.data()), questionLen, sizeof(dnsheader), false, nullptr, nullptr, &consumed);
+
+  size_t pos = sizeof(dnsheader) + consumed + 4;
+  /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2) = 11 */
+  BOOST_REQUIRE_EQUAL(ntohs(dh->arcount), 1);
+  BOOST_REQUIRE(questionLen > pos + 11);
+  /* OPT root label (1) followed by type (2) */
+  BOOST_REQUIRE_EQUAL(query.at(pos), 0);
+  BOOST_REQUIRE(query.at(pos+2) == QType::OPT);
+
+  std::map<uint16_t, EDNSOptionView> options;
+  int res = getEDNSOptions(reinterpret_cast<char*>(query.data())+pos+9, questionLen - pos - 9, options);
+  BOOST_REQUIRE_EQUAL(res, 0);
+
+  /* 3 EDNS options but two of them are EDNS Cookie, so we only keep one */
+  BOOST_CHECK_EQUAL(options.size(), 2);
+
+  auto it = options.find(EDNSOptionCode::ECS);
+  BOOST_REQUIRE(it != options.end());
+  BOOST_REQUIRE(it->second.content != nullptr);
+  BOOST_REQUIRE_GT(it->second.size, 0);
+
+  EDNSSubnetOpts eso;
+  BOOST_REQUIRE(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso));
+  BOOST_CHECK(eso.source == ecs);
+
+  it = options.find(EDNSOptionCode::COOKIE);
+  BOOST_REQUIRE(it != options.end());
+  BOOST_REQUIRE(it->second.content != nullptr);
+  BOOST_REQUIRE_GT(it->second.size, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/pdns/recursordist/test-negcache_cc.cc b/pdns/recursordist/test-negcache_cc.cc
new file mode 100644 (file)
index 0000000..c0ee00e
--- /dev/null
@@ -0,0 +1,395 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "negcache.hh"
+#include "dnsrecords.hh"
+#include "utility.hh"
+
+static recordsAndSignatures genRecsAndSigs(const DNSName& name, const uint16_t qtype, const string& content, bool sigs) {
+  recordsAndSignatures ret;
+
+  DNSRecord rec;
+  rec.d_name = name;
+  rec.d_type = qtype;
+  rec.d_ttl = 600;
+  rec.d_place = DNSResourceRecord::AUTHORITY;
+  rec.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, QClass::IN, content));
+
+  ret.records.push_back(rec);
+
+  if (sigs) {
+    rec.d_type = QType::RRSIG;
+    rec.d_content = std::make_shared<RRSIGRecordContent>(QType(qtype).getName() + " 5 3 600 2100010100000000 2100010100000000 24567 dummy data");
+    ret.signatures.push_back(rec);
+  }
+
+  return ret;
+}
+
+static NegCache::NegCacheEntry genNegCacheEntry(const DNSName& name, const DNSName& auth, const struct timeval& now, const uint16_t qtype=0) {
+  NegCache::NegCacheEntry ret;
+
+  ret.d_name = name;
+  ret.d_qtype = QType(qtype);
+  ret.d_auth = auth;
+  ret.d_ttd = now.tv_sec + 600;
+  ret.authoritySOA = genRecsAndSigs(auth, QType::SOA, "ns1 hostmaster 1 2 3 4 5", true);
+  ret.DNSSECRecords = genRecsAndSigs(auth, QType::NSEC, "deadbeef", true);
+
+  return ret;
+}
+
+BOOST_AUTO_TEST_SUITE(negcache_cc)
+
+BOOST_AUTO_TEST_CASE(test_get_entry) {
+  /* Add a full name negative entry to the cache and attempt to get an entry for
+   * the A record. Should yield the full name does not exist entry
+   */
+  DNSName qname("www2.powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+
+  BOOST_CHECK_EQUAL(cache.size(), 1);
+
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.get(qname, QType(1), now, ne);
+
+  BOOST_CHECK(ret);
+  BOOST_CHECK_EQUAL(ne.d_name, qname);
+  BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(0).getName());
+  BOOST_CHECK_EQUAL(ne.d_auth, auth);
+}
+
+BOOST_AUTO_TEST_CASE(test_get_NODATA_entry) {
+  DNSName qname("www2.powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now, 1));
+
+  BOOST_CHECK_EQUAL(cache.size(), 1);
+
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.get(qname, QType(1), now, ne);
+
+  BOOST_CHECK(ret);
+  BOOST_CHECK_EQUAL(ne.d_name, qname);
+  BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(1).getName());
+  BOOST_CHECK_EQUAL(ne.d_auth, auth);
+
+  NegCache::NegCacheEntry ne2;
+  ret = cache.get(qname, QType(16), now, ne2);
+  BOOST_CHECK_EQUAL(ret, false);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_entry) {
+  DNSName qname("com");
+  DNSName auth(".");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+
+  BOOST_CHECK_EQUAL(cache.size(), 1);
+
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.getRootNXTrust(qname, now, ne);
+
+  BOOST_CHECK(ret);
+  BOOST_CHECK_EQUAL(ne.d_name, qname);
+  BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(0).getName());
+  BOOST_CHECK_EQUAL(ne.d_auth, auth);
+}
+
+BOOST_AUTO_TEST_CASE(test_add_and_get_expired_entry) {
+  DNSName qname("www2.powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+  now.tv_sec -= 1000;
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+
+  BOOST_CHECK_EQUAL(cache.size(), 1);
+
+  NegCache::NegCacheEntry ne;
+
+  now.tv_sec += 1000;
+  bool ret = cache.get(qname, QType(1), now, ne);
+
+  BOOST_CHECK_EQUAL(ret, false);
+  BOOST_CHECK_EQUAL(ne.d_name, DNSName());
+  BOOST_CHECK_EQUAL(ne.d_auth, DNSName());
+  BOOST_CHECK(ne.authoritySOA.records.empty());
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_expired_entry) {
+  DNSName qname("com");
+  DNSName auth(".");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+  now.tv_sec -= 1000;
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+
+  BOOST_CHECK_EQUAL(cache.size(), 1);
+
+  NegCache::NegCacheEntry ne;
+
+  now.tv_sec += 1000;
+  bool ret = cache.getRootNXTrust(qname, now, ne);
+
+  BOOST_CHECK_EQUAL(ret, false);
+  BOOST_CHECK_EQUAL(ne.d_name, DNSName());
+  BOOST_CHECK_EQUAL(ne.d_auth, DNSName());
+  BOOST_CHECK(ne.authoritySOA.records.empty());
+}
+
+BOOST_AUTO_TEST_CASE(test_add_updated_entry) {
+  DNSName qname("www2.powerdns.com");
+  DNSName auth("powerdns.com");
+  DNSName auth2("com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+  // Should override the existing entry for www2.powerdns.com
+  cache.add(genNegCacheEntry(qname, auth2, now));
+
+  BOOST_CHECK_EQUAL(cache.size(), 1);
+
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.get(qname, QType(1), now, ne);
+
+  BOOST_CHECK(ret);
+  BOOST_CHECK_EQUAL(ne.d_name, qname);
+  BOOST_CHECK_EQUAL(ne.d_auth, auth2);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust) {
+  DNSName qname("www2.powerdns.com");
+  DNSName auth("powerdns.com");
+  DNSName qname2("com");
+  DNSName auth2(".");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+  cache.add(genNegCacheEntry(qname2, auth2, now));
+
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.getRootNXTrust(qname, now, ne);
+
+  BOOST_CHECK(ret);
+  BOOST_CHECK_EQUAL(ne.d_name, qname2);
+  BOOST_CHECK_EQUAL(ne.d_auth, auth2);
+}
+
+BOOST_AUTO_TEST_CASE(test_getRootNXTrust_full_domain_only) {
+  DNSName qname("www2.powerdns.com");
+  DNSName auth("powerdns.com");
+  DNSName qname2("com");
+  DNSName auth2(".");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  cache.add(genNegCacheEntry(qname, auth, now));
+  cache.add(genNegCacheEntry(qname2, auth2, now, 1)); // Add the denial for COM|A
+
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.getRootNXTrust(qname, now, ne);
+
+  BOOST_CHECK_EQUAL(ret, false);
+}
+
+BOOST_AUTO_TEST_CASE(test_prune) {
+  string qname(".powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  NegCache::NegCacheEntry ne;
+  for(int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+    cache.add(ne);
+  }
+
+  BOOST_CHECK_EQUAL(cache.size(), 400);
+
+  cache.prune(100);
+
+  BOOST_CHECK_EQUAL(cache.size(), 100);
+}
+
+BOOST_AUTO_TEST_CASE(test_wipe_single) {
+  string qname(".powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  NegCache::NegCacheEntry ne;
+  ne = genNegCacheEntry(auth, auth, now);
+  cache.add(ne);
+
+  for(int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+    cache.add(ne);
+  }
+
+  BOOST_CHECK_EQUAL(cache.size(), 401);
+
+  // Should only wipe the powerdns.com entry
+  cache.wipe(auth);
+  BOOST_CHECK_EQUAL(cache.size(), 400);
+
+  NegCache::NegCacheEntry ne2;
+  bool ret = cache.get(auth, QType(1), now, ne2);
+
+  BOOST_CHECK_EQUAL(ret, false);
+  BOOST_CHECK_EQUAL(ne2.d_auth, DNSName());
+  BOOST_CHECK_EQUAL(ne2.d_name, DNSName());
+
+  cache.wipe(DNSName("1.powerdns.com"));
+  BOOST_CHECK_EQUAL(cache.size(), 399);
+
+  NegCache::NegCacheEntry ne3;
+  ret = cache.get(auth, QType(1), now, ne3);
+
+  BOOST_CHECK_EQUAL(ret, false);
+  BOOST_CHECK_EQUAL(ne3.d_auth, DNSName());
+  BOOST_CHECK_EQUAL(ne3.d_name, DNSName());
+}
+
+BOOST_AUTO_TEST_CASE(test_wipe_subtree) {
+  string qname(".powerdns.com");
+  string qname2("powerdns.org");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  NegCache::NegCacheEntry ne;
+  ne = genNegCacheEntry(auth, auth, now);
+  cache.add(ne);
+
+  for(int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+    cache.add(ne);
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname2), auth, now);
+    cache.add(ne);
+  }
+
+  BOOST_CHECK_EQUAL(cache.size(), 801);
+
+  // Should wipe all the *.powerdns.com and powerdns.com entries
+  cache.wipe(auth, true);
+  BOOST_CHECK_EQUAL(cache.size(), 400);
+}
+
+BOOST_AUTO_TEST_CASE(test_clear) {
+  string qname(".powerdns.com");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  NegCache::NegCacheEntry ne;
+
+  for(int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+    cache.add(ne);
+  }
+
+  BOOST_CHECK_EQUAL(cache.size(), 400);
+  cache.clear();
+  BOOST_CHECK_EQUAL(cache.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_dumpToFile) {
+  NegCache cache;
+  vector<string> expected;
+  expected.push_back("www1.powerdns.com. 600 IN TYPE0 VIA powerdns.com.\n");
+  expected.push_back("www2.powerdns.com. 600 IN TYPE0 VIA powerdns.com.\n");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  cache.add(genNegCacheEntry(DNSName("www1.powerdns.com"), DNSName("powerdns.com"), now));
+  cache.add(genNegCacheEntry(DNSName("www2.powerdns.com"), DNSName("powerdns.com"), now));
+
+  FILE* fp;
+  fp = tmpfile();
+  if (!fp)
+    BOOST_FAIL("Temporary file could not be opened");
+
+  cache.dumpToFile(fp);
+
+  rewind(fp);
+  char *line = nullptr;
+  size_t len = 0;
+  ssize_t read;
+
+  for (const auto& str : expected) {
+    read = getline(&line, &len, fp);
+    if (read == -1)
+      BOOST_FAIL("Unable to read a line from the temp file");
+    BOOST_CHECK_EQUAL(line, str);
+  }
+  fclose(fp);
+}
+
+BOOST_AUTO_TEST_CASE(test_count) {
+  string qname(".powerdns.com");
+  string qname2("powerdns.org");
+  DNSName auth("powerdns.com");
+
+  struct timeval now;
+  Utility::gettimeofday(&now, 0);
+
+  NegCache cache;
+  NegCache::NegCacheEntry ne;
+  ne = genNegCacheEntry(auth, auth, now);
+  cache.add(ne);
+
+  for(int i = 0; i < 400; i++) {
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname), auth, now);
+    cache.add(ne);
+    ne = genNegCacheEntry(DNSName(std::to_string(i) + qname2), auth, now);
+    cache.add(ne);
+  }
+
+  uint64_t count;
+  count = cache.count(auth);
+  BOOST_CHECK_EQUAL(count, 1);
+  count = cache.count(auth, QType(1));
+  BOOST_CHECK_EQUAL(count, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc
new file mode 100644 (file)
index 0000000..812ccb3
--- /dev/null
@@ -0,0 +1,2368 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "arguments.hh"
+#include "lua-recursor4.hh"
+#include "namespaces.hh"
+#include "rec-lua-conf.hh"
+#include "root-dnssec.hh"
+#include "syncres.hh"
+#include "validate-recursor.hh"
+
+std::unordered_set<DNSName> g_delegationOnly;
+RecursorStats g_stats;
+GlobalStateHolder<LuaConfigItems> g_luaconfs;
+NetmaskGroup* g_dontQuery{nullptr};
+__thread MemRecursorCache* t_RC{nullptr};
+SyncRes::domainmap_t* g_initialDomainMap{nullptr};
+unsigned int g_numThreads = 1;
+
+/* Fake some required functions we didn't want the trouble to
+   link with */
+ArgvMap &arg()
+{
+  static ArgvMap theArg;
+  return theArg;
+}
+
+int getMTaskerTID()
+{
+  return 0;
+}
+
+bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret)
+{
+  return false;
+}
+
+int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res)
+{
+  return 0;
+}
+
+/* primeHints() is only here for now because it
+   was way too much trouble to link with the real one.
+   We should fix this, empty functions are one thing, but this is
+   bad.
+*/
+
+#include "root-addresses.hh"
+
+void primeHints(void)
+{
+  vector<DNSRecord> nsset;
+  if(!t_RC)
+    t_RC = new MemRecursorCache();
+
+  DNSRecord arr, aaaarr, nsrr;
+  nsrr.d_name=g_rootdnsname;
+  arr.d_type=QType::A;
+  aaaarr.d_type=QType::AAAA;
+  nsrr.d_type=QType::NS;
+  arr.d_ttl=aaaarr.d_ttl=nsrr.d_ttl=time(nullptr)+3600000;
+
+  for(char c='a';c<='m';++c) {
+    static char templ[40];
+    strncpy(templ,"a.root-servers.net.", sizeof(templ) - 1);
+    templ[sizeof(templ)-1] = '\0';
+    *templ=c;
+    aaaarr.d_name=arr.d_name=DNSName(templ);
+    nsrr.d_content=std::make_shared<NSRecordContent>(DNSName(templ));
+    arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
+    vector<DNSRecord> aset;
+    aset.push_back(arr);
+    t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true); // auth, nuke it all
+    if (rootIps6[c-'a'] != NULL) {
+      aaaarr.d_content=std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c-'a']));
+
+      vector<DNSRecord> aaaaset;
+      aaaaset.push_back(aaaarr);
+      t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true);
+    }
+
+    nsset.push_back(nsrr);
+  }
+  t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), false); // and stuff in the cache
+}
+
+LuaConfigItems::LuaConfigItems()
+{
+  for (const auto &dsRecord : rootDSs) {
+    auto ds=unique_ptr<DSRecordContent>(dynamic_cast<DSRecordContent*>(DSRecordContent::make(dsRecord)));
+    dsAnchors[g_rootdnsname].insert(*ds);
+  }
+}
+
+/* Some helpers functions */
+
+static void init(bool debug=false)
+{
+  if (debug) {
+    L.setName("test");
+    L.setLoglevel((Logger::Urgency)(6)); // info and up
+    L.disableSyslog(true);
+    L.toConsole(Logger::Info);
+  }
+
+  seedRandom("/dev/urandom");
+  reportAllTypes();
+
+  if (g_dontQuery)
+    delete g_dontQuery;
+  g_dontQuery = new NetmaskGroup();
+
+  if (t_RC)
+    delete t_RC;
+  t_RC = new MemRecursorCache();
+
+  if (g_initialDomainMap)
+    delete g_initialDomainMap;
+  g_initialDomainMap = new SyncRes::domainmap_t(); // new threads needs this to be setup
+
+  SyncRes::s_maxqperq = 50;
+  SyncRes::s_maxtotusec = 1000*7000;
+  SyncRes::s_maxdepth = 40;
+  SyncRes::s_maxnegttl = 3600;
+  SyncRes::s_maxcachettl = 86400;
+  SyncRes::s_packetcachettl = 3600;
+  SyncRes::s_packetcacheservfailttl = 60;
+  SyncRes::s_serverdownmaxfails = 64;
+  SyncRes::s_serverdownthrottletime = 60;
+  SyncRes::s_doIPv6 = true;
+  SyncRes::s_ecsipv4limit = 24;
+  SyncRes::s_ecsipv6limit = 56;
+  SyncRes::s_rootNXTrust = true;
+  SyncRes::s_minimumTTL = 0;
+  SyncRes::s_serverID = "PowerDNS Unit Tests Server ID";
+
+  g_ednssubnets = NetmaskGroup();
+  g_ednsdomains = SuffixMatchNode();
+  g_useIncomingECS = false;
+  g_delegationOnly.clear();
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dfe.clear();
+  g_luaconfs.setState(luaconfsCopy);
+
+  ::arg().set("version-string", "string reported on version.pdns or version.bind")="PowerDNS Unit Tests";
+}
+
+static void initSR(std::unique_ptr<SyncRes>& sr, bool edns0, bool dnssec, SyncRes::LogMode lm=SyncRes::LogNone, time_t fakeNow=0)
+{
+  struct timeval now;
+  if (fakeNow > 0) {
+    now.tv_sec = fakeNow;
+    now.tv_usec = 0;
+  }
+  else {
+    Utility::gettimeofday(&now, 0);
+  }
+
+  sr = std::unique_ptr<SyncRes>(new SyncRes(now));
+  sr->setDoEDNS0(edns0);
+  sr->setDoDNSSEC(dnssec);
+  sr->setLogMode(lm);
+  t_sstorage->domainmap = g_initialDomainMap;
+  t_sstorage->negcache.clear();
+  t_sstorage->nsSpeeds.clear();
+  t_sstorage->ednsstatus.clear();
+  t_sstorage->throttle.clear();
+  t_sstorage->fails.clear();
+  t_sstorage->dnssecmap.clear();
+}
+
+static void setLWResult(LWResult* res, int rcode, bool aa=false, bool tc=false, bool edns=false)
+{
+  res->d_rcode = rcode;
+  res->d_aabit = aa;
+  res->d_tcbit = tc;
+  res->d_haveEDNS = edns;
+}
+
+static void addRecordToList(std::vector<DNSRecord>& records, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place, uint32_t ttl)
+{
+  DNSRecord rec;
+  rec.d_place = place;
+  rec.d_name = name;
+  rec.d_type = type;
+  rec.d_ttl = ttl;
+
+  if (type == QType::NS) {
+    rec.d_content = std::make_shared<NSRecordContent>(DNSName(content));
+  }
+  else if (type == QType::A) {
+    rec.d_content = std::make_shared<ARecordContent>(ComboAddress(content));
+  }
+  else if (type == QType::AAAA) {
+    rec.d_content = std::make_shared<AAAARecordContent>(ComboAddress(content));
+  }
+  else if (type == QType::CNAME) {
+    rec.d_content = std::make_shared<CNAMERecordContent>(DNSName(content));
+  }
+  else if (type == QType::OPT) {
+    rec.d_content = std::make_shared<OPTRecordContent>();
+  }
+  else {
+    rec.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(type, QClass::IN, content));
+  }
+
+  records.push_back(rec);
+}
+
+static void addRecordToList(std::vector<DNSRecord>& records, const std::string& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place, uint32_t ttl)
+{
+  addRecordToList(records, name, type, content, place, ttl);
+}
+
+static void addRecordToLW(LWResult* res, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60)
+{
+  addRecordToList(res->d_records, name, type, content, place, ttl);
+}
+
+static void addRecordToLW(LWResult* res, const std::string& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60)
+{
+  addRecordToLW(res, DNSName(name), type, content, place, ttl);
+}
+
+static bool isRootServer(const ComboAddress& ip)
+{
+  for (size_t idx = 0; idx < rootIps4Count; idx++) {
+    if (ip.toString() == rootIps4[idx]) {
+      return true;
+    }
+  }
+
+  for (size_t idx = 0; idx < rootIps6Count; idx++) {
+    if (ip.toString() == rootIps6[idx]) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/* Real tests */
+
+BOOST_AUTO_TEST_SUITE(syncres_cc)
+
+BOOST_AUTO_TEST_CASE(test_root_primed) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  /* we are primed, we should be able to resolve NS . without any query */
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 13);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_not_primed) {
+  std::unique_ptr<SyncRes> sr;
+  init(false);
+  initSR(sr, true, false);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      queriesCount++;
+
+      if (domain == g_rootdnsname && type == QType::NS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, g_rootdnsname, QType::NS, "a.root-servers.net.", DNSResourceRecord::ANSWER, 3600);
+        addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* we are not primed yet, so SyncRes will have to call primeHints()
+     then call getRootNS(), for which at least one of the root servers needs to answer */
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(queriesCount, 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_not_primed_and_no_response) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+  std::set<ComboAddress> downServers;
+
+  /* we are not primed yet, so SyncRes will have to call primeHints()
+     then call getRootNS(), for which at least one of the root servers needs to answer.
+     None will, so it should ServFail.
+  */
+  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      downServers.insert(ip);
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK(downServers.size() > 0);
+  /* we explicitly refuse to mark the root servers down */
+  for (const auto& server : downServers) {
+    BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 0);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_formerr_fallback) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  ComboAddress noEDNSServer;
+  size_t queriesWithEDNS = 0;
+  size_t queriesWithoutEDNS = 0;
+
+  sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &noEDNSServer](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      if (EDNS0Level != 0) {
+        queriesWithEDNS++;
+        noEDNSServer = ip;
+
+        setLWResult(res, RCode::FormErr);
+        return 1;
+      }
+
+      queriesWithoutEDNS++;
+
+      if (domain == DNSName("powerdns.com") && type == QType::A && !doTCP) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.1");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  primeHints();
+
+  /* fake that the root NS doesn't handle EDNS, check that we fallback */
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(queriesWithEDNS, 1);
+  BOOST_CHECK_EQUAL(queriesWithoutEDNS, 1);
+  BOOST_CHECK_EQUAL(t_sstorage->ednsstatus.size(), 1);
+  BOOST_CHECK_EQUAL(t_sstorage->ednsstatus[noEDNSServer].mode, SyncRes::EDNSStatus::NOEDNS);
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_notimp_fallback) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  size_t queriesWithEDNS = 0;
+  size_t queriesWithoutEDNS = 0;
+
+  sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      if (EDNS0Level != 0) {
+        queriesWithEDNS++;
+        setLWResult(res, RCode::NotImp);
+        return 1;
+      }
+
+      queriesWithoutEDNS++;
+
+      if (domain == DNSName("powerdns.com") && type == QType::A && !doTCP) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.1");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  primeHints();
+
+  /* fake that the NS doesn't handle EDNS, check that we fallback */
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(queriesWithEDNS, 1);
+  BOOST_CHECK_EQUAL(queriesWithoutEDNS, 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_tc_fallback_to_tcp) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  sr->setAsyncCallback([](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      if (!doTCP) {
+        setLWResult(res, 0, false, true, false);
+        return 1;
+      }
+      if (domain == DNSName("powerdns.com") && type == QType::A && doTCP) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.1");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  primeHints();
+
+  /* fake that the NS truncates every request over UDP, we should fallback to TCP */
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_all_nss_down) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+  std::set<ComboAddress> downServers;
+
+  primeHints();
+
+  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return 1;
+      }
+      else {
+        downServers.insert(ip);
+        return 0;
+      }
+    });
+
+  DNSName target("powerdns.com.");
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK_EQUAL(downServers.size(), 4);
+
+  for (const auto& server : downServers) {
+    BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 1);
+    BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(server, target, QType::A)));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_all_nss_network_error) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+  std::set<ComboAddress> downServers;
+
+  primeHints();
+
+  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return 1;
+      }
+      else {
+        downServers.insert(ip);
+        return -1;
+      }
+    });
+
+  /* exact same test than the previous one, except instead of a time out we fake a network error */
+  DNSName target("powerdns.com.");
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK_EQUAL(downServers.size(), 4);
+
+  for (const auto& server : downServers) {
+    BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 1);
+    BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(server, target, QType::A)));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_os_limit_errors) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+  std::set<ComboAddress> downServers;
+
+  primeHints();
+
+  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return 1;
+      }
+      else {
+        if (downServers.size() < 3) {
+          /* only the last one will answer */
+          downServers.insert(ip);
+          return -2;
+        }
+        else {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42");
+          return 1;
+        }
+      }
+    });
+
+  DNSName target("powerdns.com.");
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(downServers.size(), 3);
+
+  /* Error is reported as "OS limit error" (-2) so the servers should _NOT_ be marked down */
+  for (const auto& server : downServers) {
+    BOOST_CHECK_EQUAL(t_sstorage->fails.value(server), 0);
+    BOOST_CHECK(!t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(server, target, QType::A)));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_glued_referral) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+      /* this will cause issue with qname minimization if we ever implement it */
+      if (domain != target) {
+        return 0;
+      }
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, target, QType::A, "192.0.2.4");
+        return 1;
+      }
+      else {
+        return 0;
+      }
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_glueless_referral) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+
+        if (domain.isPartOf(DNSName("com."))) {
+          addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        } else if (domain.isPartOf(DNSName("org."))) {
+          addRecordToLW(res, "org.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        }
+        else {
+          setLWResult(res, RCode::NXDomain, false, false, true);
+          return 1;
+        }
+
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+        if (domain == target) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+          addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+          return 1;
+        }
+        else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2");
+          addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::AAAA, "2001:DB8::2");
+          return 1;
+        }
+        else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3");
+          addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::AAAA, "2001:DB8::3");
+          return 1;
+        }
+
+        setLWResult(res, RCode::NXDomain, false, false, true);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, target, QType::A, "192.0.2.4");
+        return 1;
+      }
+      else {
+        return 0;
+      }
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_submask_by_domain) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  g_useIncomingECS = true;
+  g_ednsdomains.add(target);
+
+  EDNSSubnetOpts incomingECS;
+  incomingECS.source = Netmask("192.0.2.128/32");
+  sr->setIncomingECSFound(true);
+  sr->setIncomingECS(incomingECS);
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      BOOST_REQUIRE(srcmask);
+      BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+}
+
+BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  g_useIncomingECS = true;
+  g_ednssubnets.addMask("192.0.2.1/32");
+
+  EDNSSubnetOpts incomingECS;
+  incomingECS.source = Netmask("2001:DB8::FF/128");
+  sr->setIncomingECSFound(true);
+  sr->setIncomingECS(incomingECS);
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        BOOST_REQUIRE(!srcmask);
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        BOOST_REQUIRE(srcmask);
+        BOOST_CHECK_EQUAL(srcmask->toString(), "2001:db8::/56");
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_following_cname) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("cname.powerdns.com.");
+  const DNSName cnameTarget("cname-target.powerdns.com");
+
+  sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        if (domain == target) {
+          setLWResult(res, 0, true, false, false);
+          addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+          return 1;
+        }
+        else if (domain == cnameTarget) {
+          setLWResult(res, 0, true, false, false);
+          addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        }
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK(ret[1].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget);
+}
+
+BOOST_AUTO_TEST_CASE(test_included_poisonous_cname) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  /* In this test we directly get the NS server for cname.powerdns.com.,
+     and we don't know whether it's also authoritative for
+     cname-target.powerdns.com or powerdns.com, so we shouldn't accept
+     the additional A record for cname-target.powerdns.com. */
+  const DNSName target("cname.powerdns.com.");
+  const DNSName cnameTarget("cname-target.powerdns.com");
+
+  sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        if (domain == target) {
+          setLWResult(res, 0, true, false, false);
+          addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+          addRecordToLW(res, cnameTarget, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL);
+          return 1;
+        } else if (domain == cnameTarget) {
+          setLWResult(res, 0, true, false, false);
+          addRecordToLW(res, cnameTarget, QType::A, "192.0.2.3");
+          return 1;
+        }
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  BOOST_REQUIRE(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(getRR<CNAMERecordContent>(ret[0])->getTarget(), cnameTarget);
+  BOOST_REQUIRE(ret[1].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget);
+  BOOST_CHECK(getRR<ARecordContent>(ret[1])->getCA() == ComboAddress("192.0.2.3"));
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_loop) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  size_t count = 0;
+  const DNSName target("cname.powerdns.com.");
+
+  sr->setAsyncCallback([target,&count](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      count++;
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        if (domain == target) {
+          setLWResult(res, 0, true, false, false);
+          addRecordToLW(res, domain, QType::CNAME, domain.toString());
+          return 1;
+        }
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_GT(ret.size(), 0);
+  BOOST_CHECK_EQUAL(count, 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_depth) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  size_t depth = 0;
+  const DNSName target("cname.powerdns.com.");
+
+  sr->setAsyncCallback([target,&depth](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, std::to_string(depth) + "-cname.powerdns.com");
+        depth++;
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), depth);
+  /* we have an arbitrary limit at 10 when following a CNAME chain */
+  BOOST_CHECK_EQUAL(depth, 10 + 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_time_limit) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  size_t queries = 0;
+  const DNSName target("cname.powerdns.com.");
+
+  sr->setAsyncCallback([target,&queries](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queries++;
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        /* Pretend that this query took 2000 ms */
+        res->d_usec = 2000;
+
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* Set the maximum time to 1 ms */
+  SyncRes::s_maxtotusec = 1000;
+
+  try {
+    vector<DNSRecord> ret;
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch(const ImmediateServFailException& e) {
+  }
+  BOOST_CHECK_EQUAL(queries, 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_referral_depth) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  size_t queries = 0;
+  const DNSName target("www.powerdns.com.");
+
+  sr->setAsyncCallback([target,&queries](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queries++;
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+
+        if (domain == DNSName("www.powerdns.com.")) {
+          addRecordToLW(res, domain, QType::NS, "ns.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        }
+        else if (domain == DNSName("ns.powerdns.com.")) {
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+        }
+        else if (domain == DNSName("ns1.powerdns.org.")) {
+          addRecordToLW(res, domain, QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+        }
+        else if (domain == DNSName("ns2.powerdns.org.")) {
+          addRecordToLW(res, domain, QType::NS, "ns3.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+        }
+        else if (domain == DNSName("ns3.powerdns.org.")) {
+          addRecordToLW(res, domain, QType::NS, "ns4.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+        }
+        else if (domain == DNSName("ns4.powerdns.org.")) {
+          addRecordToLW(res, domain, QType::NS, "ns5.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+          addRecordToLW(res, domain, QType::A, "192.0.2.1", DNSResourceRecord::AUTHORITY, 172800);
+        }
+
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* Set the maximum depth low */
+  SyncRes::s_maxdepth = 10;
+
+  try {
+    vector<DNSRecord> ret;
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch(const ImmediateServFailException& e) {
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_qperq) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  size_t queries = 0;
+  const DNSName target("cname.powerdns.com.");
+
+  sr->setAsyncCallback([target,&queries](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queries++;
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, std::to_string(queries) + "-cname.powerdns.com");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* Set the maximum number of questions very low */
+  SyncRes::s_maxqperq = 5;
+
+  try {
+    vector<DNSRecord> ret;
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch(const ImmediateServFailException& e) {
+    BOOST_CHECK_EQUAL(queries, SyncRes::s_maxqperq);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_throttled_server) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("throttled.powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  size_t queriesToNS = 0;
+
+  sr->setAsyncCallback([target,ns,&queriesToNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ns) {
+
+        queriesToNS++;
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* mark ns as down */
+  t_sstorage->throttle.throttle(time(nullptr), boost::make_tuple(ns, "", 0), SyncRes::s_serverdownthrottletime, 10000);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  /* we should not have sent any queries to ns */
+  BOOST_CHECK_EQUAL(queriesToNS, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_throttled_server_count) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const ComboAddress ns("192.0.2.1:53");
+
+  const size_t blocks = 10;
+  /* mark ns as down for 'blocks' queries */
+  t_sstorage->throttle.throttle(time(nullptr), boost::make_tuple(ns, "", 0), SyncRes::s_serverdownthrottletime, blocks);
+
+  for (size_t idx = 0; idx < blocks; idx++) {
+    BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+  }
+
+  /* we have been throttled 'blocks' times, we should not be throttled anymore */
+  BOOST_CHECK(!t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+}
+
+BOOST_AUTO_TEST_CASE(test_throttled_server_time) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const ComboAddress ns("192.0.2.1:53");
+
+  const size_t seconds = 1;
+  /* mark ns as down for 'seconds' seconds */
+  t_sstorage->throttle.throttle(time(nullptr), boost::make_tuple(ns, "", 0), seconds, 10000);
+  BOOST_CHECK(t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+
+  sleep(seconds + 1);
+
+  /* we should not be throttled anymore */
+  BOOST_CHECK(!t_sstorage->throttle.shouldThrottle(time(nullptr), boost::make_tuple(ns, "", 0)));
+}
+
+BOOST_AUTO_TEST_CASE(test_dont_query_server) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("throttled.powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  size_t queriesToNS = 0;
+
+  sr->setAsyncCallback([target,ns,&queriesToNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ns) {
+
+        queriesToNS++;
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* prevent querying this NS */
+  g_dontQuery->addMask(Netmask(ns));
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  /* we should not have sent any queries to ns */
+  BOOST_CHECK_EQUAL(queriesToNS, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_nx_trust) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target1("powerdns.com.");
+  const DNSName target2("notpowerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queriesCount++;
+
+      if (isRootServer(ip)) {
+
+        if (domain == target1) {
+          setLWResult(res, RCode::NXDomain, true, false, true);
+          addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+        }
+        else {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+          addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        }
+
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  /* one for target1 and one for the entire TLD */
+  BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 2);
+
+  ret.clear();
+  res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  /* one for target1 and one for the entire TLD */
+  BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 2);
+
+  /* we should have sent only one query */
+  BOOST_CHECK_EQUAL(queriesCount, 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_root_nx_dont_trust) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target1("powerdns.com.");
+  const DNSName target2("notpowerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queriesCount++;
+
+      if (isRootServer(ip)) {
+
+        if (domain == target1) {
+          setLWResult(res, RCode::NXDomain, true, false, true);
+          addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+        }
+        else {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+          addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        }
+
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  SyncRes::s_rootNXTrust = false;
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  /* one for target1 */
+  BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 1);
+
+  ret.clear();
+  res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  /* one for target1 */
+  BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 1);
+
+  /* we should have sent three queries */
+  BOOST_CHECK_EQUAL(queriesCount, 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("www.powerdns.com.");
+  const DNSName cnameTarget("cname.powerdns.com.");
+
+  g_useIncomingECS = true;
+  g_ednsdomains.add(DNSName("powerdns.com."));
+
+  EDNSSubnetOpts incomingECS;
+  incomingECS.source = Netmask("192.0.2.128/32");
+  sr->setIncomingECSFound(true);
+  sr->setIncomingECS(incomingECS);
+
+  sr->setAsyncCallback([target,cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      BOOST_REQUIRE(srcmask);
+      BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+        if (domain == target) {
+          /* Type 2 NXDOMAIN (rfc2308 section-2.1) */
+          setLWResult(res, RCode::NXDomain, true, false, true);
+          addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+          addRecordToLW(res, "powerdns.com", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+        }
+        else if (domain == cnameTarget) {
+          /* we shouldn't get there since the Type NXDOMAIN should have been enough,
+             but we might if we still chase the CNAME. */
+          setLWResult(res, RCode::NXDomain, true, false, true);
+          addRecordToLW(res, "powerdns.com", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+        }
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 2);
+  /* no negative cache entry because the response was variable */
+  BOOST_CHECK_EQUAL(t_sstorage->negcache.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_ns_speed) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  std::map<ComboAddress, uint64_t> nsCounts;
+
+  sr->setAsyncCallback([target,&nsCounts](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, domain, QType::NS, "pdns-public-ns3.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "pdns-public-ns3.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "pdns-public-ns3.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 3600);
+
+        return 1;
+      } else {
+        nsCounts[ip]++;
+
+        if (ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("192.0.2.2:53")) {
+          BOOST_CHECK_LT(nsCounts.size(), 3);
+
+          /* let's time out on pdns-public-ns2.powerdns.com. */
+          return 0;
+        }
+        else if (ip == ComboAddress("192.0.2.1:53")) {
+          BOOST_CHECK_EQUAL(nsCounts.size(), 3);
+
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::A, "192.0.2.254");
+          return 1;
+        }
+
+        return 0;
+      }
+
+      return 0;
+    });
+
+  struct timeval now;
+  gettimeofday(&now, 0);
+
+  /* make pdns-public-ns2.powerdns.com. the fastest NS, with its IPv6 address faster than the IPV4 one,
+     then pdns-public-ns1.powerdns.com. on IPv4 */
+  t_sstorage->nsSpeeds[DNSName("pdns-public-ns1.powerdns.com.")].submit(ComboAddress("192.0.2.1:53"), 100, &now);
+  t_sstorage->nsSpeeds[DNSName("pdns-public-ns1.powerdns.com.")].submit(ComboAddress("[2001:DB8::1]:53"), 10000, &now);
+  t_sstorage->nsSpeeds[DNSName("pdns-public-ns2.powerdns.com.")].submit(ComboAddress("192.0.2.2:53"), 10, &now);
+  t_sstorage->nsSpeeds[DNSName("pdns-public-ns2.powerdns.com.")].submit(ComboAddress("[2001:DB8::2]:53"), 1, &now);
+  t_sstorage->nsSpeeds[DNSName("pdns-public-ns3.powerdns.com.")].submit(ComboAddress("192.0.2.3:53"), 10000, &now);
+  t_sstorage->nsSpeeds[DNSName("pdns-public-ns3.powerdns.com.")].submit(ComboAddress("[2001:DB8::3]:53"), 10000, &now);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(nsCounts.size(), 3);
+  BOOST_CHECK_EQUAL(nsCounts[ComboAddress("192.0.2.1:53")], 1);
+  BOOST_CHECK_EQUAL(nsCounts[ComboAddress("192.0.2.2:53")], 1);
+  BOOST_CHECK_EQUAL(nsCounts[ComboAddress("[2001:DB8::2]:53")], 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_flawed_nsset) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.254");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* we populate the cache with a flawed NSset, i.e. there is a NS entry but no corresponding glue */
+  time_t now = time(nullptr);
+  std::vector<DNSRecord> records;
+  std::vector<shared_ptr<RRSIGRecordContent> > sigs;
+  addRecordToList(records, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, now + 3600);
+
+  t_RC->replace(now, target, QType(QType::NS), records, sigs, true, boost::optional<Netmask>());
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_cache_hit) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      return 0;
+    });
+
+  /* we populate the cache with eveything we need */
+  time_t now = time(nullptr);
+  std::vector<DNSRecord> records;
+  std::vector<shared_ptr<RRSIGRecordContent> > sigs;
+
+  addRecordToList(records, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, now + 3600);
+  t_RC->replace(now, target , QType(QType::A), records, sigs, true, boost::optional<Netmask>());
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_no_rd) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  size_t queriesCount = 0;
+
+  sr->setCacheOnly();
+
+  sr->setAsyncCallback([target,&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queriesCount++;
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl) {
+  std::unique_ptr<SyncRes> sr;
+  init(false);
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("cachettl.powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+
+  sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 7200);
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  SyncRes::s_minimumTTL = 60;
+  SyncRes::s_maxcachettl = 3600;
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK_EQUAL(ret[0].d_ttl, SyncRes::s_minimumTTL);
+
+  const ComboAddress who;
+  vector<DNSRecord> cached;
+  const time_t now = time(nullptr);
+  BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), &cached, who), 0);
+  BOOST_REQUIRE_EQUAL(cached.size(), 1);
+  BOOST_REQUIRE_GT(cached[0].d_ttl, now);
+  BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumTTL);
+
+  cached.clear();
+  BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::NS), &cached, who), 0);
+  BOOST_REQUIRE_EQUAL(cached.size(), 1);
+  BOOST_REQUIRE_GT(cached[0].d_ttl, now);
+  BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl);
+}
+
+BOOST_AUTO_TEST_CASE(test_cache_expired_ttl) {
+  std::unique_ptr<SyncRes> sr;
+  init(false);
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+
+        addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  /* we populate the cache with entries that expired 60s ago*/
+  time_t now = time(nullptr);
+  std::vector<DNSRecord> records;
+  std::vector<shared_ptr<RRSIGRecordContent> > sigs;
+  addRecordToList(records, target, QType::A, "192.0.2.42", DNSResourceRecord::ANSWER, now - 60);
+
+  t_RC->replace(now - 3600, target, QType(QType::A), records, sigs, true, boost::optional<Netmask>());
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_REQUIRE(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(getRR<ARecordContent>(ret[0])->getCA().toStringWithPort(), ComboAddress("192.0.2.2").toStringWithPort());
+}
+
+BOOST_AUTO_TEST_CASE(test_delegation_only) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  /* Thanks, Verisign */
+  g_delegationOnly.insert(DNSName("com."));
+  g_delegationOnly.insert(DNSName("net."));
+
+  const DNSName target("nx-powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_unauth_any) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::ANY), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_no_data) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      setLWResult(res, 0, true, false, true);
+      return 1;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_skip_opt_any) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::A, "192.0.2.42");
+      addRecordToLW(res, domain, QType::ANY, "0 0");
+      addRecordToLW(res, domain, QType::OPT, "");
+      return 1;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_nsec_nodnssec) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+      addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      return 1;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_nsec_dnssec) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, true);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+      addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      return 1;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_nx_nsec_nodnssec) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      setLWResult(res, RCode::NXDomain, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+      addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      return 1;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_nx_nsec_dnssec) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, true);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      setLWResult(res, RCode::NXDomain, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      /* the NSEC and RRSIG contents are complete garbage, please ignore them */
+      addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY);
+      return 1;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(ret.size(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_qclass_none) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, true);
+
+  primeHints();
+
+  /* apart from special names and QClass::ANY, anything else than QClass::IN should be rejected right away */
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queriesCount++;
+      return 0;
+    });
+
+  const DNSName target("powerdns.com.");
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::NONE, ret);
+  BOOST_CHECK_EQUAL(res, -1);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfr) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, true);
+
+  primeHints();
+
+  /* {A,I}XFR should be rejected right away */
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      cerr<<"asyncresolve called to ask "<<ip.toStringWithPort()<<" about "<<domain.toString()<<" / "<<QType(type).getName()<<" over "<<(doTCP ? "TCP" : "UDP")<<" (rd: "<<sendRDQuery<<", EDNS0 level: "<<EDNS0Level<<")"<<endl;
+      queriesCount++;
+      return 0;
+    });
+
+  const DNSName target("powerdns.com.");
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::AXFR), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, -1);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  res = sr->beginResolve(target, QType(QType::IXFR), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, -1);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_special_names) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, true);
+
+  primeHints();
+
+  /* special names should be handled internally */
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      queriesCount++;
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(DNSName("1.0.0.127.in-addr.arpa."), QType(QType::PTR), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::PTR);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("1.0.0.127.in-addr.arpa."), QType(QType::ANY), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::PTR);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."), QType(QType::PTR), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::PTR);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."), QType(QType::ANY), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::PTR);
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("localhost."), QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(getRR<ARecordContent>(ret[0])->getCA().toString(), "127.0.0.1");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("localhost."), QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::AAAA);
+  BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(ret[0])->getCA().toString(), "::1");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("localhost."), QType(QType::ANY), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  for (const auto& rec : ret) {
+    BOOST_REQUIRE((rec.d_type == QType::A) || rec.d_type == QType::AAAA);
+    if (rec.d_type == QType::A) {
+      BOOST_CHECK_EQUAL(getRR<ARecordContent>(rec)->getCA().toString(), "127.0.0.1");
+    }
+    else {
+      BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(rec)->getCA().toString(), "::1");
+    }
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("version.bind."), QType(QType::TXT), QClass::CHAOS, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::TXT);
+  BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("version.bind."), QType(QType::ANY), QClass::CHAOS, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::TXT);
+  BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("version.pdns."), QType(QType::TXT), QClass::CHAOS, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::TXT);
+  BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("version.pdns."), QType(QType::ANY), QClass::CHAOS, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::TXT);
+  BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests\"");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("id.server."), QType(QType::TXT), QClass::CHAOS, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::TXT);
+  BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests Server ID\"");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+
+  ret.clear();
+  res = sr->beginResolve(DNSName("id.server."), QType(QType::ANY), QClass::CHAOS, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::TXT);
+  BOOST_CHECK_EQUAL(getRR<TXTRecordContent>(ret[0])->d_text, "\"PowerDNS Unit Tests Server ID\"");
+  BOOST_CHECK_EQUAL(queriesCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_ipv4_rpz) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("rpz.powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+
+  sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  DNSFilterEngine::Policy pol;
+  pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+  luaconfsCopy.dfe.addNSIPTrigger(Netmask(ns, 32), pol, 0);
+  g_luaconfs.setState(luaconfsCopy);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, -2);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("rpz.powerdns.com.");
+  const ComboAddress ns("[2001:DB8::42]:53");
+
+  sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  DNSFilterEngine::Policy pol;
+  pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+  luaconfsCopy.dfe.addNSIPTrigger(Netmask(ns, 128), pol, 0);
+  g_luaconfs.setState(luaconfsCopy);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, -2);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("rpz.powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  const DNSName nsName("ns1.powerdns.com.");
+
+  sr->setAsyncCallback([target,ns,nsName](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  DNSFilterEngine::Policy pol;
+  pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+  luaconfsCopy.dfe.addNSTrigger(nsName, pol, 0);
+  g_luaconfs.setState(luaconfsCopy);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, -2);
+  BOOST_CHECK_EQUAL(ret.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("rpz.powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  const DNSName nsName("ns1.powerdns.com.");
+
+  sr->setAsyncCallback([target,ns,nsName](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      } else if (ip == ns) {
+
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  DNSFilterEngine::Policy pol;
+  pol.d_kind = DNSFilterEngine::PolicyKind::Drop;
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dfe.setPolicyName(0, "Unit test policy 0");
+  luaconfsCopy.dfe.addNSIPTrigger(Netmask(ns, 128), pol, 0);
+  luaconfsCopy.dfe.addNSTrigger(nsName, pol, 0);
+  g_luaconfs.setState(luaconfsCopy);
+
+  /* RPZ is disabled for this query, we should not be blocked */
+  sr->setWantsRPZ(false);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_nord) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  const ComboAddress forwardedNS("192.0.2.42:53");
+
+  SyncRes::AuthDomain ad;
+  ad.d_rdForward = false;
+  ad.d_servers.push_back(forwardedNS);
+  (*t_sstorage->domainmap)[target] = ad;
+
+  sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (ip == forwardedNS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  /* simulate a no-RD query */
+  sr->setCacheOnly();
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_rd) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  const ComboAddress forwardedNS("192.0.2.42:53");
+
+  SyncRes::AuthDomain ad;
+  ad.d_rdForward = false;
+  ad.d_servers.push_back(forwardedNS);
+  (*t_sstorage->domainmap)[target] = ad;
+
+  sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (ip == forwardedNS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  const ComboAddress forwardedNS("192.0.2.42:53");
+
+  SyncRes::AuthDomain ad;
+  ad.d_rdForward = true;
+  ad.d_servers.push_back(forwardedNS);
+  (*t_sstorage->domainmap)[target] = ad;
+
+  sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (ip == forwardedNS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  /* simulate a no-RD query */
+  sr->setCacheOnly();
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  const ComboAddress ns("192.0.2.1:53");
+  const ComboAddress forwardedNS("192.0.2.42:53");
+
+  SyncRes::AuthDomain ad;
+  ad.d_rdForward = true;
+  ad.d_servers.push_back(forwardedNS);
+  (*t_sstorage->domainmap)[target] = ad;
+
+  sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+
+      if (ip == forwardedNS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::A, "192.0.2.42");
+        return 1;
+      }
+
+      return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+}
+
+/*
+// cerr<<"asyncresolve called to ask "<<ip.toStringWithPort()<<" about "<<domain.toString()<<" / "<<QType(type).getName()<<" over "<<(doTCP ? "TCP" : "UDP")<<" (rd: "<<sendRDQuery<<", EDNS0 level: "<<EDNS0Level<<")"<<endl;
+
+- check out of band support
+
+- check preoutquery
+
+*/
+
+BOOST_AUTO_TEST_SUITE_END()
index 1a70f3d2188d8443966d8b773f9c98abe9bb2481..33f0bbaef2c6b6c59be82cb4b1b4d94f08ef9b4e 100644 (file)
@@ -20,7 +20,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#ifdef HAVE_CONFIG_H
 #include "config.h"
+#endif
 
 #include <atomic>
 #include <condition_variable>
index 67c1b789946006766b5436ddb3871dc7b3f8ad70..588610f64dd8e38ba2e21eaa5e9c1e432a3adfa1 100644 (file)
@@ -4,7 +4,8 @@
 #include "packethandler.hh"
 #include "qtype.hh"
 #include "dnspacket.hh"
-#include "packetcache.hh"
+#include "auth-caches.hh"
+#include "statbag.hh"
 #include "dnsseckeeper.hh"
 #include "base64.hh"
 #include "base32.hh"
@@ -15,7 +16,6 @@
 #include "dns_random.hh"
 #include "backends/gsql/ssql.hh"
 
-extern PacketCache PC;
 extern StatBag S;
 
 pthread_mutex_t PacketHandler::s_rfc2136lock=PTHREAD_MUTEX_INITIALIZER;
@@ -966,7 +966,7 @@ int PacketHandler::processUpdate(DNSPacket *p) {
       // Purge the records!
       string zone(di.zone.toString());
       zone.append("$");
-      PC.purge(zone);
+      purgeAuthCaches(zone);
 
       L<<Logger::Info<<msgPrefix<<"Update completed, "<<changedRecords<<" changed records committed."<<endl;
     } else {
index f9ec172d45310469f95934d4189f26920fd87e45..3ab5263dca599786a3b8fd98212eb5526ea4dec3 100644 (file)
  */
 #pragma once
 
-static const char*rootIps4[]={"198.41.0.4",             // a.root-servers.net.
-                              "192.228.79.201",         // b.root-servers.net.
-                              "192.33.4.12",            // c.root-servers.net.
-                              "199.7.91.13",            // d.root-servers.net.
-                              "192.203.230.10",         // e.root-servers.net.
-                              "192.5.5.241",            // f.root-servers.net.
-                              "192.112.36.4",           // g.root-servers.net.
-                              "198.97.190.53",          // h.root-servers.net.
-                              "192.36.148.17",          // i.root-servers.net.
-                              "192.58.128.30",          // j.root-servers.net.
-                              "193.0.14.129",           // k.root-servers.net.
-                              "199.7.83.42",            // l.root-servers.net.
-                              "202.12.27.33"            // m.root-servers.net.
-                              };
+static const char* const rootIps4[]={"198.41.0.4",             // a.root-servers.net.
+                                     "192.228.79.201",         // b.root-servers.net.
+                                     "192.33.4.12",            // c.root-servers.net.
+                                     "199.7.91.13",            // d.root-servers.net.
+                                     "192.203.230.10",         // e.root-servers.net.
+                                     "192.5.5.241",            // f.root-servers.net.
+                                     "192.112.36.4",           // g.root-servers.net.
+                                     "198.97.190.53",          // h.root-servers.net.
+                                     "192.36.148.17",          // i.root-servers.net.
+                                     "192.58.128.30",          // j.root-servers.net.
+                                     "193.0.14.129",           // k.root-servers.net.
+                                     "199.7.83.42",            // l.root-servers.net.
+                                     "202.12.27.33"            // m.root-servers.net.
+                                    };
+static size_t const rootIps4Count = sizeof(rootIps4) / sizeof(*rootIps4);
 
-static const char*rootIps6[]={"2001:503:ba3e::2:30",    // a.root-servers.net.
-                              "2001:500:84::b",         // b.root-servers.net.
-                              "2001:500:2::c",          // c.root-servers.net.
-                              "2001:500:2d::d",         // d.root-servers.net.
-                              "2001:500:a8::e",         // e.root-servers.net.
-                              "2001:500:2f::f",         // f.root-servers.net.
-                              "2001:500:12::d0d",       // g.root-servers.net.
-                              "2001:500:1::53",         // h.root-servers.net.
-                              "2001:7fe::53",           // i.root-servers.net.
-                              "2001:503:c27::2:30",     // j.root-servers.net.
-                              "2001:7fd::1",            // k.root-servers.net.
-                              "2001:500:9f::42",        // l.root-servers.net.
-                              "2001:dc3::35"            // m.root-servers.net.
-                              };
+static const char* const rootIps6[]={"2001:503:ba3e::2:30",    // a.root-servers.net.
+                                     "2001:500:84::b",         // b.root-servers.net.
+                                     "2001:500:2::c",          // c.root-servers.net.
+                                     "2001:500:2d::d",         // d.root-servers.net.
+                                     "2001:500:a8::e",         // e.root-servers.net.
+                                     "2001:500:2f::f",         // f.root-servers.net.
+                                     "2001:500:12::d0d",       // g.root-servers.net.
+                                     "2001:500:1::53",         // h.root-servers.net.
+                                     "2001:7fe::53",           // i.root-servers.net.
+                                     "2001:503:c27::2:30",     // j.root-servers.net.
+                                     "2001:7fd::1",            // k.root-servers.net.
+                                     "2001:500:9f::42",        // l.root-servers.net.
+                                     "2001:dc3::35"            // m.root-servers.net.
+                                    };
+static size_t const rootIps6Count = sizeof(rootIps6) / sizeof(*rootIps6);
index 1f5bb37fe7ed6084d56243ec7c41c759846c5c7c..415c74522fa51c9f9dffb2243168220286ffea28 100644 (file)
@@ -22,5 +22,5 @@
 
 #pragma once
 
-static const char*rootDSs[]={"19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5",
-                             "20326 8 2 e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d"};
+static const char* const rootDSs[]={"19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5",
+                                    "20326 8 2 e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d"};
index c7fa5b817942a40f0fc495a1b8a4fbbfc59dd33a..e1190985a96335ffb4678026cde724433d5be3a8 100644 (file)
@@ -26,7 +26,7 @@ void doSecPoll(time_t* last_secpoll)
   gettimeofday(&now, 0);
   SyncRes sr(now);
   if (g_dnssecmode != DNSSECMode::Off)
-    sr.d_doDNSSEC=true;
+    sr.setDoDNSSEC(true);
   vector<DNSRecord> ret;
 
   string version = "recursor-" +pkgv;
index 36558a733bf5edcede61ca68179c1cca1fe4e123..6cd81097d7a5b2367008da2e541278ad52f18662 100644 (file)
@@ -279,7 +279,7 @@ try
   DNSSECKeeper dk;
   UeberBackend db("key-only");
   
-  chunk_t* chunk;
+  chunk_t* chunk = nullptr;
   int res;
   for(;;) {
     res = readn(fd, &chunk, sizeof(chunk));
@@ -287,21 +287,32 @@ try
       break;
     if(res < 0)
       unixDie("reading object pointer to sign from pdns");
-    set<DNSName> authSet;
-    authSet.insert(d_signer);
-    addRRSigs(dk, db, authSet, *chunk);
-    ++d_signed;
-    
-    writen2(fd, &chunk, sizeof(chunk));
+    try {
+      set<DNSName> authSet;
+      authSet.insert(d_signer);
+      addRRSigs(dk, db, authSet, *chunk);
+      ++d_signed;
+
+      writen2(fd, &chunk, sizeof(chunk));
+      chunk = nullptr;
+    }
+    catch(const PDNSException& pe) {
+      delete chunk;
+      throw;
+    }
+    catch(const std::exception& e) {
+      delete chunk;
+      throw;
+    }
   }
   close(fd);
 }
-catch(PDNSException& pe)
+catch(const PDNSException& pe)
 {
   L<<Logger::Error<<"Signing thread died because of PDNSException: "<<pe.reason<<endl;
   close(fd);
 }
-catch(std::exception& e)
+catch(const std::exception& e)
 {
   L<<Logger::Error<<"Signing thread died because of std::exception: "<<e.what()<<endl;
   close(fd);
index 41fa9f802f302148294e15f99611abdf52b6f447..4e2afe24662f977571ee0ca2c364f49e5eb08784 100644 (file)
@@ -32,7 +32,7 @@ public:
   void run()
   {
 #ifdef HAVE_NET_SNMP
-  d_thread = std::move(std::thread(&SNMPAgent::worker, this));
+  d_thread = std::thread(&SNMPAgent::worker, this);
 #endif /* HAVE_NET_SNMP */
   }
 
index 048e411762c979c3345a75bc91c3a064635fa57d..ab15434b2dbf4eca054ee692a1c680d28202273a 100644 (file)
 #include "statbag.hh"
 #include "stubresolver.hh"
 
-// s_stubresolvers contains the ComboAddresses that are used by
+// s_resolversForStub contains the ComboAddresses that are used by
 // stubDoResolve
-static vector<ComboAddress> s_stubresolvers;
+static vector<ComboAddress> s_resolversForStub;
 
-/** Parse /etc/resolv.conf and add the nameservers to the vector
- * s_stubresolvers.
+/*
+ * Returns false if no resolvers are configured, while emitting a warning about this
+ */
+bool resolversDefined()
+{
+  if (s_resolversForStub.empty()) {
+    L<<Logger::Warning<<"No upstream resolvers configured, stub resolving (including secpoll and ALIAS) impossible."<<endl;
+    return false;
+  }
+  return true;
+}
+
+/*
+ * Fill the s_resolversForStub vector with addresses for the upstream resolvers.
+ * First, parse the `resolver` configuration option for IP addresses to use.
+ * If that doesn't work, parse /etc/resolv.conf and add those nameservers to
+ * s_resolversForStub.
  */
 void stubParseResolveConf()
 {
-  ifstream ifs("/etc/resolv.conf");
-  if(!ifs)
-    return;
-
-  string line;
-  while(std::getline(ifs, line)) {
-    boost::trim_right_if(line, is_any_of(" \r\n\x1a"));
-    boost::trim_left(line); // leading spaces, let's be nice
-
-    string::size_type tpos = line.find_first_of(";#");
-    if(tpos != string::npos)
-      line.resize(tpos);
-
-    if(boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
-      vector<string> parts;
-      stringtok(parts, line, " \t,"); // be REALLY nice
-      for(vector<string>::const_iterator iter = parts.begin()+1; iter != parts.end(); ++iter) {
-        try {
-          s_stubresolvers.push_back(ComboAddress(*iter, 53));
-        }
-        catch(...)
-        {
+  if(::arg().mustDo("resolver")) {
+    vector<string> parts;
+    stringtok(parts, ::arg()["resolver"], " ,\t");
+    for (const auto& addr : parts)
+      s_resolversForStub.push_back(ComboAddress(addr, 53));
+  }
+
+  if (s_resolversForStub.empty()) {
+    ifstream ifs("/etc/resolv.conf");
+    if(!ifs)
+      return;
+
+    string line;
+    while(std::getline(ifs, line)) {
+      boost::trim_right_if(line, is_any_of(" \r\n\x1a"));
+      boost::trim_left(line); // leading spaces, let's be nice
+
+      string::size_type tpos = line.find_first_of(";#");
+      if(tpos != string::npos)
+        line.resize(tpos);
+
+      if(boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
+        vector<string> parts;
+        stringtok(parts, line, " \t,"); // be REALLY nice
+        for(vector<string>::const_iterator iter = parts.begin()+1; iter != parts.end(); ++iter) {
+          try {
+            s_resolversForStub.push_back(ComboAddress(*iter, 53));
+          }
+          catch(...)
+          {
+          }
         }
       }
     }
   }
-
-  if(::arg().mustDo("resolver"))
-    s_stubresolvers.push_back(ComboAddress(::arg()["resolver"], 53));
-
-  // Last resort, add 127.0.0.1
-  if(s_stubresolvers.empty()) {
-    s_stubresolvers.push_back(ComboAddress("127.0.0.1", 53));
-  }
+  // Emit a warning if there are no stubs.
+  resolversDefined();
 }
 
-// s_stubresolvers contains the ComboAddresses that are used to resolve the
+// s_resolversForStub contains the ComboAddresses that are used to resolve the
 int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret)
 {
+  if (!resolversDefined())
+    return RCode::ServFail;
+
   vector<uint8_t> packet;
 
   DNSPacketWriter pw(packet, qname, qtype);
   pw.getHeader()->id=dns_random(0xffff);
   pw.getHeader()->rd=1;
-  if (s_stubresolvers.empty()) {
-    L<<Logger::Warning<<"No recursors set, stub resolving (including secpoll and ALIAS) impossible."<<endl;
-    return RCode::ServFail;
-  }
 
   string msg ="Doing stub resolving, using resolvers: ";
-  for (const auto& server : s_stubresolvers) {
+  for (const auto& server : s_resolversForStub) {
     msg += server.toString() + ", ";
   }
   L<<Logger::Debug<<msg.substr(0, msg.length() - 2)<<endl;
 
-  for(const ComboAddress& dest :  s_stubresolvers) {
+  for(const ComboAddress& dest :  s_resolversForStub) {
     Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
     sock.setNonBlocking();
     sock.connect(dest);
index fbf9aeae1a31a5b2a7d8f7344544efe8c993cb2a..bcbec421da07e5a572486257f7da5a9cdf8caa08 100644 (file)
@@ -24,4 +24,5 @@
 #include "dnsparser.hh"
 
 void stubParseResolveConf();
+bool resolversDefined();
 int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret);
index 76cd5089dd853a880b32a6bf2d655a11a0d87a9c..75a9430fc7f9467a3d36702190cf989ccdbf8933 100644 (file)
@@ -67,6 +67,8 @@ std::atomic<uint64_t> SyncRes::s_throttledqueries;
 std::atomic<uint64_t> SyncRes::s_dontqueries;
 std::atomic<uint64_t> SyncRes::s_nodelegated;
 std::atomic<uint64_t> SyncRes::s_unreachables;
+uint8_t SyncRes::s_ecsipv4limit;
+uint8_t SyncRes::s_ecsipv6limit;
 unsigned int SyncRes::s_minimumTTL;
 bool SyncRes::s_doIPv6;
 bool SyncRes::s_nopacketcache;
@@ -111,8 +113,8 @@ static void accountAuthLatency(int usec, int family)
 
 
 SyncRes::SyncRes(const struct timeval& now) :  d_outqueries(0), d_tcpoutqueries(0), d_throttledqueries(0), d_timeouts(0), d_unreachables(0),
-                                              d_totUsec(0), d_doDNSSEC(false), d_now(now),
-                                              d_cacheonly(false), d_nocache(false), d_doEDNS0(false), d_lm(s_lm)
+                                              d_totUsec(0), d_now(now),
+                                              d_cacheonly(false), d_nocache(false), d_doDNSSEC(false), d_doEDNS0(false), d_lm(s_lm)
                                                  
 { 
   if(!t_sstorage) {
@@ -127,49 +129,11 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
   d_wasVariable=false;
   d_wasOutOfBand=false;
 
-  if( (qtype.getCode() == QType::AXFR))
-    return -1;
-
-  static const DNSName arpa("1.0.0.127.in-addr.arpa."), localhost("localhost."), 
-    versionbind("version.bind."), idserver("id.server."), versionpdns("version.pdns.");
-
-  if( (qtype.getCode()==QType::PTR && qname==arpa) ||
-      (qtype.getCode()==QType::A && qname==localhost)) {
-    ret.clear();
-    DNSRecord dr;
-    dr.d_name=qname;
-    dr.d_place = DNSResourceRecord::ANSWER;
-    dr.d_type=qtype.getCode();
-    dr.d_class=QClass::IN;
-    dr.d_ttl=86400;
-    if(qtype.getCode()==QType::PTR)
-      dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::PTR, 1, "localhost."));
-    else
-      dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::A, 1, "127.0.0.1"));
-    ret.push_back(dr);
-    d_wasOutOfBand=true;
+  if (doSpecialNamesResolve(qname, qtype, qclass, ret))
     return 0;
-  }
 
-  if(qclass==QClass::CHAOS && qtype.getCode()==QType::TXT &&
-        (qname==versionbind || qname==idserver || qname==versionpdns )
-     ) {
-    ret.clear();
-    DNSRecord dr;
-    dr.d_name=qname;
-    dr.d_type=qtype.getCode();
-    dr.d_class=qclass;
-    dr.d_ttl=86400;
-    dr.d_place = DNSResourceRecord::ANSWER;
-    if(qname==versionbind  || qname==versionpdns)
-      dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::TXT, 3, "\""+::arg()["version-string"]+"\""));
-    else
-      dr.d_content=shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(QType::TXT, 3, "\""+s_serverID+"\""));
-
-    ret.push_back(dr);
-    d_wasOutOfBand=true;
-    return 0;
-  }
+  if( (qtype.getCode() == QType::AXFR) || (qtype.getCode() == QType::IXFR))
+    return -1;
 
   if(qclass==QClass::ANY)
     qclass=QClass::IN;
@@ -181,6 +145,74 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
   return res;
 }
 
+/*! Handles all special, built-in names
+ * Fills ret with an answer and returns true if it handled the query.
+ *
+ * Handles the following queries (and their ANY variants):
+ *
+ * - localhost. IN A
+ * - localhost. IN AAAA
+ * - 1.0.0.127.in-addr.arpa. IN PTR
+ * - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. IN PTR
+ * - version.bind. CH TXT
+ * - version.pdns. CH TXT
+ * - id.server. CH TXT
+ */
+bool SyncRes::doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t &qclass, vector<DNSRecord> &ret)
+{
+  static const DNSName arpa("1.0.0.127.in-addr.arpa."), ip6_arpa("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."),
+    localhost("localhost."), versionbind("version.bind."), idserver("id.server."), versionpdns("version.pdns.");
+
+  bool handled = false;
+  vector<pair<QType::typeenum, string> > answers;
+
+  if ((qname == arpa || qname == ip6_arpa) &&
+      qclass == QClass::IN) {
+    handled = true;
+    if (qtype == QType::PTR || qtype == QType::ANY)
+      answers.push_back({QType::PTR, "localhost."});
+  }
+
+  if (qname == localhost &&
+      qclass == QClass::IN) {
+    handled = true;
+    if (qtype == QType::A || qtype == QType::ANY)
+      answers.push_back({QType::A, "127.0.0.1"});
+    if (qtype == QType::AAAA || qtype == QType::ANY)
+      answers.push_back({QType::AAAA, "::1"});
+  }
+
+  if ((qname == versionbind || qname == idserver || qname == versionpdns) &&
+      qclass == QClass::CHAOS) {
+    handled = true;
+    if (qtype == QType::TXT || qtype == QType::ANY) {
+      if(qname == versionbind || qname == versionpdns)
+        answers.push_back({QType::TXT, "\""+::arg()["version-string"]+"\""});
+      else
+        answers.push_back({QType::TXT, "\""+s_serverID+"\""});
+    }
+  }
+
+  if (handled && !answers.empty()) {
+    ret.clear();
+    d_wasOutOfBand=true;
+
+    DNSRecord dr;
+    dr.d_name = qname;
+    dr.d_place = DNSResourceRecord::ANSWER;
+    dr.d_class = qclass;
+    dr.d_ttl = 86400;
+    for (const auto& ans : answers) {
+      dr.d_type = ans.first;
+      dr.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(ans.first, qclass, ans.second));
+      ret.push_back(dr);
+    }
+  }
+
+  return handled;
+}
+
+
 //! This is the 'out of band resolver', in other words, the authoritative server
 bool SyncRes::doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int& res)
 {
@@ -347,7 +379,7 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
   */
 
   SyncRes::EDNSStatus* ednsstatus;
-  ednsstatus = &t_sstorage->ednsstatus[ip]; // does this include port? 
+  ednsstatus = &t_sstorage->ednsstatus[ip]; // does this include port? YES
 
   if(ednsstatus->modeSetAt && ednsstatus->modeSetAt + 3600 < d_now.tv_sec) {
     *ednsstatus=SyncRes::EDNSStatus();
@@ -373,8 +405,13 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
     }
     else if(ednsMANDATORY || mode==EDNSStatus::UNKNOWN || mode==EDNSStatus::EDNSOK || mode==EDNSStatus::EDNSIGNORANT)
       EDNSLevel = 1;
-    
-    ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, luaconfsLocal->outgoingProtobufServer, res);
+
+    if (d_asyncResolve) {
+      ret = d_asyncResolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, luaconfsLocal->outgoingProtobufServer, res);
+    }
+    else {
+      ret=asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, luaconfsLocal->outgoingProtobufServer, res);
+    }
     if(ret < 0) {
       return ret; // transport error, nothing to learn here
     }
@@ -408,6 +445,15 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
   return ret;
 }
 
+/*! This function will check the cache and go out to the internet if the answer is not in cache
+ *
+ * \param qname The name we need an answer for
+ * \param qtype
+ * \param ret The vector of DNSRecords we need to fill with the answers
+ * \param depth The recursion depth we are in
+ * \param beenthere
+ * \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
+ */
 int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere)
 {
   string prefix;
@@ -422,6 +468,8 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
     throw ImmediateServFailException("More than "+std::to_string(s_maxdepth)+" (max-recursion-depth) levels of recursion needed while resolving "+qname.toLogString());
 
   int res=0;
+
+  // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
   if(!(d_nocache && qtype.getCode()==QType::NS && qname.isRoot())) {
     if(d_cacheonly) { // very limited OOB support
       LWResult lwr;
@@ -439,15 +487,19 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
           const ComboAddress remoteIP = servers.front();
           LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
 
-         boost::optional<Netmask> nm;
+          boost::optional<Netmask> nm;
           res=asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, qtype.getCode(), false, false, &d_now, nm, &lwr);
           // filter out the good stuff from lwr.result()
-
-         for(const auto& rec : lwr.d_records) {
-            if(rec.d_place == DNSResourceRecord::ANSWER)
-              ret.push_back(rec);
+          if (res == 1) {
+            for(const auto& rec : lwr.d_records) {
+              if(rec.d_place == DNSResourceRecord::ANSWER)
+                ret.push_back(rec);
+            }
+            return 0;
+          }
+          else {
+            return RCode::ServFail;
           }
-          return res;
         }
       }
     }
@@ -644,7 +696,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
       // We lost the root NS records
       primeHints();
       LOG(prefix<<qname<<": reprimed the root"<<endl);
-      getRootNS();
+      getRootNS(d_now, d_asyncResolve);
     }
   }while(subdomain.chopOff());
 }
@@ -729,7 +781,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
          ret.push_back(sigdr);
        }
 
-        if(!(qtype==QType(QType::CNAME))) { // perhaps they really wanted a CNAME!
+        if(qtype != QType::CNAME) { // perhaps they really wanted a CNAME!
           set<GetBestNSAnswer>beenthere;
           res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere);
         }
@@ -743,11 +795,19 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   return false;
 }
 
-static const DNSName getLastLabel(const DNSName& qname)
-{
-  DNSName ret(qname);
-  ret.trimToLabels(1);
-  return ret;
+/*!
+ * Convience function to push the records from records into ret with a new TTL
+ *
+ * \param records DNSRecords that need to go into ret
+ * \param ttl     The new TTL for these records
+ * \param ret     The vector of DNSRecords that should contian the records with the modified TTL
+ */
+static void addTTLModifiedRecords(const vector<DNSRecord>& records, const uint32_t ttl, vector<DNSRecord>& ret) {
+  for (const auto& rec : records) {
+    DNSRecord r(rec);
+    r.d_ttl = ttl;
+    ret.push_back(r);
+  }
 }
 
 
@@ -761,70 +821,52 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
     prefix.append(depth, ' ');
   }
 
+  // sqname and sqtype are used contain 'higher' names if we have them (e.g. powerdns.com|SOA when we find a negative entry for doesnotexists.powerdns.com|A)
   DNSName sqname(qname);
   QType sqt(qtype);
   uint32_t sttl=0;
   //  cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
 
-  pair<negcache_t::const_iterator, negcache_t::const_iterator> range;
-  QType qtnull(0);
-
   DNSName authname(qname);
   bool wasForwardedOrAuth = (getBestAuthZone(&authname) != t_sstorage->domainmap->end());
+  NegCache::NegCacheEntry ne;
 
   if(s_rootNXTrust &&
-     (range.first=t_sstorage->negcache.find(tie(getLastLabel(qname), qtnull))) != t_sstorage->negcache.end() &&
-      !(wasForwardedOrAuth && !authname.isRoot()) && // when forwarding, the root may only neg-cache if it was forwarded to.
-      range.first->d_qname.isRoot() && (uint32_t)d_now.tv_sec < range.first->d_ttd) {
-    sttl=range.first->d_ttd - d_now.tv_sec;
-
-    LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<range.first->d_name<<"' & '"<<range.first->d_qname<<"' for another "<<sttl<<" seconds"<<endl);
+     t_sstorage->negcache.getRootNXTrust(qname, d_now, ne) &&
+      ne.d_auth.isRoot() &&
+      !(wasForwardedOrAuth && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
+    sttl = ne.d_ttd - d_now.tv_sec;
+    LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
     res = RCode::NXDomain;
-    sqname=range.first->d_qname;
-    sqt=QType::SOA;
-    moveCacheItemToBack(t_sstorage->negcache, range.first);
-
-    giveNegative=true;
+    giveNegative = true;
   }
-  else {
-    range=t_sstorage->negcache.equal_range(tie(qname));
-    negcache_t::iterator ni;
-    for(ni=range.first; ni != range.second; ni++) {
-      // we have something
-      if(!(wasForwardedOrAuth && ni->d_qname != authname) && // Only the authname nameserver can neg cache entries
-         (ni->d_qtype.getCode() == 0 || ni->d_qtype == qtype)) {
-       res=0;
-       if((uint32_t)d_now.tv_sec < ni->d_ttd) {
-         sttl=ni->d_ttd - d_now.tv_sec;
-         if(ni->d_qtype.getCode()) {
-           LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ni->d_qname<<"' for another "<<sttl<<" seconds"<<endl);
-           res = RCode::NoError;
-         }
-         else {
-           LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ni->d_qname<<"' for another "<<sttl<<" seconds"<<endl);
-           res= RCode::NXDomain;
-         }
-         giveNegative=true;
-         sqname=ni->d_qname;
-         sqt=QType::SOA;
-          if(d_doDNSSEC) {
-            for(const auto& p : ni->d_dnssecProof) {
-              for(const auto& rec: p.second.records) 
-                ret.push_back(rec);
-              for(const auto& rec: p.second.signatures) 
-                ret.push_back(rec);
-            }
-          }
-         moveCacheItemToBack(t_sstorage->negcache, ni);
-         break;
-       }
-       else {
-         LOG(prefix<<qname<<": Entire name '"<<qname<<"' or type was negatively cached, but entry expired"<<endl);
-         moveCacheItemToFront(t_sstorage->negcache, ni);
-       }
-      }
+  else if (t_sstorage->negcache.get(qname, qtype, d_now, ne) &&
+           !(wasForwardedOrAuth && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
+    res = 0;
+    sttl = ne.d_ttd - d_now.tv_sec;
+    giveNegative = true;
+    if(ne.d_qtype.getCode()) {
+      LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
+      res = RCode::NoError;
     }
+    else {
+      LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
+      res = RCode::NXDomain;
+    }
+    if(d_doDNSSEC) {
+      addTTLModifiedRecords(ne.DNSSECRecords.records, sttl, ret);
+      addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
+    }
+  }
+
+  if (giveNegative) {
+    // Transplant SOA to the returned packet
+    addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
+    if(d_doDNSSEC)
+      addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
+    return true;
   }
+
   vector<DNSRecord> cset;
   bool found=false, expired=false;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
@@ -836,10 +878,6 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
       if(j->d_ttl>(unsigned int) d_now.tv_sec) {
         DNSRecord dr=*j;
         ttl = (dr.d_ttl-=d_now.tv_sec);
-        if(giveNegative) {
-          dr.d_place=DNSResourceRecord::AUTHORITY;
-          dr.d_ttl=sttl;
-        }
         ret.push_back(dr);
         LOG("[ttl="<<dr.d_ttl<<"] ");
         found=true;
@@ -856,7 +894,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
       dr.d_name=sqname;
       dr.d_ttl=ttl; 
       dr.d_content=signature;
-      dr.d_place= giveNegative ? DNSResourceRecord::AUTHORITY : DNSResourceRecord::ANSWER;
+      dr.d_place = DNSResourceRecord::ANSWER;
       dr.d_class=1;
       ret.push_back(dr);
     }
@@ -930,35 +968,53 @@ static bool magicAddrMatch(const QType& query, const QType& answer)
   return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
 }
 
-
-static recsig_t harvestRecords(const vector<DNSRecord>& records, const set<uint16_t>& types)
-{
-  recsig_t ret;
+/* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
+ *
+ * \param records The records to parse for the authority SOA and NSEC(3) records
+ * \param ne      The NegCacheEntry to be filled out (will not be cleared, only appended to
+ */
+static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne) {
+  static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
   for(const auto& rec : records) {
+    if(rec.d_place != DNSResourceRecord::AUTHORITY)
+      // RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
+      // the AUTHORITY section. Section 3.1.1 indicates that that RRSIGs for
+      // records MUST be in the same section as the records they cover.
+      // Hence, we ignore all records outside of the AUTHORITY section.
+      continue;
+
     if(rec.d_type == QType::RRSIG) {
-      auto rrs=getRR<RRSIGRecordContent>(rec);
-      if(rrs && types.count(rrs->d_type))
-       ret[make_pair(rec.d_name, rrs->d_type)].signatures.push_back(rec);
+      auto rrsig = getRR<RRSIGRecordContent>(rec);
+      if(rrsig) {
+        if(rrsig->d_type == QType::SOA) {
+          ne.authoritySOA.signatures.push_back(rec);
+        }
+        if(nsecTypes.count(rrsig->d_type)) {
+          ne.DNSSECRecords.signatures.push_back(rec);
+        }
+      }
+      continue;
+    }
+    if(rec.d_type == QType::SOA) {
+      ne.authoritySOA.records.push_back(rec);
+      continue;
+    }
+    if(nsecTypes.count(rec.d_type)) {
+      ne.DNSSECRecords.records.push_back(rec);
+      continue;
     }
-    else if(types.count(rec.d_type))
-      ret[make_pair(rec.d_name, rec.d_type)].records.push_back(rec);
   }
-  return ret;
 }
 
+// TODO remove after processRecords is fixed!
+// Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
 static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
 {
-  auto csp = harvestRecords(records, {QType::NSEC, QType::NSEC3, QType::SOA});
-  for(const auto& c : csp) {
-    if(c.first.second == QType::NSEC || c.first.second == QType::NSEC3 || c.first.second == QType::SOA) {
-      if(c.first.second !=QType::SOA) {
-       for(const auto& rec : c.second.records)
-         ret.push_back(rec);
-      }
-      for(const auto& rec : c.second.signatures)
-       ret.push_back(rec);
-    }
-  }
+  NegCache::NegCacheEntry ne;
+  harvestNXRecords(records, ne);
+  ret.insert(ret.end(), ne.authoritySOA.signatures.begin(), ne.authoritySOA.signatures.end());
+  ret.insert(ret.end(), ne.DNSSECRecords.records.begin(), ne.DNSSECRecords.records.end());
+  ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
 }
 
 bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
@@ -1171,17 +1227,18 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       if(newtarget.empty()) // only add a SOA if we're not going anywhere after this
         ret.push_back(rec);
       if(!wasVariable()) {
-        NegCacheEntry ne;
-
-        ne.d_qname=rec.d_name;
-        ne.d_ttd=d_now.tv_sec + rec.d_ttl;
-        ne.d_name=qname;
-        ne.d_qtype=QType(0); // this encodes 'whole record'
-        ne.d_dnssecProof = harvestRecords(lwr.d_records, {QType::NSEC, QType::NSEC3});
-        replacing_insert(t_sstorage->negcache, ne);
-        if(s_rootNXTrust && auth.isRoot()) {
-          ne.d_name = getLastLabel(ne.d_name);
-          replacing_insert(t_sstorage->negcache, ne);
+        NegCache::NegCacheEntry ne;
+
+        ne.d_name = rec.d_name;
+        ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+        ne.d_name = qname;
+        ne.d_qtype = QType(0); // this encodes 'whole record'
+        ne.d_auth = auth;
+        harvestNXRecords(lwr.d_records, ne);
+        t_sstorage->negcache.add(ne);
+        if(s_rootNXTrust && auth.isRoot()) { // We should check if it was forwarded here, see issue #5107
+          ne.d_name = ne.d_name.getLastLabel();
+          t_sstorage->negcache.add(ne);
         }
       }
 
@@ -1237,14 +1294,14 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
         ret.push_back(rec);
         if(!wasVariable()) {
-          NegCacheEntry ne;
-          ne.d_qname=rec.d_name;
-          ne.d_ttd=d_now.tv_sec + rec.d_ttl;
-          ne.d_name=qname;
-          ne.d_qtype=qtype;
-          ne.d_dnssecProof = harvestRecords(lwr.d_records, {QType::NSEC, QType::NSEC3});
+          NegCache::NegCacheEntry ne;
+          ne.d_auth = rec.d_name;
+          ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+          ne.d_name = qname;
+          ne.d_qtype = qtype;
+          harvestNXRecords(lwr.d_records, ne);
           if(qtype.getCode()) {  // prevents us from blacking out a whole domain
-            replacing_insert(t_sstorage->negcache, ne);
+            t_sstorage->negcache.add(ne);
           }
         }
         negindic=true;
@@ -1360,7 +1417,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
              LOG(prefix<<qname<<": query handled by Lua"<<endl);
            }
            else {
-             ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP, d_incomingECSFound ? d_incomingECS : boost::none);
+             ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP);
               if(ednsmask) {
                 LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
               }
@@ -1537,6 +1594,36 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
   return -1;
 }
 
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem)
+{
+  boost::optional<Netmask> result;
+  ComboAddress trunc;
+  uint8_t bits;
+  if(d_incomingECSFound) {
+    if (d_incomingECS->source.getBits() == 0) {
+      /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
+      return result;
+    }
+    trunc = d_incomingECS->source.getMaskedNetwork();
+    bits = d_incomingECS->source.getBits();
+  }
+  else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
+    trunc = local;
+    bits = local.isIPv4() ? 32 : 128;
+  }
+  else {
+    /* nothing usable */
+    return result;
+  }
+
+  if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
+    bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit));
+    trunc.truncate(bits);
+    return boost::optional<Netmask>(Netmask(trunc, bits));
+  }
+
+  return result;
+}
 
 // used by PowerDNSLua - note that this neglects to add the packet count & statistics back to pdns_ercursor.cc
 int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<DNSRecord>& ret)
@@ -1549,3 +1636,46 @@ int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<D
   
   return res;
 }
+
+#include "validate-recursor.hh"
+
+int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
+  SyncRes sr(now);
+  sr.setDoEDNS0(true);
+  sr.setNoCache();
+  sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
+  sr.setAsyncCallback(asyncCallback);
+
+  vector<DNSRecord> ret;
+  int res=-1;
+  try {
+    res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
+    if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
+      ResolveContext ctx;
+      auto state = validateRecords(ctx, ret);
+      if (state == Bogus)
+        throw PDNSException("Got Bogus validation result for .|NS");
+    }
+    return res;
+  }
+  catch(PDNSException& e)
+  {
+    L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
+  }
+
+  catch(std::exception& e)
+  {
+    L<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
+  }
+
+  catch(...)
+  {
+    L<<Logger::Error<<"Failed to update . records, got an exception"<<endl;
+  }
+  if(!res) {
+    L<<Logger::Notice<<"Refreshed . records"<<endl;
+  }
+  else
+    L<<Logger::Error<<"Failed to update . records, RCODE="<<res<<endl;
+  return res;
+}
index 727c9adc30260bd31a13613d4a0013c0d1030b6a..e0d110178dd14f99aad83587ef86a24c0c8f6549 100644 (file)
@@ -19,8 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
-#ifndef PDNS_SYNCRES_HH
-#define PDNS_SYNCRES_HH
+#pragma once
 #include <string>
 #include <atomic>
 #include "utility.hh"
 #include "validate.hh"
 #include "ednssubnet.hh"
 #include "filterpo.hh"
+#include "negcache.hh"
 
+#ifdef HAVE_CONFIG_H
 #include "config.h"
+#endif
+
 #ifdef HAVE_PROTOBUF
 #include <boost/uuid/uuid.hpp>
 #include <boost/uuid/uuid_generators.hpp>
 #endif
 
 void primeHints(void);
-int getRootNS(void);
-class RecursorLua4;
-
-struct BothRecordsAndSignatures
-{
-  vector<DNSRecord> records;
-  vector<DNSRecord> signatures;
-};
-typedef map<pair<DNSName,uint16_t>, BothRecordsAndSignatures> recsig_t;
 
-struct NegCacheEntry
-{
-  DNSName d_name;
-  QType d_qtype;
-  DNSName d_qname;
-  uint32_t d_ttd;
-  uint32_t getTTD() const
-  {
-    return d_ttd;
-  }
-  recsig_t d_dnssecProof;
-};
+class RecursorLua4;
 
 typedef map<
   DNSName,
@@ -114,10 +97,11 @@ public:
     typename cont_t::iterator i=d_cont.find(t);
     if(i==d_cont.end())
       return false;
-    if(now > i->second.ttd || i->second.count-- < 0) {
+    if(now > i->second.ttd || i->second.count == 0) {
       d_cont.erase(i);
       return false;
     }
+    i->second.count--;
 
     return true; // still listed, still blocked
   }
@@ -133,10 +117,15 @@ public:
       d_cont[t]=e;
   }
 
-  unsigned int size()
+  unsigned int size() const
   {
     return (unsigned int)d_cont.size();
   }
+
+  void clear()
+  {
+    d_cont.clear();
+  }
 private:
   unsigned int d_limit;
   time_t d_ttl;
@@ -208,7 +197,7 @@ public:
     return d_val*=factor;
   }
 
-  double peek(void)
+  double peek(void) const
   {
     return d_val;
   }
@@ -231,9 +220,9 @@ public:
   Counters()
   {
   }
-  unsigned long value(const Thing& t)
+  unsigned long value(const Thing& t) const
   {
-    typename cont_t::iterator i=d_cont.find(t);
+    typename cont_t::const_iterator i=d_cont.find(t);
 
     if(i==d_cont.end()) {
       return 0;
@@ -272,7 +261,11 @@ public:
       d_cont.erase(i);
     }
   }
-  size_t size()
+  void clear()
+  {
+    d_cont.clear();
+  }
+  size_t size() const
   {
     return d_cont.size();
   }
@@ -289,6 +282,8 @@ public:
 
   explicit SyncRes(const struct timeval& now);
 
+  typedef std::function<int(const ComboAddress& ip, const DNSName& qdomain, int qtype, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult *lwr)> asyncresolve_t;
+
   int beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret);
   void setId(int id)
   {
@@ -324,6 +319,26 @@ public:
     d_doEDNS0=state;
   }
 
+  void setDoDNSSEC(bool state=true)
+  {
+    d_doDNSSEC=state;
+  }
+
+  void setWantsRPZ(bool state=true)
+  {
+    d_wantsRPZ=state;
+  }
+
+  bool getWantsRPZ() const
+  {
+    return d_wantsRPZ;
+  }
+
+  void setIncomingECSFound(bool state=true)
+  {
+    d_incomingECSFound=state;
+  }
+
   string getTrace() const
   {
     return d_trace.str();
@@ -344,14 +359,35 @@ public:
     return d_wasOutOfBand;
   }
 
+  struct timeval getNow() const
+  {
+    return d_now;
+  }
+
   void setSkipCNAMECheck(bool skip = false)
   {
     d_skipCNAMECheck = skip;
   }
 
-  int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res) const;
+  void setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS)
+  {
+    d_incomingECS = incomingECS;
+  }
+
+#ifdef HAVE_PROTOBUF
+  void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
+  {
+    d_initialRequestId = initialRequestId;
+  }
+#endif
+
+  void setAsyncCallback(asyncresolve_t func)
+  {
+    d_asyncResolve = func;
+  }
 
   static void doEDNSDumpAndClose(int fd);
+  static int getRootNS(struct timeval now, asyncresolve_t asyncCallback);
 
   static std::atomic<uint64_t> s_queries;
   static std::atomic<uint64_t> s_outgoingtimeouts;
@@ -370,10 +406,6 @@ public:
   static unsigned int s_maxdepth;
   std::unordered_map<std::string,bool> d_discardedPolicies;
   DNSFilterEngine::Policy d_appliedPolicy;
-  boost::optional<const EDNSSubnetOpts&> d_incomingECS;
-#ifdef HAVE_PROTOBUF
-  boost::optional<const boost::uuids::uuid&> d_initialRequestId;
-#endif
   unsigned int d_outqueries;
   unsigned int d_tcpoutqueries;
   unsigned int d_throttledqueries;
@@ -381,28 +413,6 @@ public:
   unsigned int d_unreachables;
   unsigned int d_totUsec;
   ComboAddress d_requestor;
-  bool d_doDNSSEC;
-  
-  bool d_wasVariable{false};
-  bool d_wasOutOfBand{false};
-  bool d_wantsRPZ{true};
-  bool d_skipCNAMECheck{false};
-  bool d_incomingECSFound{false};
-  
-  typedef multi_index_container <
-    NegCacheEntry,
-    indexed_by <
-       ordered_unique<
-           composite_key<
-                 NegCacheEntry,
-                    member<NegCacheEntry, DNSName, &NegCacheEntry::d_name>,
-                    member<NegCacheEntry, QType, &NegCacheEntry::d_qtype>
-           >,
-           composite_key_compare<CanonDNSNameCompare, std::less<QType> >
-       >,
-       sequenced<>
-    >
-  > negcache_t;
 
   //! This represents a number of decaying Ewmas, used to store performance per nameserver-name.
   /** Modelled to work mostly like the underlying DecayingEwma. After you've called get,
@@ -495,24 +505,25 @@ public:
 
   typedef Counters<ComboAddress> fails_t;
 
-  struct timeval d_now;
   static unsigned int s_maxnegttl;
   static unsigned int s_maxcachettl;
   static unsigned int s_packetcachettl;
   static unsigned int s_packetcacheservfailttl;
   static unsigned int s_serverdownmaxfails;
   static unsigned int s_serverdownthrottletime;
+  static uint8_t s_ecsipv4limit;
+  static uint8_t s_ecsipv6limit;
   static bool s_nopacketcache;
   static string s_serverID;
 
   struct StaticStorage {
-    negcache_t negcache;
     nsspeeds_t nsSpeeds;
     ednsstatus_t ednsstatus;
     throttle_t throttle;
     fails_t fails;
     domainmap_t* domainmap;
     map<DNSName, bool> dnssecmap;
+    NegCache negcache;
   };
 
 private:
@@ -539,13 +550,37 @@ private:
   RCode::rcodes_ updateCacheFromRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const DNSName& auth, NsSet& nameservers, const DNSName& tns, const boost::optional<Netmask>);
   bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, bool& sawDS);
 
-private:
+  bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t &qclass, vector<DNSRecord> &ret);
+
+  int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res) const;
+
+  boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
+
   ostringstream d_trace;
   shared_ptr<RecursorLua4> d_pdl;
+  boost::optional<const EDNSSubnetOpts&> d_incomingECS;
+#ifdef HAVE_PROTOBUF
+  boost::optional<const boost::uuids::uuid&> d_initialRequestId;
+#endif
+  asyncresolve_t d_asyncResolve{nullptr};
+  struct timeval d_now;
   string d_prefix;
+
+  /* When d_cacheonly is set to true, we will only check the cache.
+   * This is set when the RD bit is unset in the incoming query
+   */
   bool d_cacheonly;
+  /* d_nocache is *only* set in getRootNS() (in pdns_recursor.cc).
+   * It forces us to not look in the cache or local auth.
+   */
   bool d_nocache;
-  bool d_doEDNS0;
+  bool d_doDNSSEC;
+  bool d_doEDNS0{true};
+  bool d_incomingECSFound{false};
+  bool d_skipCNAMECheck{false};
+  bool d_wantsRPZ{true};
+  bool d_wasOutOfBand{false};
+  bool d_wasVariable{false};
 
   static LogMode s_lm;
   LogMode d_lm;
@@ -676,7 +711,7 @@ public:
   TCPConnection(int fd, const ComboAddress& addr);
   ~TCPConnection();
 
-  int getFD()
+  int getFD() const
   {
     return d_fd;
   }
@@ -744,7 +779,6 @@ uint64_t* pleaseWipeCache(const DNSName& canon, bool subtree=false);
 uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree);
 uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree=false);
 void doCarbonDump(void*);
-boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional<const EDNSSubnetOpts&> incomingECS);
 void  parseEDNSSubnetWhitelist(const std::string& wlist);
 
 extern __thread struct timeval g_now;
@@ -755,5 +789,3 @@ extern SuffixMatchNode g_ednsdomains;
 #ifdef HAVE_PROTOBUF
 extern __thread boost::uuids::random_generator* t_uuidGenerator;
 #endif
-
-#endif
index 03cf47ffe1510eb22537330ed8bab573e3d3ef56..e2540935a257ee7c7d6e85b8d57fc80f58fc5a4f 100644 (file)
@@ -23,7 +23,7 @@
 #include "config.h"
 #endif
 #include <boost/algorithm/string.hpp>
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
 #include "utility.hh"
 #include "dnssecinfra.hh"
 #include "dnsseckeeper.hh"
@@ -32,6 +32,7 @@
 #include <cstring>
 #include <cstdlib>
 #include <sys/types.h>
+#include <netinet/tcp.h>
 #include <iostream>
 #include <string>
 #include "tcpreceiver.hh"
@@ -56,7 +57,7 @@
 #include "namespaces.hh"
 #include "signingpipe.hh"
 #include "stubresolver.hh"
-extern PacketCache PC;
+extern AuthPacketCache PC;
 extern StatBag S;
 
 /**
@@ -808,6 +809,11 @@ int TCPNameserver::doAXFR(const DNSName &target, shared_ptr<DNSPacket> q, int ou
     }
   }
 
+  // Group records by name and type, signpipe stumbles over interrupted rrsets
+  sort(zrrs.begin(), zrrs.end(), [](const DNSZoneRecord& a, const DNSZoneRecord& b) {
+    return tie(a.dr.d_name, a.dr.d_type) < tie(b.dr.d_name, b.dr.d_type);
+  });
+
   if(rectify) {
     // set auth
     for(DNSZoneRecord &zrr :  zrrs) {
@@ -1233,7 +1239,18 @@ TCPNameserver::TCPNameserver()
       L<<Logger::Error<<"Setsockopt failed"<<endl;
       exit(1);  
     }
-    
+
+    if (::arg().asNum("tcp-fast-open") > 0) {
+#ifdef TCP_FASTOPEN
+      int fastOpenQueueSize = ::arg().asNum("tcp-fast-open");
+      if (setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &fastOpenQueueSize, sizeof fastOpenQueueSize) < 0) {
+        L<<Logger::Error<<"Failed to enable TCP Fast Open for listening socket: "<<strerror(errno)<<endl;
+      }
+#else
+      L<<Logger::Warning<<"TCP Fast Open configured but not supported for listening socket"<<endl;
+#endif
+    }
+
     if( ::arg().mustDo("non-local-bind") )
        Utility::setBindAny(AF_INET, s);
 
@@ -1274,6 +1291,18 @@ TCPNameserver::TCPNameserver()
       L<<Logger::Error<<"Setsockopt failed"<<endl;
       exit(1);  
     }
+
+    if (::arg().asNum("tcp-fast-open") > 0) {
+#ifdef TCP_FASTOPEN
+      int fastOpenQueueSize = ::arg().asNum("tcp-fast-open");
+      if (setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &fastOpenQueueSize, sizeof fastOpenQueueSize) < 0) {
+        L<<Logger::Error<<"Failed to enable TCP Fast Open for listening socket: "<<strerror(errno)<<endl;
+      }
+#else
+      L<<Logger::Warning<<"TCP Fast Open configured but not supported for listening socket"<<endl;
+#endif
+    }
+
     if( ::arg().mustDo("non-local-bind") )
        Utility::setBindAny(AF_INET6, s);
     if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &tmp, sizeof(tmp)) < 0) {
index bb829614e91660c7cf0886e22212199a3c2f2134..771bfb84bd448a320a5f8cb425ed5e1394bbd8e6 100644 (file)
@@ -858,4 +858,14 @@ BOOST_AUTO_TEST_CASE(test_getrawlabel) {
   BOOST_CHECK_THROW(name.getRawLabel(name.countLabels()), std::out_of_range);
 }
 
+BOOST_AUTO_TEST_CASE(test_getlastlabel) {
+  DNSName name("www.powerdns.com");
+  DNSName ans = name.getLastLabel();
+
+  // Check the const-ness
+  BOOST_CHECK_EQUAL(name, DNSName("www.powerdns.com"));
+
+  // Check if the last label is indeed returned
+  BOOST_CHECK_EQUAL(ans, DNSName("com"));
+}
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/pdns/test-lock_hh.cc b/pdns/test-lock_hh.cc
new file mode 100644 (file)
index 0000000..707dcc1
--- /dev/null
@@ -0,0 +1,54 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#include "lock.hh"
+#include <thread>
+
+using namespace boost;
+
+BOOST_AUTO_TEST_SUITE(test_lock_hh)
+
+static std::vector<std::unique_ptr<pthread_rwlock_t> > g_locks;
+
+static void lthread()
+{
+  std::vector<ReadLock> rlocks;
+  for(auto& pp : g_locks)
+    rlocks.emplace_back(&*pp);
+  
+}
+
+BOOST_AUTO_TEST_CASE(test_pdns_lock)
+{
+  for(unsigned int n=0; n < 1000; ++n) {
+    auto p = new pthread_rwlock_t;
+    pthread_rwlock_init(p, 0);
+    g_locks.emplace_back(p);
+  }
+
+  std::vector<ReadLock> rlocks;
+  for(auto& pp : g_locks)
+    rlocks.emplace_back(&*pp);
+
+  std::thread thr(lthread);
+  thr.join();
+  rlocks.clear();
+
+  std::vector<WriteLock> wlocks;
+  for(auto& pp : g_locks)
+    wlocks.emplace_back(&*pp);
+
+  TryReadLock trl(&*g_locks[0]);
+  BOOST_CHECK(!trl.gotIt());
+
+  wlocks.clear();
+  TryReadLock trl2(&*g_locks[0]);
+  BOOST_CHECK(trl2.gotIt());
+  
+  
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index ca245f6a2694ebcdd95157ff26ad033ce6fee5cf..c3868a6fc9a8b9a73a8ad003a189b0cb2c0742ac 100644 (file)
@@ -9,63 +9,58 @@
 #include "iputils.hh"
 #include "nameserver.hh"
 #include "statbag.hh"
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
 #include "arguments.hh"
 #include <utility>
 extern StatBag S;
 
 BOOST_AUTO_TEST_SUITE(packetcache_cc)
 
-BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
-  PacketCache PC;
+BOOST_AUTO_TEST_CASE(test_AuthQueryCacheSimple) {
+  AuthQueryCache QC;
+  QC.setMaxEntries(1000000);
 
-  ::arg().set("max-cache-entries", "Maximum number of cache entries")="1000000";
-  ::arg().set("cache-ttl","Seconds to store packets in the PacketCache")="20";
-  ::arg().set("negquery-cache-ttl","Seconds to store negative query results in the QueryCache")="60";
-  ::arg().set("query-cache-ttl","Seconds to store query results in the QueryCache")="20";
+  vector<DNSZoneRecord> records;
 
-  S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance");
-  S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance");
+  BOOST_CHECK_EQUAL(QC.size(), 0);
+  QC.insert(DNSName("hello"), QType(QType::A), records, 3600, 1);
+  BOOST_CHECK_EQUAL(QC.size(), 1);
+  BOOST_CHECK_EQUAL(QC.purge(), 1);
+  BOOST_CHECK_EQUAL(QC.size(), 0);
 
-
-  BOOST_CHECK_EQUAL(PC.size(), 0);
-  PC.insert(DNSName("hello"), QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
-  BOOST_CHECK_EQUAL(PC.size(), 1);
-  PC.purge();
-  BOOST_CHECK_EQUAL(PC.size(), 0);
-
-  int counter=0;
+  uint64_t counter=0;
   try {
     for(counter = 0; counter < 100000; ++counter) {
       DNSName a=DNSName("hello ")+DNSName(std::to_string(counter));
       BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
 
-      PC.insert(a, QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
-      if(!PC.purge(a.toString()))
-       BOOST_FAIL("Could not remove entry we just added to packet cache!");
-      PC.insert(a, QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);
+      QC.insert(a, QType(QType::A), records, 3600, 1);
+      if(!QC.purge(a.toString()))
+       BOOST_FAIL("Could not remove entry we just added to the query cache!");
+      QC.insert(a, QType(QType::A), records, 3600, 1);
     }
 
-    BOOST_CHECK_EQUAL(PC.size(), counter);
-    
-    int delcounter=0;
+    BOOST_CHECK_EQUAL(QC.size(), counter);
+
+    uint64_t delcounter=0;
     for(delcounter=0; delcounter < counter/100; ++delcounter) {
       DNSName a=DNSName("hello ")+DNSName(std::to_string(delcounter));
-      PC.purge(a.toString());
+      BOOST_CHECK_EQUAL(QC.purge(a.toString()), 1);
     }
-    
-    BOOST_CHECK_EQUAL(PC.size(), counter-delcounter);
-    
-    int matches=0;
+
+    BOOST_CHECK_EQUAL(QC.size(), counter-delcounter);
+
+    uint64_t matches=0;
     vector<DNSZoneRecord> entry;
-    int expected=counter-delcounter;
+    int64_t expected=counter-delcounter;
     for(; delcounter < counter; ++delcounter) {
-      if(PC.getEntry(DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), PacketCache::QUERYCACHE, entry, 1)) {
+      if(QC.getEntry(DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), entry, 1)) {
        matches++;
       }
     }
     BOOST_CHECK_EQUAL(matches, expected);
-    //    BOOST_CHECK_EQUAL(entry, "something");
+    BOOST_CHECK_EQUAL(entry.size(), records.size());
   }
   catch(PDNSException& e) {
     cerr<<"Had error: "<<e.reason<<endl;
@@ -74,14 +69,16 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
 
 }
 
-static PacketCache* g_PC;
+static AuthQueryCache* g_QC;
+static AtomicCounter g_QCmissing;
 
-static void *threadMangler(void* a)
+static void *threadQCMangler(void* a)
 try
 {
+  vector<DNSZoneRecord> records;
   unsigned int offset=(unsigned int)(unsigned long)a;
   for(unsigned int counter=0; counter < 100000; ++counter)
-    g_PC->insert(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), PacketCache::QUERYCACHE, "something", 3600, 1);    
+    g_QC->insert(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), records, 3600, 1);
   return 0;
 }
  catch(PDNSException& e) {
@@ -89,53 +86,152 @@ try
    throw;
  }
 
-AtomicCounter g_missing;
-
-static void *threadReader(void* a)
+static void *threadQCReader(void* a)
 try
 {
   unsigned int offset=(unsigned int)(unsigned long)a;
   vector<DNSZoneRecord> entry;
   for(unsigned int counter=0; counter < 100000; ++counter)
-    if(!g_PC->getEntry(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), PacketCache::QUERYCACHE, entry, 1)) {
-       g_missing++;
+    if(!g_QC->getEntry(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), entry, 1)) {
+      g_QCmissing++;
     }
   return 0;
 }
 catch(PDNSException& e) {
-  cerr<<"Had error in threadReader: "<<e.reason<<endl;
+  cerr<<"Had error in threadQCReader: "<<e.reason<<endl;
   throw;
 }
 
+BOOST_AUTO_TEST_CASE(test_QueryCacheThreaded) {
+  try {
+    AuthQueryCache QC;
+    QC.setMaxEntries(1000000);
+    g_QC=&QC;
+    pthread_t tid[4];
+    for(int i=0; i < 4; ++i)
+      pthread_create(&tid[i], 0, threadQCMangler, (void*)(i*1000000UL));
+    void* res;
+    for(int i=0; i < 4 ; ++i)
+      pthread_join(tid[i], &res);
+
+    BOOST_CHECK_EQUAL(QC.size() + S.read("deferred-cache-inserts"), 400000);
+    BOOST_CHECK_SMALL(1.0*S.read("deferred-cache-inserts"), 10000.0);
+
+    for(int i=0; i < 4; ++i)
+      pthread_create(&tid[i], 0, threadQCReader, (void*)(i*1000000UL));
+    for(int i=0; i < 4 ; ++i)
+      pthread_join(tid[i], &res);
+
+    BOOST_CHECK(S.read("deferred-cache-inserts") + S.read("deferred-cache-lookup") >= g_QCmissing);
+    //    BOOST_CHECK_EQUAL(S.read("deferred-cache-lookup"), 0); // cache cleaning invalidates this
+  }
+  catch(PDNSException& e) {
+    cerr<<"Had error: "<<e.reason<<endl;
+    throw;
+  }
+
+}
+
+static AuthPacketCache* g_PC;
+static AtomicCounter g_PCmissing;
+
+static void *threadPCMangler(void* a)
+try
+{
+  unsigned int offset=(unsigned int)(unsigned long)a;
+  for(unsigned int counter=0; counter < 100000; ++counter) {
+    vector<uint8_t> pak;
+    DNSName qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
+
+    DNSPacketWriter pw(pak, qname, QType::A);
+    DNSPacket q(true);
+    q.parse((char*)&pak[0], pak.size());
+
+    pak.clear();
+    DNSPacketWriter pw2(pak, qname, QType::A);
+    pw2.startRecord(qname, QType::A, 16, 1, DNSResourceRecord::ANSWER);
+    pw2.xfrIP(htonl(0x7f000001));
+    pw2.commit();
+
+    DNSPacket r(false);
+    r.parse((char*)&pak[0], pak.size());
+
+    /* this step is necessary to get a valid hash */
+    DNSPacket cached(false);
+    g_PC->get(&q, &cached);
+
+    g_PC->insert(&q, &r, 10);
+  }
+
+  return 0;
+}
+ catch(PDNSException& e) {
+   cerr<<"Had error: "<<e.reason<<endl;
+   throw;
+ }
+
+static void *threadPCReader(void* a)
+try
+{
+  unsigned int offset=(unsigned int)(unsigned long)a;
+  vector<DNSZoneRecord> entry;
+  for(unsigned int counter=0; counter < 100000; ++counter) {
+    vector<uint8_t> pak;
+    DNSName qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
+
+    DNSPacketWriter pw(pak, qname, QType::A);
+    DNSPacket q(true);
+    q.parse((char*)&pak[0], pak.size());
+    DNSPacket r(false);
+
+    if(!g_PC->get(&q, &r)) {
+      g_PCmissing++;
+    }
+  }
 
+  return 0;
+}
+catch(PDNSException& e) {
+  cerr<<"Had error in threadPCReader: "<<e.reason<<endl;
+  throw;
+}
 
 BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
   try {
-    PacketCache PC;
+    AuthPacketCache PC;
+    PC.setMaxEntries(1000000);
+    PC.setTTL(20);
+
     g_PC=&PC;
     pthread_t tid[4];
-    for(int i=0; i < 4; ++i) 
-      pthread_create(&tid[i], 0, threadMangler, (void*)(i*1000000UL));
+    for(int i=0; i < 4; ++i)
+      pthread_create(&tid[i], 0, threadPCMangler, (void*)(i*1000000UL));
     void* res;
     for(int i=0; i < 4 ; ++i)
       pthread_join(tid[i], &res);
-    
-    BOOST_CHECK_EQUAL(PC.size() + S.read("deferred-cache-inserts"), 400000);
-    BOOST_CHECK_SMALL(1.0*S.read("deferred-cache-inserts"), 10000.0);
 
-    for(int i=0; i < 4; ++i) 
-      pthread_create(&tid[i], 0, threadReader, (void*)(i*1000000UL));
+    BOOST_CHECK_EQUAL(PC.size() + S.read("deferred-packetcache-inserts"), 400000);
+    BOOST_CHECK_SMALL(1.0*S.read("deferred-packetcache-inserts"), 10000.0);
+
+    for(int i=0; i < 4; ++i)
+      pthread_create(&tid[i], 0, threadPCReader, (void*)(i*1000000UL));
     for(int i=0; i < 4 ; ++i)
       pthread_join(tid[i], &res);
 
-    BOOST_CHECK(S.read("deferred-cache-inserts") + S.read("deferred-cache-lookup") >= g_missing);
-    //    BOOST_CHECK_EQUAL(S.read("deferred-cache-lookup"), 0); // cache cleaning invalidates this
+/*
+    cerr<<"Misses: "<<S.read("packetcache-miss")<<endl;
+    cerr<<"Hits: "<<S.read("packetcache-hit")<<endl;
+    cerr<<"Deferred inserts: "<<S.read("deferred-packetcache-inserts")<<endl;
+    cerr<<"Deferred lookups: "<<S.read("deferred-packetcache-lookup")<<endl;
+*/
+    BOOST_CHECK_EQUAL(g_PCmissing + S.read("packetcache-hit"), 400000);
+    BOOST_CHECK_GT(S.read("deferred-packetcache-inserts") + S.read("deferred-packetcache-lookup"), g_PCmissing);
   }
   catch(PDNSException& e) {
     cerr<<"Had error: "<<e.reason<<endl;
     throw;
   }
-  
+
 }
 
 bool g_stopCleaning;
@@ -143,34 +239,34 @@ static void *cacheCleaner(void*)
 try
 {
   while(!g_stopCleaning) {
-    g_PC->cleanup();
+    g_QC->cleanup();
   }
 
   return 0;
 }
 catch(PDNSException& e) {
-  cerr<<"Had error in threadReader: "<<e.reason<<endl;
+  cerr<<"Had error in cacheCleaner: "<<e.reason<<endl;
   throw;
 }
 
-BOOST_AUTO_TEST_CASE(test_PacketCacheClean) {
+BOOST_AUTO_TEST_CASE(test_QueryCacheClean) {
   try {
-    PacketCache PC;
+    AuthQueryCache QC;
+    QC.setMaxEntries(10000);
+    vector<DNSZoneRecord> records;
 
     for(unsigned int counter = 0; counter < 1000000; ++counter) {
-      PC.insert(DNSName("hello ")+DNSName(std::to_string(counter)), QType(QType::A), PacketCache::QUERYCACHE, "something", 1, 1);
+      QC.insert(DNSName("hello ")+DNSName(std::to_string(counter)), QType(QType::A), records, 1, 1);
     }
 
     sleep(1);
-    
-    g_PC=&PC;
-    pthread_t tid[4];
 
-    ::arg().set("max-cache-entries")="10000";
+    g_QC=&QC;
+    pthread_t tid[4];
 
-    pthread_create(&tid[0], 0, threadReader, (void*)(0*1000000UL));
-    pthread_create(&tid[1], 0, threadReader, (void*)(1*1000000UL));
-    pthread_create(&tid[2], 0, threadReader, (void*)(2*1000000UL));
+    pthread_create(&tid[0], 0, threadQCReader, (void*)(0*1000000UL));
+    pthread_create(&tid[1], 0, threadQCReader, (void*)(1*1000000UL));
+    pthread_create(&tid[2], 0, threadQCReader, (void*)(2*1000000UL));
     //    pthread_create(&tid[2], 0, threadMangler, (void*)(0*1000000UL));
     pthread_create(&tid[3], 0, cacheCleaner, 0);
 
@@ -181,71 +277,187 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheClean) {
     pthread_join(tid[3], &res);
   }
   catch(PDNSException& e) {
-    cerr<<"Had error in threadReader: "<<e.reason<<endl;
+    cerr<<"Had error in test_QueryCacheClean: "<<e.reason<<endl;
     throw;
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_PacketCachePacket) {
+BOOST_AUTO_TEST_CASE(test_AuthPacketCache) {
   try {
     ::arg().setSwitch("no-shuffle","Set this to prevent random shuffling of answers - for regression testing")="off";
 
-    PacketCache PC;
+    AuthPacketCache PC;
+    PC.setTTL(20);
+    PC.setMaxEntries(100000);
+
     vector<uint8_t> pak;
-    vector<pair<uint16_t,string > > opts;
+    DNSPacket q(true), differentIDQ(true), ednsQ(true), ednsVersion42(true), ednsDO(true), ecs1(true), ecs2(true), ecs3(true);
+    DNSPacket r(false), r2(false);
 
-    DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
-    DNSPacket q(true), r(false), r2(false);
-    q.parse((char*)&pak[0], pak.size());
+    {
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      q.parse((char*)&pak[0], pak.size());
 
-    pak.clear();
-    DNSPacketWriter pw2(pak, DNSName("www.powerdns.com"), QType::A);
-    pw2.startRecord(DNSName("www.powerdns.com"), QType::A, 16, 1, DNSResourceRecord::ANSWER);
-    pw2.xfrIP(htonl(0x7f000001));
-    pw2.commit();
+      differentIDQ.parse((char*)&pak[0], pak.size());
+      differentIDQ.setID(4242);
 
-    r.parse((char*)&pak[0], pak.size());
+      pw.addOpt(512, 0, 0);
+      pw.commit();
+      ednsQ.parse((char*)&pak[0], pak.size());
+
+      pak.clear();
+    }
+
+    DNSPacketWriter::optvect_t opts;
+    EDNSSubnetOpts ecsOpts;
+    {
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      pw.addOpt(512, 0, 0, DNSPacketWriter::optvect_t(), 42);
+      pw.commit();
+      ednsVersion42.parse((char*)&pak[0], pak.size());
+      pak.clear();
+    }
+
+    {
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      pw.addOpt(512, 0, EDNSOpts::DNSSECOK);
+      pw.commit();
+      ednsDO.parse((char*)&pak[0], pak.size());
+      pak.clear();
+    }
+
+    {
+      ecsOpts.source = Netmask(ComboAddress("192.0.2.1"), 32);
+      opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(ecsOpts)));
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      pw.addOpt(512, 0, 0, opts);
+      pw.commit();
+      ecs1.parse((char*)&pak[0], pak.size());
+      pak.clear();
+      opts.clear();
+    }
+
+    {
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      ecsOpts.source = Netmask(ComboAddress("192.0.2.2"), 32);
+      opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(ecsOpts)));
+      pw.addOpt(512, 0, 0, opts);
+      pw.commit();
+      ecs2.parse((char*)&pak[0], pak.size());
+      pak.clear();
+      opts.clear();
+    }
+
+    {
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      ecsOpts.source = Netmask(ComboAddress("192.0.2.3"), 16);
+      opts.push_back(make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(ecsOpts)));
+      pw.addOpt(512, 0, 0, opts);
+      pw.commit();
+      ecs3.parse((char*)&pak[0], pak.size());
+      pak.clear();
+      opts.clear();
+    }
+
+    {
+      DNSPacketWriter pw(pak, DNSName("www.powerdns.com"), QType::A);
+      pw.startRecord(DNSName("www.powerdns.com"), QType::A, 16, 1, DNSResourceRecord::ANSWER);
+      pw.xfrIP(htonl(0x7f000001));
+      pw.commit();
+
+      r.parse((char*)&pak[0], pak.size());
+    }
+
+    /* this call is required so the correct hash is set into q->d_hash */
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
 
     PC.insert(&q, &r, 3600);
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
+    BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
+
+    /* different QID, still should match */
+    BOOST_CHECK_EQUAL(PC.get(&differentIDQ, &r2), true);
+    BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
+
+    /* with EDNS, should not match */
+    BOOST_CHECK_EQUAL(PC.get(&ednsQ, &r2), false);
+    /* inserting the EDNS-enabled one too */
+    PC.insert(&ednsQ, &r, 3600);
+    BOOST_CHECK_EQUAL(PC.size(), 2);
+
+    /* different EDNS versions, should not match */
+    BOOST_CHECK_EQUAL(PC.get(&ednsVersion42, &r2), false);
+
+    /* EDNS DO set, should not match */
+    BOOST_CHECK_EQUAL(PC.get(&ednsDO, &r2), false);
 
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+    /* EDNS Client Subnet set, should not match
+       since not only we don't skip the actual option, but the
+       total EDNS opt RR is still different. */
+    BOOST_CHECK_EQUAL(PC.get(&ecs1, &r2), false);
+
+    /* inserting the version with ECS Client Subnet set,
+     it should NOT replace the existing EDNS one. */
+    PC.insert(&ecs1, &r, 3600);
+    BOOST_CHECK_EQUAL(PC.size(), 3);
+
+    /* different subnet of same size, should NOT match
+     since we don't skip the option */
+    BOOST_CHECK_EQUAL(PC.get(&ecs2, &r2), false);
     BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
 
-    PC.purge("www.powerdns.com");
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+    /* different subnet of different size, should NOT match. */
+    BOOST_CHECK_EQUAL(PC.get(&ecs3, &r2), false);
+
+    BOOST_CHECK_EQUAL(PC.purge("www.powerdns.com"), 3);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+    BOOST_CHECK_EQUAL(PC.size(), 0);
 
     PC.insert(&q, &r, 3600);
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
     BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
-    PC.purge("com$");
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+    BOOST_CHECK_EQUAL(PC.purge("com$"), 1);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+    BOOST_CHECK_EQUAL(PC.size(), 0);
 
     PC.insert(&q, &r, 3600);
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
     BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
-    PC.purge("powerdns.com$");
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+    BOOST_CHECK_EQUAL(PC.purge("powerdns.com$"), 1);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+    BOOST_CHECK_EQUAL(PC.size(), 0);
 
     PC.insert(&q, &r, 3600);
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
     BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
-    PC.purge("www.powerdns.com$");
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 0);
+    BOOST_CHECK_EQUAL(PC.purge("www.powerdns.com$"), 1);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), false);
+    BOOST_CHECK_EQUAL(PC.size(), 0);
 
     PC.insert(&q, &r, 3600);
-    PC.purge("www.powerdns.net");
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+    BOOST_CHECK_EQUAL(PC.purge("www.powerdns.net"), 0);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
     BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
-    PC.purge("net$");
-    BOOST_CHECK_EQUAL(PC.get(&q, &r2), 1);
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+
+    BOOST_CHECK_EQUAL(PC.purge("net$"), 0);
+    BOOST_CHECK_EQUAL(PC.get(&q, &r2), true);
     BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
-    PC.purge("www.powerdns.com$");
+    BOOST_CHECK_EQUAL(PC.size(), 1);
+
+    BOOST_CHECK_EQUAL(PC.purge("www.powerdns.com$"), 1);
     BOOST_CHECK_EQUAL(PC.size(), 0);
   }
   catch(PDNSException& e) {
-    cerr<<"Had error in threadReader: "<<e.reason<<endl;
+    cerr<<"Had error in AuthPacketCache: "<<e.reason<<endl;
     throw;
   }
-} 
+}
 
 BOOST_AUTO_TEST_SUITE_END()
index f4b492b66652531adc727b07d8abd0317f5ebbd4..5b1c084633b18229d7955ef15b22a00e59a9d1f3 100644 (file)
@@ -18,7 +18,7 @@ BOOST_AUTO_TEST_SUITE(recpacketcache_cc)
 BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple) {
   RecursorPacketCache rpc;
   string fpacket;
-  int tag=0;
+  unsigned int tag=0;
   uint32_t age=0;
   uint32_t qhash=0;
   uint32_t ttd=3600;
@@ -77,6 +77,123 @@ BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple) {
 
   rpc.doWipePacketCache(DNSName("com"), 0xffff, true);
   BOOST_CHECK_EQUAL(rpc.size(), 0);
-} 
+}
+
+BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
+  /* Insert a response with tag1, the exact same query with a different tag
+     should lead to a miss. Inserting a different response with the second tag
+     should not override the first one, and we should get a hit for the
+     query with either tags, with the response matching the tag.
+  */
+  RecursorPacketCache rpc;
+  string fpacket;
+  const unsigned int tag1=0;
+  const unsigned int tag2=42;
+  uint32_t age=0;
+  uint32_t qhash=0;
+  uint32_t temphash=0;
+  uint32_t ttd=3600;
+  BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+  DNSName qname("www.powerdns.com");
+  vector<uint8_t> packet;
+  DNSPacketWriter pw(packet, qname, QType::A);
+  pw.getHeader()->rd=true;
+  pw.getHeader()->qr=false;
+  pw.getHeader()->id=random();
+  string qpacket(reinterpret_cast<const char*>(&packet[0]), packet.size());
+  pw.startRecord(qname, QType::A, ttd);
+
+  /* Both interfaces (with and without the qname/qtype/qclass) should get the same hash */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+
+  /* Different tag, should still get get the same hash, for both interfaces */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+
+  {
+    ARecordContent ar("127.0.0.1");
+    ar.toPacket(pw);
+    pw.commit();
+  }
+  string r1packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+  {
+    ARecordContent ar("127.0.0.2");
+    ar.toPacket(pw);
+    pw.commit();
+  }
+  string r2packet(reinterpret_cast<const char*>(&packet[0]), packet.size());
+
+  BOOST_CHECK(r1packet != r2packet);
+
+  /* inserting a response for tag1 */
+  rpc.insertResponsePacket(tag1, qhash, qname, QType::A, QClass::IN, r1packet, time(0), ttd);
+  BOOST_CHECK_EQUAL(rpc.size(), 1);
+
+  /* inserting a different response for tag2, should not override the first one */
+  rpc.insertResponsePacket(tag2, qhash, qname, QType::A, QClass::IN, r2packet, time(0), ttd);
+  BOOST_CHECK_EQUAL(rpc.size(), 2);
+
+  /* remove all responses from the cache */
+  rpc.doPruneTo(0);
+  BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+  /* reinsert both */
+  rpc.insertResponsePacket(tag1, qhash, qname, QType::A, QClass::IN, r1packet, time(0), ttd);
+  BOOST_CHECK_EQUAL(rpc.size(), 1);
+
+  rpc.insertResponsePacket(tag2, qhash, qname, QType::A, QClass::IN, r2packet, time(0), ttd);
+  BOOST_CHECK_EQUAL(rpc.size(), 2);
+
+  /* remove the responses by qname, should remove both */
+  rpc.doWipePacketCache(qname);
+  BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+  /* insert the response for tag1 */
+  rpc.insertResponsePacket(tag1, qhash, qname, QType::A, QClass::IN, r1packet, time(0), ttd);
+  BOOST_CHECK_EQUAL(rpc.size(), 1);
+
+  /* we can retrieve it */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* with both interfaces */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* but not with the second tag */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), false);
+  /* we should still get the same hash */
+  BOOST_CHECK_EQUAL(temphash, qhash);
+
+  /* adding a response for the second tag */
+  rpc.insertResponsePacket(tag2, qhash, qname, QType::A, QClass::IN, r2packet, time(0), ttd);
+  BOOST_CHECK_EQUAL(rpc.size(), 2);
+
+  /* We still get the correct response for the first tag */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag1, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r1packet);
+
+  /* and the correct response for the second tag */
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r2packet);
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag2, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &temphash), true);
+  BOOST_CHECK_EQUAL(qhash, temphash);
+  BOOST_CHECK_EQUAL(fpacket, r2packet);
+}
 
 BOOST_AUTO_TEST_SUITE_END()
index f45de57a0325496098c1f7f7f8f577b252228bd4..d923d52d1ea001cfcfb71d3cd38573bc34b6fbbb 100644 (file)
@@ -7,7 +7,6 @@
 #include <boost/assign/list_of.hpp>
 
 #include <boost/tuple/tuple.hpp>
-#include <boost/iostreams/stream.hpp>
 #include <boost/iostreams/device/file.hpp>
 #include "dns.hh"
 #include "zoneparser-tng.hh"
index b3ff81535e999fb1f1a7dc8ba4d6654670f87306..b4e55adbf58141c8e5c0f22d04701ef1ec2814fa 100644 (file)
@@ -6,8 +6,10 @@
 #include "config.h"
 #endif
 #include <boost/test/unit_test.hpp>
-#include "packetcache.hh"
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
+#include "statbag.hh"
 StatBag S;
-PacketCache PC;
-
+AuthPacketCache PC;
+AuthQueryCache QC;
 
index 4a7a55783c7aaa8dc9f8d5e1c974abc97d3500be..258c85dc28d4622c275cbea54e2585932f654f44 100644 (file)
@@ -25,7 +25,7 @@
 #include <boost/archive/binary_iarchive.hpp>
 #include <boost/archive/binary_oarchive.hpp>
 
-#include "packetcache.hh"
+#include "auth-querycache.hh"
 #include "utility.hh"
 
 
@@ -444,30 +444,22 @@ void UeberBackend::cleanup()
   for_each(backends.begin(),backends.end(),del);
 }
 
-// silly Solaris fix
-#undef PC
-
 // returns -1 for miss, 0 for negative match, 1 for hit
 int UeberBackend::cacheHas(const Question &q, vector<DNSZoneRecord> &rrs)
 {
-  extern PacketCache PC;
-  static AtomicCounter *qcachehit=S.getPointer("query-cache-hit");
-  static AtomicCounter *qcachemiss=S.getPointer("query-cache-miss");
+  extern AuthQueryCache QC;
 
   if(!d_cache_ttl && ! d_negcache_ttl) {
-    (*qcachemiss)++;
     return -1;
   }
 
   rrs.clear();
   //  L<<Logger::Warning<<"looking up: '"<<q.qname+"'|N|"+q.qtype.getName()+"|"+itoa(q.zoneId)<<endl;
 
-  bool ret=PC.getEntry(q.qname, q.qtype, PacketCache::QUERYCACHE, rrs, q.zoneId);   // think about lowercasing here
+  bool ret=QC.getEntry(q.qname, q.qtype, rrs, q.zoneId);   // think about lowercasing here
   if(!ret) {
-    (*qcachemiss)++;
     return -1;
   }
-  (*qcachehit)++;
   if(rrs.empty()) // negatively cached
     return 0;
   
@@ -476,16 +468,16 @@ int UeberBackend::cacheHas(const Question &q, vector<DNSZoneRecord> &rrs)
 
 void UeberBackend::addNegCache(const Question &q)
 {
-  extern PacketCache PC;
+  extern AuthQueryCache QC;
   if(!d_negcache_ttl)
     return;
   // we should also not be storing negative answers if a pipebackend does scopeMask, but we can't pass a negative scopeMask in an empty set!
-  PC.insert(q.qname, q.qtype, PacketCache::QUERYCACHE, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
+  QC.insert(q.qname, q.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
 }
 
 void UeberBackend::addCache(const Question &q, const vector<DNSZoneRecord> &rrs)
 {
-  extern PacketCache PC;
+  extern AuthQueryCache QC;
 
   if(!d_cache_ttl)
     return;
@@ -498,7 +490,7 @@ void UeberBackend::addCache(const Question &q, const vector<DNSZoneRecord> &rrs)
      return;
   }
 
-  PC.insert(q.qname, q.qtype, PacketCache::QUERYCACHE, rrs, store_ttl, q.zoneId);
+  QC.insert(q.qname, q.qtype, rrs, store_ttl, q.zoneId);
 }
 
 void UeberBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
index f6c9372e460593122ff2919a6a134f2e1e2dae3a..1af29ec0c08aadc9d5a0d5017ca99e399ff15e42 100644 (file)
@@ -6,6 +6,8 @@
 DNSSECMode g_dnssecmode{DNSSECMode::ProcessNoValidate};
 bool g_dnssecLogBogus;
 
+extern int getMTaskerTID();
+
 #define LOG(x) if(g_dnssecLOG) { L <<Logger::Warning << x; }
 
 class SRRecordOracle : public DNSRecordOracle
@@ -19,13 +21,13 @@ public:
     struct timeval tv;
     gettimeofday(&tv, 0);
     SyncRes sr(tv);
-    sr.setId(MT->getTid());
+    sr.setId(getMTaskerTID());
 #ifdef HAVE_PROTOBUF
-    sr.d_initialRequestId = d_ctx.d_initialRequestId;
+    sr.setInitialRequestId(d_ctx.d_initialRequestId);
 #endif
 
     vector<DNSRecord> ret;
-    sr.d_doDNSSEC=true;
+    sr.setDoDNSSEC(true);
     if (qtype == QType::DS || qtype == QType::DNSKEY || qtype == QType::NS)
       sr.setSkipCNAMECheck(true);
     sr.beginResolve(qname, QType(qtype), 1, ret);
index 667e964faf20cc7348d2eb478e4c48117b4ef6ef..30507299449547c0abf1424d7b542c42097e4519 100644 (file)
@@ -28,7 +28,6 @@
 #include "json.hh"
 #include "webserver.hh"
 #include "logger.hh"
-#include "packetcache.hh"
 #include "statbag.hh"
 #include "misc.hh"
 #include "arguments.hh"
 #include <iomanip>
 #include "zoneparser-tng.hh"
 #include "common_startup.hh"
-
+#include "auth-caches.hh"
 
 using json11::Json;
 
 extern StatBag S;
-extern PacketCache PC;
 
 static void patchZone(HttpRequest* req, HttpResponse* resp);
 static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs);
@@ -1352,7 +1350,7 @@ static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptr
     }
 
     sd.db->commitTransaction();
-    PC.purgeExact(rr.qname);
+    purgeAuthCachesExact(rr.qname);
   }
 }
 
@@ -1479,7 +1477,7 @@ static void patchZone(HttpRequest* req, HttpResponse* resp) {
   }
   di.backend->commitTransaction();
 
-  PC.purgeExact(zonename);
+  purgeAuthCachesExact(zonename);
 
   // now the PTRs
   storeChangedPTRs(B, new_ptrs);
@@ -1578,10 +1576,10 @@ void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
 
   DNSName canon = apiNameToDNSName(req->getvars["domain"]);
 
-  int count = PC.purgeExact(canon);
+  uint64_t count = purgeAuthCachesExact(canon);
   resp->setBody(Json::object {
-    { "count", count },
-    { "result", "Flushed cache." }
+      { "count", (int) count },
+      { "result", "Flushed cache." }
   });
 }
 
index 048479d68f5f3eebe480ca6770572d903aa9a587..ecaf2de39902cb90916e993136f540e44d69be9b 100644 (file)
@@ -25,6 +25,7 @@ class DNSDistTest(unittest.TestCase):
     that the queries sent from dnsdist were as expected.
     """
     _dnsDistPort = 5340
+    _dnsDistListeningAddr = "127.0.0.1"
     _testServerPort = 5350
     _toResponderQueue = Queue.Queue()
     _fromResponderQueue = Queue.Queue()
@@ -62,7 +63,7 @@ class DNSDistTest(unittest.TestCase):
             conf.write(cls._config_template % params)
 
         dnsdistcmd = [os.environ['DNSDISTBIN'], '-C', conffile,
-                      '-l', '127.0.0.1:%d' % cls._dnsDistPort]
+                      '-l', '%s:%d' % (cls._dnsDistListeningAddr, cls._dnsDistPort) ]
         for acl in cls._acl:
             dnsdistcmd.extend(['--acl', acl])
         print(' '.join(dnsdistcmd))
index 952301ec6287720dd88878eaed8d040be775b7f6..00ec12b36e039ae804882385b7e4142823dad81e 100644 (file)
@@ -1370,3 +1370,72 @@ class TestAdvancedRD(DNSDistTest):
         receivedQuery.id = query.id
         self.assertEquals(receivedQuery, query)
         self.assertEquals(receivedResponse, response)
+
+class TestAdvancedGetLocalPort(DNSDistTest):
+
+    _config_template = """
+    function answerBasedOnLocalPort(dq)
+      local port = dq.localaddr:getPort()
+      return DNSAction.Spoof, "port-was-"..port..".local-port.advanced.tests.powerdns.com."
+    end
+    addLuaAction("local-port.advanced.tests.powerdns.com.", answerBasedOnLocalPort)
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testAdvancedGetLocalPort(self):
+        """
+        Advanced: Return CNAME containing the local port
+        """
+        name = 'local-port.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.CNAME,
+                                    'port-was-{}.local-port.advanced.tests.powerdns.com.'.format(self._dnsDistPort))
+        response.answer.append(rrset)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, response)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, response)
+
+class TestAdvancedGetLocalPortOnAnyBind(DNSDistTest):
+
+    _config_template = """
+    function answerBasedOnLocalPort(dq)
+      local port = dq.localaddr:getPort()
+      return DNSAction.Spoof, "port-was-"..port..".local-port-any.advanced.tests.powerdns.com."
+    end
+    addLuaAction("local-port-any.advanced.tests.powerdns.com.", answerBasedOnLocalPort)
+    newServer{address="127.0.0.1:%s"}
+    """
+    _dnsDistListeningAddr = "0.0.0.0"
+
+    def testAdvancedGetLocalPortOnAnyBind(self):
+        """
+        Advanced: Return CNAME containing the local port for an ANY bind
+        """
+        name = 'local-port-any.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.CNAME,
+                                    'port-was-{}.local-port-any.advanced.tests.powerdns.com.'.format(self._dnsDistPort))
+        response.answer.append(rrset)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, response)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, response)
index ad6c9992f50be35ecec7b63f8ed28e18fc51d5a5..a46a40f724ea48527423774e1b663d8e0603f9c9 100644 (file)
@@ -1269,3 +1269,60 @@ class TestCachingFailureTTL(DNSDistTest):
             total += self._responsesCounter[key]
 
         self.assertEquals(total, misses)
+
+class TestCachingDontAge(DNSDistTest):
+
+    _config_template = """
+    pc = newPacketCache(100, 86400, 0, 60, 60, true)
+    getPool(""):setCache(pc)
+    newServer{address="127.0.0.1:%s"}
+    """
+    def testCacheDoesntDecreaseTTL(self):
+        """
+        Cache: Cache doesn't decrease TTL with 'don't age' set
+
+        dnsdist is configured to cache entries but without aging the TTL,
+        we are sending one request (cache miss) and verify that the cache
+        hits don't have a decreasing TTL.
+        """
+        ttl = 600
+        misses = 0
+        name = 'cachedoesntdecreasettl.cache-dont-age.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        # first query to fill the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+        misses += 1
+
+        # next queries should hit the cache
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, response)
+        for an in receivedResponse.answer:
+            self.assertTrue(an.ttl == ttl)
+
+        # now we wait a bit for the TTL to decrease
+        time.sleep(1)
+
+        # next queries should hit the cache
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, response)
+        for an in receivedResponse.answer:
+            self.assertTrue(an.ttl == ttl)
+
+        total = 0
+        for key in self._responsesCounter:
+            total += self._responsesCounter[key]
+
+        self.assertEquals(total, misses)
index c4565d62906e08c4f702a3b65a1b2d8cbadae8ec..fb7c0ce54e52116b7acac56c0bc77154aa6eebe0 100644 (file)
@@ -1,4 +1,5 @@
 #!/usr/bin/env python
+import base64
 import time
 import dns
 from dnsdisttests import DNSDistTest
@@ -345,8 +346,12 @@ class TestDynBlockResponseBytes(DNSDistTest):
     _dynBlockBytesPerSecond = 200
     _dynBlockPeriod = 2
     _dynBlockDuration = 5
-    _config_params = ['_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey)
+    _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
     _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
     function maintenance()
            addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
     end
@@ -373,6 +378,9 @@ class TestDynBlockResponseBytes(DNSDistTest):
 
         allowed = 0
         sent = 0
+
+        print(time.time())
+
         for _ in xrange(self._dynBlockBytesPerSecond * 5 / len(response.to_wire())):
             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
             sent = sent + len(response.to_wire())
@@ -385,22 +393,46 @@ class TestDynBlockResponseBytes(DNSDistTest):
                 # the query has not reached the responder,
                 # let's clear the response queue
                 self.clearToResponderQueue()
+                # and stop right there, otherwise we might
+                # wait for so long that the dynblock is gone
+                # by the time we finished
+                break
 
         # we might be already blocked, but we should have been able to send
         # at least self._dynBlockBytesPerSecond bytes
+        print(allowed)
+        print(sent)
+        print(time.time())
         self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
 
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
         if allowed == sent:
             # wait for the maintenance function to run
+            print("Waiting for the maintenance function to run")
             time.sleep(2)
 
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
         self.assertEquals(receivedResponse, None)
 
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
         # wait until we are not blocked anymore
         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
 
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
         # this one should succeed
         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
         receivedQuery.id = query.id
@@ -422,6 +454,10 @@ class TestDynBlockResponseBytes(DNSDistTest):
                 # the query has not reached the responder,
                 # let's clear the response queue
                 self.clearToResponderQueue()
+                # and stop right there, otherwise we might
+                # wait for so long that the dynblock is gone
+                # by the time we finished
+                break
 
         # we might be already blocked, but we should have been able to send
         # at least self._dynBlockBytesPerSecond bytes
index c68b39f9b1313e4c552d198519ce4be53e00ac88..00a483345102dbd851e3c280d3e2cacf49dfeeae 100644 (file)
@@ -2,6 +2,8 @@
 corrupt-packets=0
 deferred-cache-inserts=0
 deferred-cache-lookup=0
+deferred-packetcache-inserts=0
+deferred-packetcache-lookup=0
 dnsupdate-answers=0
 dnsupdate-changes=0
 dnsupdate-queries=0
@@ -10,8 +12,9 @@ incoming-notifications=0
 key-cache-size=0
 meta-cache-size=1
 overload-drops=0
-packetcache-size=8
+packetcache-size=4
 qsize-q=0
+query-cache-size=4
 rd-queries=0
 recursing-answers=0
 recursing-questions=0
index f8a52831e79ef819e470bdb89ff9721077b89b88..479c243168d17b5eb3e0d56ea68b05a2d0188bdb 100644 (file)
@@ -1,5 +1,5 @@
 17a1219ca79020c1c67ad173a291d3c8  ../regression-tests/zones/example.com
-b4feb02bd50f7b78cd5c40c7a560019c  ../regression-tests/zones/test.com
+fe49d2784b1bcc3b91ddd5619f0b6cc1  ../regression-tests/zones/test.com
 005b3381db2a7dc70b690484f6ab7770  ../regression-tests/zones/test.dyndns
 0861783f9f83acd9044278cda2528ee2  ../regression-tests/zones/wtest.com
 42b442de632686e94bde75acf66cf524  ../regression-tests/zones/nztest.com
@@ -11,4 +11,4 @@ a63dc120391d9df0003f2ec4f461a6af  ../regression-tests/zones/secure-delegated.dns
 b1f775045fa2cf0a3b91aa834af06e49  ../regression-tests/zones/stest.com
 a98864b315f16bcf49ce577426063c42  ../regression-tests/zones/cdnskey-cds-test.com
 9aeed2c26d0c3ba3baf22dfa9568c451  ../regression-tests/zones/2.0.192.in-addr.arpa
-da60133e145e3a8ff6c86003607a5d96  ../modules/tinydnsbackend/data.cdb
+e30afafb846e773a5bff70dcfdb52088  ../modules/tinydnsbackend/data.cdb
index e1a9259d5377599ba8624ef9742c2cd46515529a..5ccdc85e8b725a5d617e4d860793cd7dac5e6e1c 100644 (file)
@@ -38,6 +38,7 @@ gsql_master()
        $RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. \
                --config-name=$backend --socket-dir=./ --no-shuffle \
                --dnsupdate=yes --resolver=8.8.8.8 --outgoing-axfr-expand-alias=yes \
+               --expand-alias=yes \
                --cache-ttl=$cachettl --dname-processing \
                --disable-axfr-rectify=yes $lua_prequery &
 
index 97380c1aabf664c070ae286ead5ff807c4b2a5fc..bb84f374376afd86b8b8a53a7e0b1cb8154112e5 100644 (file)
@@ -1,3 +1,2 @@
 RFC2136 describes that every update message should result in the SOA-serial to be changed.
 This test checks if that happens.
-
index d92ae4e200ad2ba5215ad23125d194be36df130d..e3de329942881a6eb1826313136f702783b1e9e6 100644 (file)
@@ -1,2 +1 @@
 A test for RFC2136 which checks section 3.4.2.2 - Replacing a record.
-
index 8ac6dcd87ba38ea656e979c748310dde62e4f6ef..be9fba039e7a5a18954cf2001fe30aef361e0370 100644 (file)
@@ -1 +1 @@
-Use CNAME to validate '@' expansion in RR contents.
\ No newline at end of file
+Use CNAME to validate '@' expansion in RR contents.
index f24685c7592cb60c2e301c04bb808571a6afc9ed..6d5692c5114ccb7688364fef1bd8f67b042750f9 100644 (file)
@@ -10,7 +10,8 @@ blah.test.com.        86400   IN      NSEC    b.c.test.com. NS RRSIG NSEC
 counter.test.com.      86400   IN      NSEC    d.test.com. A RRSIG NSEC
 d.test.com.    86400   IN      NSEC    _double._tcp.dc.test.com. DNAME RRSIG NSEC
 enum.test.com. 86400   IN      NSEC    hightxt.test.com. NAPTR RRSIG NSEC
-hightxt.test.com.      86400   IN      NSEC    ns1.test.com. TXT RRSIG NSEC SPF
+hightxt.test.com.      86400   IN      NSEC    interrupted-rrset.test.com. TXT RRSIG NSEC SPF
+interrupted-rrset.test.com.    86400   IN      NSEC    ns1.test.com. A TXT RRSIG NSEC
 ns1.test.com.  86400   IN      NSEC    ns2.test.com. A RRSIG NSEC
 ns2.test.com.  86400   IN      NSEC    server1.test.com. A RRSIG NSEC
 server1.test.com.      86400   IN      NSEC    *.test.test.com. A RP RRSIG NSEC
index 10c17297903e800514bc1364cb8c98f08047b163..b92a0b31344a67231f39958fe089db174b5f3b6b 100644 (file)
@@ -13,7 +13,8 @@ aovp95mr44hqefrqus6nomsd944bm3vb.test.com.    86400   IN      NSEC3   1 0 1 abcd B022O9DKSAJ
 b022o9dksaj737fh77e7kqqtj3om56ki.test.com.     86400   IN      NSEC3   1 0 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5
 dafc69cv5n2tfcf6ovbvtv94drgmqjo5.test.com.     86400   IN      NSEC3   1 0 1 abcd DE592K86U3HEVDJ57JPBT7J5KV7DOO78 TXT RRSIG
 de592k86u3hevdj57jpbt7j5kv7doo78.test.com.     86400   IN      NSEC3   1 0 1 abcd EBAN51BJGUGORB20UNP5PEEC7S5D2EKA NS
-eban51bjgugorb20unp5peec7s5d2eka.test.com.     86400   IN      NSEC3   1 0 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP SRV RRSIG
+eban51bjgugorb20unp5peec7s5d2eka.test.com.     86400   IN      NSEC3   1 0 1 abcd ENG6HBK77VJMQFVG6S04HAJOA2201LII SRV RRSIG
+eng6hbk77vjmqfvg6s04hajoa2201lii.test.com.     86400   IN      NSEC3   1 0 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP A TXT RRSIG
 h5855rvon2aasm8qv1nk49i1b2mkbejp.test.com.     86400   IN      NSEC3   1 0 1 abcd IAI9HIN25MEH689R5V5GTIFK8OM5DI0E A RRSIG
 iai9hin25meh689r5v5gtifk8om5di0e.test.com.     86400   IN      NSEC3   1 0 1 abcd IGF4M7OTECACH14P0A6INGI7DBUAS5B2 A RRSIG
 igf4m7otecach14p0a6ingi7dbuas5b2.test.com.     86400   IN      NSEC3   1 0 1 abcd N5RSKFSBG0UK5SSPJ595R6546HKK5VK1 A RP RRSIG
index e83ea26554e51e422af5f1ca9503898035ab7e3e..07ea72cf83e631b9e32b9108e69176581b4328b9 100644 (file)
@@ -12,7 +12,8 @@ a5labagjjevr86gh0hf3jg7nufhga5ar.test.com.    86400   IN      NSEC3   1 1 1 abcd AOVP95MR44H
 aovp95mr44hqefrqus6nomsd944bm3vb.test.com.     86400   IN      NSEC3   1 1 1 abcd B022O9DKSAJ737FH77E7KQQTJ3OM56KI A RRSIG
 b022o9dksaj737fh77e7kqqtj3om56ki.test.com.     86400   IN      NSEC3   1 1 1 abcd DAFC69CV5N2TFCF6OVBVTV94DRGMQJO5
 dafc69cv5n2tfcf6ovbvtv94drgmqjo5.test.com.     86400   IN      NSEC3   1 1 1 abcd EBAN51BJGUGORB20UNP5PEEC7S5D2EKA TXT RRSIG
-eban51bjgugorb20unp5peec7s5d2eka.test.com.     86400   IN      NSEC3   1 1 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP SRV RRSIG
+eban51bjgugorb20unp5peec7s5d2eka.test.com.     86400   IN      NSEC3   1 1 1 abcd ENG6HBK77VJMQFVG6S04HAJOA2201LII SRV RRSIG
+eng6hbk77vjmqfvg6s04hajoa2201lii.test.com.     86400   IN      NSEC3   1 1 1 abcd H5855RVON2AASM8QV1NK49I1B2MKBEJP A TXT RRSIG
 h5855rvon2aasm8qv1nk49i1b2mkbejp.test.com.     86400   IN      NSEC3   1 1 1 abcd IAI9HIN25MEH689R5V5GTIFK8OM5DI0E A RRSIG
 iai9hin25meh689r5v5gtifk8om5di0e.test.com.     86400   IN      NSEC3   1 1 1 abcd IGF4M7OTECACH14P0A6INGI7DBUAS5B2 A RRSIG
 igf4m7otecach14p0a6ingi7dbuas5b2.test.com.     86400   IN      NSEC3   1 1 1 abcd N5RSKFSBG0UK5SSPJ595R6546HKK5VK1 A RP RRSIG
index 3174f5dd07bce2026939ce4e51ec033d21a64f87..e3b67c2156351b3beaa64d2b74acef27d5301b50 100644 (file)
@@ -1,2 +1 @@
 This test checks a secure delegations.
-
index bdfcbe55467d4a3a821f9c507da9ca082f96b587..d040d06edfa85125b3083bd22afc25b4dad07126 100644 (file)
@@ -38,3 +38,6 @@ hightxt               IN      TXT     "v=spf1 mx ip4:78.46.192.210 –all"
 hightxt                IN      SPF     "v=spf1 mx ip4:78.46.192.210 –all"
 d              IN      DNAME   d2.test2.com.
 urc65226       IN      TYPE65226 \# 3 414243
+interrupted-rrset      IN      A       1.1.1.1
+interrupted-rrset      IN      TXT     "check AXFR signpipe"
+interrupted-rrset      IN      A       2.2.2.2