]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #8942 from Habbie/lua-backtrace
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Thu, 24 Sep 2020 08:51:02 +0000 (10:51 +0200)
committerGitHub <noreply@github.com>
Thu, 24 Sep 2020 08:51:02 +0000 (10:51 +0200)
lua: add backtraces to errors

531 files changed:
.circleci/config.yml
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
.github/actions/spell-check/allow.txt [new file with mode: 0644]
.github/actions/spell-check/excludes.txt [new file with mode: 0644]
.github/actions/spell-check/expect.txt [new file with mode: 0644]
.github/actions/spell-check/only.txt [new file with mode: 0644]
.github/actions/spell-check/patterns.txt [new file with mode: 0644]
.github/workflows/fuzz.yml
.github/workflows/spelling.yml [new file with mode: 0644]
README.md
SECURITY.md
build-scripts/changelog-from-pr.py
build-scripts/docker/generate-repo-files.sh
build-scripts/test-recursor
build-scripts/test-recursor-bulk
build-scripts/travis.sh
builder-support/debian/authoritative/debian-buster/config/geoip.conf
builder-support/debian/authoritative/debian-buster/pdns-backend-sqlite3.docs
builder-support/debian/authoritative/debian-buster/rules
builder-support/debian/authoritative/debian-jessie/config/geoip.conf
builder-support/debian/authoritative/debian-jessie/pdns-backend-sqlite3.docs
builder-support/debian/authoritative/debian-jessie/rules
builder-support/debian/authoritative/debian-stretch/config/geoip.conf
builder-support/debian/authoritative/debian-stretch/pdns-backend-sqlite3.docs
builder-support/debian/authoritative/debian-stretch/rules
builder-support/debian/recursor/debian-buster/pdns-recursor.init
builder-support/debian/recursor/debian-buster/rules
builder-support/debian/recursor/debian-jessie/pdns-recursor.init
builder-support/debian/recursor/debian-jessie/rules
builder-support/debian/recursor/debian-stretch/pdns-recursor.init
builder-support/debian/recursor/debian-stretch/rules
builder-support/dockerfiles/Dockerfile.debbuild-prepare
builder-support/dockerfiles/Dockerfile.rpmbuild
builder-support/dockerfiles/Dockerfile.target.amazon-2
builder-support/dockerfiles/Dockerfile.target.centos-6
builder-support/dockerfiles/Dockerfile.target.centos-7
builder-support/dockerfiles/Dockerfile.target.centos-8
builder-support/dockerfiles/Dockerfile.target.ubuntu-focal [new file with mode: 0644]
builder-support/specs/dnsdist.spec
builder-support/specs/pdns-recursor.spec
builder-support/specs/pdns.spec
codedocs/doxygen.conf
configure.ac
contrib/pdnsutil.bash_completion.d
docs/appendices/EOL.rst
docs/appendices/FAQ.rst
docs/appendices/backend-writers-guide.rst
docs/appendices/types.rst
docs/backends/bind.rst
docs/backends/generic-mysql.rst
docs/backends/generic-postgresql.rst
docs/backends/generic-sql.rst
docs/backends/ldap.rst
docs/backends/lmdb.rst
docs/changelog/4.0.rst
docs/changelog/4.1.rst
docs/changelog/4.2.rst
docs/changelog/4.3.rst
docs/changelog/pre-4.0.rst
docs/dnssec/pkcs11.rst
docs/dnssec/profile.rst
docs/dnsupdate.rst
docs/domainmetadata.rst
docs/http-api/index.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
docs/http-api/tsigkey.rst
docs/http-api/zone.rst
docs/index.rst
docs/installation.rst
docs/lua-records/reference/dnsname.rst
docs/lua-records/reference/netmask.rst
docs/manpages/nsec3dig.1.rst
docs/manpages/pdns_control.1.rst
docs/manpages/pdns_server.1.rst
docs/manpages/pdnsutil.1.rst
docs/manpages/sdig.1.rst
docs/migration.rst
docs/modes-of-operation.rst
docs/secpoll.zone
docs/security-advisories/powerdns-advisory-2020-05.rst [new file with mode: 0644]
docs/security-advisories/powerdns-advisory-2020-06.rst [new file with mode: 0644]
docs/settings.rst
docs/tsig.rst
docs/upgrading.rst
ext/lmdb-safe/lmdb-safe.cc
ext/lmdb-safe/lmdb-safe.hh
ext/luawrapper/include/LuaContext.hpp
ext/yahttp/yahttp/reqresp.cpp
fuzzing/README.md
fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-local-header [new file with mode: 0644]
fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-v4-with-tlvs [new file with mode: 0644]
m4/pdns_check_dnstap.m4
m4/pdns_enable_gss_tsig.m4 [deleted file]
m4/systemd.m4
modules/bindbackend/Makefile.am
modules/bindbackend/bindbackend2.cc
modules/bindbackend/bindbackend2.hh
modules/geoipbackend/geoipbackend.cc
modules/geoipbackend/geoipbackend.hh
modules/gmysqlbackend/4.2.0_to_4.3.0_schema.mysql.sql
modules/gmysqlbackend/gmysqlbackend.cc
modules/gmysqlbackend/smysql.cc
modules/gmysqlbackend/smysql.hh
modules/godbcbackend/godbcbackend.cc
modules/gpgsqlbackend/4.2.0_to_4.3.0_schema.pgsql.sql
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gpgsqlbackend/schema.pgsql.sql
modules/gpgsqlbackend/spgsql.cc
modules/gpgsqlbackend/spgsql.hh
modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql [new file with mode: 0644]
modules/gsqlite3backend/Makefile.am
modules/gsqlite3backend/gsqlite3backend.cc
modules/gsqlite3backend/schema.sqlite3.sql
modules/ldapbackend/ldapauthenticator.cc
modules/ldapbackend/ldapauthenticator_p.hh
modules/ldapbackend/ldapbackend.cc
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh
modules/lua2backend/lua2backend.cc
modules/pipebackend/coprocess.cc
modules/pipebackend/coprocess.hh
modules/pipebackend/pipebackend.cc
modules/randombackend/randombackend.cc
modules/remotebackend/Gemfile.lock
modules/remotebackend/Makefile.am
modules/remotebackend/httpconnector.cc
modules/remotebackend/regression-tests/Gemfile.lock [changed from file to symlink]
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/tinydnsbackend/tinydnsbackend.cc
modules/tinydnsbackend/tinydnsbackend.hh
pdns/.gitignore
pdns/Makefile.am
pdns/auth-caches.cc
pdns/auth-carbon.cc
pdns/auth-packetcache.cc
pdns/auth-packetcache.hh
pdns/auth-querycache.cc
pdns/auth-querycache.hh
pdns/axfr-retriever.cc [new file with mode: 0644]
pdns/axfr-retriever.hh [new file with mode: 0644]
pdns/backends/gsql/gsqlbackend.cc
pdns/backends/gsql/gsqlbackend.hh
pdns/base32.cc
pdns/bind-dnssec.4.2.0_to_4.3.0_schema.sqlite3.sql
pdns/bind-dnssec.schema.sqlite3.sql
pdns/bindparser.yy
pdns/cachecleaner.hh
pdns/calidns.cc
pdns/common_startup.cc
pdns/common_startup.hh
pdns/communicator.cc
pdns/communicator.hh
pdns/dbdnsseckeeper.cc
pdns/delaypipe.cc
pdns/delaypipe.hh
pdns/devpollmplexer.cc
pdns/distributor.hh
pdns/dns.hh
pdns/dns_random.cc
pdns/dns_random.hh
pdns/dnsbackend.cc
pdns/dnsbackend.hh
pdns/dnsbulktest.cc
pdns/dnscrypt.cc
pdns/dnscrypt.hh
pdns/dnsdist-cache.cc
pdns/dnsdist-cache.hh
pdns/dnsdist-carbon.cc
pdns/dnsdist-console.cc
pdns/dnsdist-console.hh
pdns/dnsdist-ecs.cc
pdns/dnsdist-lbpolicies.hh
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-lua-inspection.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist-lua-vars.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-tcp.cc
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistconf.lua
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/dnsdist-backend.cc
pdns/dnsdistdist/dnsdist-healthchecks.cc
pdns/dnsdistdist/dnsdist-idstate.cc
pdns/dnsdistdist/dnsdist-kvs.cc
pdns/dnsdistdist/dnsdist-kvs.hh
pdns/dnsdistdist/dnsdist-lbpolicies.cc
pdns/dnsdistdist/dnsdist-lua-bindings-dnscrypt.cc
pdns/dnsdistdist/dnsdist-lua-bindings-kvs.cc
pdns/dnsdistdist/dnsdist-lua-bindings-packetcache.cc
pdns/dnsdistdist/dnsdist-lua-bindings-protobuf.cc
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/dnsdist-lua-inspection-ffi.hh
pdns/dnsdistdist/dnsdist-proxy-protocol.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-proxy-protocol.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-rules.hh
pdns/dnsdistdist/dnsdist-secpoll.cc
pdns/dnsdistdist/dnsdist-web.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist.service.in
pdns/dnsdistdist/docs/advanced/acl.rst
pdns/dnsdistdist/docs/advanced/ecs.rst
pdns/dnsdistdist/docs/advanced/index.rst
pdns/dnsdistdist/docs/advanced/proxyprotocol.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/advanced/tuning.rst
pdns/dnsdistdist/docs/advanced/xpf.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/changelog.rst
pdns/dnsdistdist/docs/conf.py
pdns/dnsdistdist/docs/glossary.rst
pdns/dnsdistdist/docs/guides/cache.rst
pdns/dnsdistdist/docs/guides/dns-over-https.rst
pdns/dnsdistdist/docs/guides/dns-over-tls.rst
pdns/dnsdistdist/docs/guides/dynblocks.rst
pdns/dnsdistdist/docs/guides/serverselection.rst
pdns/dnsdistdist/docs/guides/webserver.rst [changed mode: 0644->0755]
pdns/dnsdistdist/docs/install.rst
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/reference/constants.rst [changed mode: 0644->0755]
pdns/dnsdistdist/docs/reference/dnsname.rst
pdns/dnsdistdist/docs/reference/dnstap.rst
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/docs/reference/index.rst [changed mode: 0644->0755]
pdns/dnsdistdist/docs/reference/kvs.rst
pdns/dnsdistdist/docs/reference/logging.rst [new file with mode: 0755]
pdns/dnsdistdist/docs/reference/netmaskgroup.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/docs/running.rst
pdns/dnsdistdist/docs/upgrade_guide.rst
pdns/dnsdistdist/doh.cc
pdns/dnsdistdist/libssl.cc
pdns/dnsdistdist/packetcache.hh [new symlink]
pdns/dnsdistdist/proxy-protocol.cc [new symlink]
pdns/dnsdistdist/proxy-protocol.hh [new symlink]
pdns/dnsdistdist/tcpiohandler.cc
pdns/dnsdistdist/test-delaypipe_hh.cc
pdns/dnsdistdist/test-dnsdistkvs_cc.cc
pdns/dnsdistdist/test-dnsdistlbpolicies_cc.cc
pdns/dnsdistdist/test-proxy_protocol_cc.cc [new symlink]
pdns/dnsdistdist/views.hh [new symlink]
pdns/dnsgram.cc
pdns/dnslabeltext.rl
pdns/dnsmessage.proto
pdns/dnsname.cc
pdns/dnsname.hh
pdns/dnspacket.cc
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnspcap.cc
pdns/dnspcap.hh
pdns/dnspcap2protobuf.cc
pdns/dnsproxy.cc
pdns/dnsproxy.hh
pdns/dnsrecords.cc
pdns/dnsrecords.hh
pdns/dnsreplay.cc
pdns/dnsscan.cc
pdns/dnsscope.cc
pdns/dnssecinfra.cc
pdns/dnsseckeeper.hh
pdns/dnssecsigner.cc
pdns/dnstap.proto
pdns/dnstcpbench.cc
pdns/dnswasher.cc
pdns/dnswriter.hh
pdns/doh.hh
pdns/dumresp.cc
pdns/dynhandler.cc
pdns/dynhandler.hh
pdns/dynlistener.cc
pdns/dynlistener.hh
pdns/ednsoptions.cc
pdns/ednsoptions.hh
pdns/filterpo.cc
pdns/filterpo.hh
pdns/fuzz_dnsdistcache.cc
pdns/fuzz_moadnsparser.cc
pdns/fuzz_packetcache.cc
pdns/fuzz_proxyprotocol.cc [new file with mode: 0644]
pdns/fuzz_zoneparsertng.cc
pdns/gss_context.cc [deleted file]
pdns/gss_context.hh [deleted file]
pdns/iputils.cc
pdns/iputils.hh
pdns/ixfrdist-stats.hh
pdns/ixfrdist-web.cc
pdns/ixfrdist.cc
pdns/ixfrdist.service.in
pdns/ixfrutils.cc
pdns/ixplore.cc
pdns/libssl.hh
pdns/lock.hh
pdns/logger.cc
pdns/logger.hh
pdns/lua-auth4.cc
pdns/lua-base4.cc
pdns/lua-record.cc
pdns/lua-recursor4-ffi.hh
pdns/lua-recursor4.cc
pdns/lua-recursor4.hh
pdns/lwres.cc
pdns/lwres.hh
pdns/mastercommunicator.cc
pdns/misc.cc
pdns/misc.hh
pdns/mtasker.cc
pdns/mtasker.hh
pdns/nameserver.cc
pdns/nameserver.hh
pdns/notify.cc
pdns/nproxy.cc
pdns/nsec3dig.cc
pdns/opensslsigners.cc
pdns/packetcache.hh
pdns/packethandler.cc
pdns/packethandler.hh
pdns/pdns.service.in
pdns/pdns_recursor.cc
pdns/pdnsutil.cc
pdns/pkcs11signers.cc
pdns/portsmplexer.cc
pdns/proxy-protocol.cc [new file with mode: 0644]
pdns/proxy-protocol.hh [new file with mode: 0644]
pdns/qtype.hh
pdns/query-local-address.cc [new file with mode: 0644]
pdns/query-local-address.hh [new file with mode: 0644]
pdns/rcpgenerator.cc
pdns/rec-carbon.cc
pdns/rec-lua-conf.cc
pdns/rec-protobuf.cc
pdns/rec-protobuf.hh
pdns/rec-snmp.cc
pdns/rec_channel.hh
pdns/rec_channel_rec.cc
pdns/receiver.cc
pdns/recpacketcache.cc
pdns/recpacketcache.hh
pdns/recursor_cache.cc
pdns/recursor_cache.hh
pdns/recursordist/Makefile.am
pdns/recursordist/RECURSOR-MIB.txt
pdns/recursordist/axfr-retriever.cc [new symlink]
pdns/recursordist/axfr-retriever.hh [new symlink]
pdns/recursordist/configure.ac
pdns/recursordist/contrib/kv-example-script.lua
pdns/recursordist/docs/appendices/EOL.rst
pdns/recursordist/docs/appendices/compiling.rst
pdns/recursordist/docs/appendices/internals.rst
pdns/recursordist/docs/changelog/4.1.rst
pdns/recursordist/docs/changelog/4.2.rst
pdns/recursordist/docs/changelog/4.3.rst
pdns/recursordist/docs/changelog/4.4.rst [new file with mode: 0644]
pdns/recursordist/docs/changelog/index.rst
pdns/recursordist/docs/changelog/pre-4.0.rst
pdns/recursordist/docs/dns64.rst
pdns/recursordist/docs/dnssec.rst
pdns/recursordist/docs/getting-started.rst
pdns/recursordist/docs/http-api/endpoint-failure.rst
pdns/recursordist/docs/http-api/zone.rst
pdns/recursordist/docs/index.rst
pdns/recursordist/docs/lua-config/protobuf.rst
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/lua-scripting/comboaddress.rst
pdns/recursordist/docs/lua-scripting/dnsname.rst
pdns/recursordist/docs/lua-scripting/dq.rst
pdns/recursordist/docs/lua-scripting/hooks.rst
pdns/recursordist/docs/lua-scripting/index.rst
pdns/recursordist/docs/lua-scripting/netmask.rst
pdns/recursordist/docs/lua-scripting/policyevent.rst [new file with mode: 0644]
pdns/recursordist/docs/manpages/pdns_recursor.1.rst
pdns/recursordist/docs/manpages/rec_control.1.rst
pdns/recursordist/docs/metrics.rst
pdns/recursordist/docs/nod_udr.rst
pdns/recursordist/docs/running.rst
pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-01.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-02.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-03.rst [new file with mode: 0644]
pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-04.rst [new file with mode: 0644]
pdns/recursordist/docs/settings.rst
pdns/recursordist/docs/upgrade.rst
pdns/recursordist/examples/safesearch.lua
pdns/recursordist/gss_context.cc [deleted symlink]
pdns/recursordist/gss_context.hh [deleted symlink]
pdns/recursordist/negcache.cc
pdns/recursordist/negcache.hh
pdns/recursordist/pdns-recursor.service.in
pdns/recursordist/proxy-protocol.cc [new symlink]
pdns/recursordist/proxy-protocol.hh [new symlink]
pdns/recursordist/query-local-address.cc [new symlink]
pdns/recursordist/query-local-address.hh [new symlink]
pdns/recursordist/rec_metrics.hh
pdns/recursordist/shuffle.cc [new symlink]
pdns/recursordist/shuffle.hh [new symlink]
pdns/recursordist/test-filterpo_cc.cc
pdns/recursordist/test-negcache_cc.cc
pdns/recursordist/test-nod_cc.cc
pdns/recursordist/test-recursorcache_cc.cc
pdns/recursordist/test-rpzloader_cc.cc
pdns/recursordist/test-secpoll_cc.cc
pdns/recursordist/test-syncres_cc.cc
pdns/recursordist/test-syncres_cc.hh
pdns/recursordist/test-syncres_cc1.cc
pdns/recursordist/test-syncres_cc10.cc [new file with mode: 0644]
pdns/recursordist/test-syncres_cc2.cc
pdns/recursordist/test-syncres_cc3.cc
pdns/recursordist/test-syncres_cc4.cc
pdns/recursordist/test-syncres_cc5.cc
pdns/recursordist/test-syncres_cc6.cc
pdns/recursordist/test-syncres_cc7.cc
pdns/recursordist/test-syncres_cc8.cc
pdns/recursordist/test-syncres_cc9.cc
pdns/recursordist/testrunner.cc
pdns/recursordist/views.hh [new symlink]
pdns/reczones.cc
pdns/remote_logger.hh
pdns/resolver.cc
pdns/resolver.hh
pdns/rfc2136handler.cc
pdns/rpzloader.cc
pdns/saxfr.cc
pdns/sdig.cc
pdns/secpoll-auth.cc
pdns/secpoll-recursor.cc
pdns/secpoll.cc
pdns/shuffle.cc [new file with mode: 0644]
pdns/shuffle.hh [new file with mode: 0644]
pdns/signingpipe.cc
pdns/slavecommunicator.cc
pdns/snmp-agent.hh
pdns/sodcrypto.cc
pdns/speedtest.cc
pdns/ssqlite3.cc
pdns/statbag.cc
pdns/statbag.hh
pdns/stubquery.cc
pdns/stubresolver.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/tcpreceiver.cc
pdns/tcpreceiver.hh
pdns/test-dns_random_hh.cc
pdns/test-dnsdist_cc.cc
pdns/test-dnsdistpacketcache_cc.cc
pdns/test-dnsrecords_cc.cc
pdns/test-lock_hh.cc
pdns/test-misc_hh.cc
pdns/test-packetcache_cc.cc
pdns/test-packetcache_hh.cc
pdns/test-proxy_protocol_cc.cc [new file with mode: 0644]
pdns/test-recpacketcache_cc.cc
pdns/test-signers.cc
pdns/test-statbag_cc.cc
pdns/test-ueberbackend_cc.cc [new file with mode: 0644]
pdns/testrunner.cc
pdns/tkey.cc
pdns/toysdig.cc
pdns/tsig-tests.cc
pdns/tsigutils.cc
pdns/tsigverifier.cc
pdns/ueberbackend.cc
pdns/ueberbackend.hh
pdns/validate.cc
pdns/validate.hh
pdns/views.hh [new file with mode: 0644]
pdns/webserver.cc
pdns/ws-auth.cc
pdns/ws-auth.hh
pdns/ws-recursor.cc
pdns/ws-recursor.hh
pdns/zone2ldap.cc
pdns/zone2sql.cc
pdns/zoneparser-tng.cc
regression-tests.api/test_TSIG.py
regression-tests.api/test_Zones.py
regression-tests.auth-py/authtests.py
regression-tests.auth-py/requirements.txt
regression-tests.auth-py/test_IXFR.py [new file with mode: 0644]
regression-tests.auth-py/test_LuaRecords.py
regression-tests.common/proxyprotocol.py [new file with mode: 0644]
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/proxyprotocol.py [new symlink]
regression-tests.dnsdist/test_API.py
regression-tests.dnsdist/test_Caching.py
regression-tests.dnsdist/test_DOH.py
regression-tests.dnsdist/test_ProxyProtocol.py [new file with mode: 0644]
regression-tests.dnsdist/test_Routing.py
regression-tests.dnsdist/test_TCPLimits.py
regression-tests.nobackend/counters/expected_result
regression-tests.nobackend/tinydns-data-check/expected_result
regression-tests.recursor-dnssec/basicDNSSEC.py
regression-tests.recursor-dnssec/proxyprotocol.py [new symlink]
regression-tests.recursor-dnssec/recursortests.py
regression-tests.recursor-dnssec/test_API.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_DNS64.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_ECS.py
regression-tests.recursor-dnssec/test_Lua.py
regression-tests.recursor-dnssec/test_PacketCache.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_Protobuf.py
regression-tests.recursor-dnssec/test_ProxyProtocol.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_RPZ.py
regression-tests.recursor-dnssec/test_ReadTrustAnchorsFromFile.py
regression-tests.recursor-dnssec/test_RoutingTag.py [new file with mode: 0644]
regression-tests.recursor/RPZ/expected_result
regression-tests.recursor/config.sh
regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result
regression-tests/README.md
regression-tests/backends/bind-master
regression-tests/backends/gmysql-master
regression-tests/backends/gmysql-slave
regression-tests/backends/gsql-common
regression-tests/backends/lmdb-master
regression-tests/named.conf
regression-tests/recursor-test
regression-tests/recursor-test-freebsd
regression-tests/start-test-stop
regression-tests/tests/cryptokeys/command
regression-tests/tests/cryptokeys/expected_result.dnssec
regression-tests/tests/cryptokeys/expected_result.narrow [new file with mode: 0644]
regression-tests/tests/cryptokeys/expected_result.nsec3 [new file with mode: 0644]
regression-tests/tests/cryptokeys/skip-drill [new file with mode: 0644]
regression-tests/tests/nsecx-mode2-wildcard-nodata/description
regression-tests/tests/nsecx-mode3-wildcard/description
regression-tests/tests/verify-dnssec-zone/command
regression-tests/zones/hiddencryptokeys.org [new file with mode: 0644]

index e5f2f928b0ed10ac73789aadd008325a30d2ec55..3ea55f4484e4a9f01b25b10158b337695d371759 100644 (file)
@@ -75,6 +75,7 @@ commands:
               dnsutils \
               ldnsutils \
               libnet-dns-perl \
+              pdns-recursor \
               unbound-host
       - run:
           name: Install jdnssectools
@@ -89,6 +90,10 @@ commands:
       - run:
           name: Allow missing tools in verify-dnssec-zone
           command: touch regression-tests/tests/verify-dnssec-zone/allow-missing
+      - run:
+          name: Start PowerDNS Recursor in the background
+          command: pdns_recursor
+          background: true
 
   auth-regress:
     description: Run one auth regression context
@@ -130,6 +135,7 @@ commands:
             ZONE2LDAP=<< parameters.prefix >>bin/zone2ldap \
             PDNSUTIL=<< parameters.prefix >>bin/pdnsutil \
             PDNSCONTROL=<< parameters.prefix >>bin/pdns_control \
+            RESOLVERIP=127.0.0.1 \
             ./start-test-stop 5300 << parameters.context >>
       - when:
           condition: << parameters.doroot >>
@@ -154,6 +160,7 @@ commands:
                   ZONE2LDAP=<< parameters.prefix >>bin/zone2ldap \
                   PDNSUTIL=<< parameters.prefix >>bin/pdnsutil \
                   PDNSCONTROL=<< parameters.prefix >>bin/pdns_control \
+                  RESOLVERIP=127.0.0.1 \
                   ./start-test-stop 5300 << parameters.context >>
 
   install-recursor-deps:
@@ -184,12 +191,14 @@ commands:
             libldap-2.4-2 \
             liblmdb0 \
             libluajit-5.1-2 \
+            libp11-kit0 \
             libpq5 \
             libssl1.1 \
             libsodium23 \
             libsystemd0 \
             default-libmysqlclient-dev \
-            unixodbc
+            unixodbc \
+            softhsm2
 
   install-dnsdist-deps:
     description: "Install all libraries needed for testing dnsdist"
@@ -211,6 +220,7 @@ commands:
               libssl-dev \
               libsystemd0 \
               libsodium23 \
+              patch \
               protobuf-compiler \
               virtualenv
 
@@ -250,6 +260,7 @@ commands:
               libldap2-dev \
               liblmdb-dev \
               libluajit-5.1-dev \
+              libp11-kit-dev \
               libpq-dev \
               libsodium-dev \
               libsqlite3-dev \
@@ -534,6 +545,7 @@ jobs:
               --enable-unit-tests \
               --enable-backend-unit-tests \
               --enable-fuzz-targets \
+              --enable-experimental-pkcs11 \
               --with-lmdb=/usr \
               --with-libsodium \
               --prefix=/opt/pdns-auth \
@@ -675,7 +687,7 @@ jobs:
     steps:
       - auth-regress-setup
       - run:
-          command: apt-get install -qq -y sqlite3
+          command: apt-get install -qq -y sqlite3 p11-kit softhsm2
       - auth-regress:
           context: bind-both
       - auth-regress:
@@ -686,6 +698,8 @@ jobs:
           context: bind-dnssec-nsec3-optout-both
       - auth-regress:
           context: bind-dnssec-nsec3-narrow
+      - auth-regress:
+          context: bind-dnssec-pkcs11
       - run:
           command: apt-get install -qq -y default-mysql-client
       - run:
@@ -732,6 +746,8 @@ jobs:
           context: gmysql-nsec3-optout-both
       - auth-regress:
           context: gmysql-nsec3-narrow
+      - auth-regress:
+          context: gmysql_sp-both
 
   test-auth-regress-gpgsql:
     resource_class: small
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644 (file)
index 0000000..99f5173
--- /dev/null
@@ -0,0 +1,6 @@
+blank_issues_enabled: false
+
+contact_links:
+- name: Get help with a question or a problem
+  url: https://www.powerdns.com/opensource.html
+  about: Find us on IRC or our mailing lists to get the answers or help you need!
\ No newline at end of file
diff --git a/.github/actions/spell-check/allow.txt b/.github/actions/spell-check/allow.txt
new file mode 100644 (file)
index 0000000..fba0b6a
--- /dev/null
@@ -0,0 +1,4753 @@
+aaaarc
+aaaarec
+aaaarr
+aaaaset
+aab
+aabbccddeeff
+aabit
+aac
+aafd
+AAg
+Aand
+Aaq
+Aarbp
+aarch
+abca
+abcabcabcabacabac
+abcb
+abcd
+abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq
+abcdefgh
+abcdefghijklmnopq
+abcdefghijklmnopqrstuv
+abcdefghijklmnopqrstuvwxyz
+abcdefghj
+abq
+ACAABIBg
+acceptspace
+accountname
+aci
+aclocal
+ACLTOK
+Acpvl
+Acu
+adata
+addaction
+addgroup
+addnode
+ADDO
+ADDOCD
+addrbuf
+ADDRCONFIG
+addrinfo
+addrlabel
+addrlen
+addrringbuf
+adduser
+addzone
+adifferentlabel
+advsys
+aerique
+afc
+afe
+Affero
+afilias
+Afio
+afpx
+afternm
+aglu
+ahudns
+aio
+Akkermann
+akxe
+Alexey
+algname
+algonum
+algotype
+alldown
+allemaal
+ALLEXTERNALS
+allmakers
+alloc
+allocflux
+alloptions
+alloutqueries
+allowedips
+allownooptinar
+allowns
+allowonear
+allowtwoan
+allowtwoarnoopt
+allowwnooptinar
+alnum
+alphtype
+alpn
+ALSONOTIFYTOK
+altfrac
+altmeters
+altsign
+Amau
+amazonaws
+amazonlinux
+Amc
+amet
+Amf
+AMFLAGS
+Ampl
+anaccount
+anadns
+aname
+ANANSWER
+ancount
+andnot
+ANDQ
+anl
+anonpdns
+anonscm
+anonymizing
+answercount
+answermask
+answername
+answersslow
+answertime
+ANYId
+ANYNo
+anytruncatetcp
+ao
+aoh
+aorudp
+AOver
+apipassword
+apisecret
+apix
+apk
+apos
+apowerdnscom
+appliedpolicy
+appliedpolicytype
+appname
+APTR
+APublic
+Aqerz
+AQEX
+arcount
+arec
+arecvtcp
+argc
+arglock
+argp
+argparse
+args
+Argumentsv
+argv
+argvec
+arl
+aroot
+arp
+arpos
+arrs
+ARSH
+asan
+asendtcp
+asendto
+aset
+asn
+asnr
+AStats
+athread
+atid
+ATime
+ATj
+Ato
+atof
+atruncatetc
+attodot
+attrany
+attributetype
+attrname
+attronly
+Auous
+authbind
+authcid
+authcmd
+authconfdir
+authdir
+authdomain
+authlog
+authname
+AUTHRUN
+authstorage
+authtests
+authzid
+authzonepath
+authzonequeries
+AUTOBRIEF
+Autobuilder
+autocalculating
+autocommit
+autogen
+autom
+autorev
+autorr
+autosetup
+autosplit
+avect
+AWbqt
+AWIBIQ
+AWith
+awithecs
+AWORD
+aww
+AXFRGET
+axfrqlen
+Ayea
+AZWLVw
+bacf
+BACKENDADDRESS
+backendfactory
+BACKENDID
+BACKENDLATENCY
+backendloader
+backendmaker
+backendname
+BACKENDORDER
+BACKENDOUTSTANDING
+BACKENDPOOLS
+BACKENDQPS
+BACKENDQPSLIMIT
+BACKENDQUERIES
+BACKENDREUSED
+BACKENDSTATE
+BACKENDWEIGHT
+BADALGO
+badmac
+badname
+badrequests
+bak
+BAQEB
+baseurl
+BASICRESP
+batchmode
+baz
+bbnew
+bbold
+bbsv
+bcbsks
+Bchl
+Bdn
+bdr
+bdsqlodbc
+befe
+beforenm
+BEFORENMBYTES
+begiter
+Beh
+bereturnscookiesecscookies
+bereturnscookiesthenecs
+bereturnsecs
+bereturnsecsthencookies
+berval
+bestmatch
+bestns
+bestpos
+Bfcw
+bff
+BFr
+BGs
+bh
+bhartvigsen
+BHQk
+bidir
+BIGNUM
+BINARYPATH
+binascii
+bincount
+BINDANY
+BINDCONF
+binddirectory
+binddnssec
+binderror
+bindir
+bindlexer
+bindmethod
+bindnow
+bindparser
+bindparserclasses
+bindpw
+bindwait
+bitcstr
+bitmembers
+bitpointers
+bitset
+bitsizes
+bitsleft
+bitstr
+Bj
+BKCNF
+bkoz
+BKU
+blablabla
+blahb
+bleh
+blen
+blhc
+blockfor
+blockset
+bloeh
+blxn
+bmc
+bmgul
+BMh
+BNA
+BNEh
+BNX
+bogusqueryring
+bogusremotering
+bogusremotes
+BOIWc
+boostorg
+booststringappend
+Bostock
+BPHYx
+bpowerdns
+bpowerdnscom
+bq
+BREHMDq
+brokeloop
+broot
+BRRRRR
+BRu
+bsqlodbc
+BSTUNE
+BTf
+btoe
+buf
+buffersize
+buflen
+BUGLIST
+bugreport
+builddir
+buildflags
+buildroot
+builtins
+bulktest
+burtle
+burtlemix
+bval
+Bvc
+bvect
+Bvy
+BWdx
+byport
+bytag
+bytearray
+byteorder
+byterate
+bytesread
+BZq
+cacheable
+cachebase
+cachecache
+cachecachevalid
+cachecleaner
+cacheda
+cachedecreasettl
+cachedifferentcase
+cachedifferentedns
+cachedir
+cachedoesntdecreasettl
+cachedqname
+cachedrtag
+cacheecsttl
+cacheentries
+cacheexpiration
+cacheexpirationdifferentsets
+cachefull
+cachehandleiter
+cachehit
+cachehitresponses
+cachehitresprulactions
+cachelimit
+cachemisses
+cachenotfullyet
+cacheonly
+cachettl
+cacheweekno
+CAcreateserial
+caddyfile
+caddyserver
+cafile
+CAINFO
+callbackfunc
+callbackmap
+callee
+callgraph
+CANTOPEN
+capair
+caplen
+carbonname
+carbonthread
+cas
+cassert
+cbe
+cbegin
+cbf
+CBlock
+cbmap
+CBTd
+cbuf
+cbufsize
+ccf
+ccname
+ccontrols
+cctype
+ccv
+cda
+cdbbc
+cdbf
+cdbfile
+cdbinit
+cdbm
+CDBQ
+cdc
+cdd
+cdde
+cdef
+cellpadding
+cend
+cerr
+cerrno
+CERTID
+CERTSTATUS
+Cexternal
+Cfb
+cft
+Cfy
+cgit
+CHACHA
+chartocode
+checkbox
+checkfunc
+checkglue
+checkinterval
+checknow
+Checkof
+checkrr
+checkzone
+childstat
+chkconfig
+chr
+chrono
+cin
+cinttypes
+ciphersuites
+circleci
+citmp
+Cj
+cka
+Ckey
+CKF
+CKK
+CKM
+CKO
+CKR
+cku
+classname
+classnum
+CLASSTOK
+cleandig
+CLEANFILES
+cleaninterval
+cleannsupdate
+cleanskipped
+clearrd
+clearrdviaaction
+cleartext
+clearthensetrules
+clen
+clientdiff
+CLIENTIP
+clientparseerrors
+clientsubnet
+clientsubnetoption
+climits
+clmn
+CLOEXEC
+closedir
+closelog
+cls
+Clvv
+cmake
+cmap
+cmatchlen
+cmath
+cmdline
+cmdomains
+cmp
+cmsgbuf
+Cmu
+cmval
+cnam
+cnameaction
+CNAMEAt
+CNAMENo
+CNAMENTA
+cnamerec
+cnamespoof
+cnamespoofaction
+cnamewildcard
+cnamewildcardnxdomain
+cnt
+Cnxd
+codedocs
+coldata
+colnum
+colspan
+columncount
+combovar
+comfun
+compat
+compgen
+compilerflags
+COMPREPLY
+concat
+concurrentqueries
+condrestart
+confbasename
+confdir
+confdirname
+conffile
+configdir
+configentry
+configpath
+configstring
+conflictor's
+confx
+connectionroom
+connectlogstr
+connectstr
+connstr
+conscli
+consequenses
+contentlen
+contentstr
+contenttype
+cookiesoption
+coproc
+copyable
+copyfile
+correctpackets
+cors
+Covcbz
+cparts
+cpid
+cplusplus
+cpng
+cpnmu
+cpnmuog
+cpnmuoj
+cpos
+CPPFLAGS
+cptr
+cpuchart
+cpugraph
+CPUIO
+cpuload
+cpuset
+cpuy
+Cqg
+CQq
+crbegin
+createdomainentry
+createzones
+creativecommons
+cred
+crend
+Crhs
+crr
+crt
+cruft
+CSAx
+Csemi
+Cserver
+cset
+csignal
+Csmtp
+cso
+cspiter
+cspmap
+csr
+cssfunction
+Cstart
+cstat
+cstddef
+cstdint
+cstdio
+cstdlib
+cstr
+cstring
+csu
+CSVE
+ctag
+ctive
+ctl
+ctlun
+Ctoroot
+ctxarg
+ctype
+CUDJFRFI
+cumul
+cumulstats
+Cunauth
+CURLE
+CURLINFO
+CURLOPT
+CURLPROTO
+curtime
+CVbx
+Cw
+Cwithin
+CWORD
+Cwww
+Cxk
+cxx
+CZENW
+daemonize
+DAFB
+dasharray
+databuffer
+datafmt
+datalen
+datapos
+dataret
+datestr
+datetime
+DBC
+dbd
+dbddb
+dbdnssec
+dbdnsseckeeper
+dbg
+DBHOST
+dbi
+DBIf
+dbkey
+dbkeyset
+dblacka
+dbmode
+Dbolui
+DBp
+DBPASS
+dbpath
+DBPORT
+dbrec
+dbrg
+DBSERVER
+DBT
+DBUSER
+DCBE
+dcec
+Dcg
+DCHVORt
+Dcity
+dcke
+DCMAKE
+Dcode
+Ddata
+ddb
+DDct
+Ddcy
+dddx
+ddeb
+ddepth
+DDWN
+deadbeef
+deadc
+debbuild
+debconf
+DEBEMAIL
+DEBFULLNAME
+debhelper
+debtest
+decafsigners
+decerement
+declarearguments
+declarestats
+decltype
+deconfigure
+deconfigured's
+deduplicate
+deepcopy
+defaultmap
+defaultport
+defaultttl
+defaultvalue
+defport
+defstr
+deinit
+delaypipe
+delcount
+delcounter
+deleteme
+deletetsigkey
+delnonterm
+depcomp
+DEPRECATEDLIST
+deps
+deque
+derp
+descr
+descrip
+deserialize
+destaddr
+destdir
+devent
+deviceid
+devnull
+devpoll
+devpollfd
+devpollmplexer
+devscripts
+dfe
+DFL
+dfsdfsdf
+dgn
+dgram
+DGUX
+DHE
+Dhh
+DHTML
+Dhydb
+dietlibc
+difflib
+diffopts
+difft
+diffto
+digesttype
+dimissing
+dimitri
+directbackendcmd
+DIRECTORYTOK
+dirent
+dirfile
+dirhdl
+dirname
+dirp
+dirs
+dirstamp
+disabledvialua
+diskspace
+distdir
+disthashseed
+distributo
+DIXl
+DIy
+djlr
+DJYNFFq
+dkc
+dke
+dkrc
+DLCC
+DLD
+DLDAP
+dlen
+dlfcn
+dlib
+DLOCALSTATEDIR
+DLOG
+dlopen
+DLQ
+DMd
+DMEd
+dmi
+dmp
+dmq
+DNAM
+DNAMETo
+DNb
+dndist
+dnf
+dni
+dnl
+dnmatch
+DNODCACHEDIR
+DNRSx
+dnsbackend
+dnsclass
+DNSDISTBIN
+dnsdistcache
+dnsdistclient
+dnsdistcmd
+dnsdistdynblocks
+dnsdistkvs
+dnsdistlbpolicies
+dnsdistluarules
+dnsdistpacketcache
+dnsdistrings
+dnsdistrules
+dnsdisttests
+dnserrors
+DNSID
+DNSIP
+dnskeyr
+dnslabeltext
+dnslen
+dnsmasq
+dnsmaster
+dnsnameqtyperings
+dnsnameraw
+dnsp
+dnsperf
+dnspkt
+DNSPORT
+dnsproxy
+dnspython
+dnsq
+dnsrecordcontent
+dnsrecordheader
+DNSRR
+dnsrule
+dnsscript
+DNSSE
+dnssecdb
+dnssecinfra
+dnsseckeeper
+dnssecmode
+dnssecok
+DNSSECOn
+dnssecsigner
+dnsstats
+dnsstrings
+dnstcpb
+dnstext
+dnstype
+dnsutils
+dnswriter
+DNSZR
+docbits
+docblock
+DOCD
+docdir
+Dockerfile
+docstring
+DOCTYPE
+doent
+doesnotmatter
+DOHFFI
+dohlocals
+dohquerypair
+dohresponsepair
+DOIx
+dokill
+dolog
+domaindetails
+domainid
+domainidindex
+domainidmetaindex
+domainmap
+domainmetaidindex
+domainmetanameindex
+domainnameindex
+domainsdone
+domcount
+dononterm
+dontallow
+dontdrop
+dontinclude
+dontqueries
+DONTWAIT
+doquery
+dosec
+dotests
+dotfile
+downcase
+Doxyfile
+doxygen
+doxyrules
+DPGZA
+dpk
+dpkg
+DPKGLIBDIR
+dpm
+dpos
+dprefix
+dptr
+dpw
+dqcount
+dqffi
+drc
+drh
+drillchase
+DRmcx
+dro
+Dropbox
+dropdb
+dropset
+dropwhencached
+drr
+drs
+drsoa
+dsa
+DSANSEC
+dsc
+dsdelegation
+dsdigestalgorithm
+dses
+DSfor
+dskey
+dsmap
+dsn
+dspk
+dsrc
+dsrec
+dss
+dsset
+dstates
+dstportrule
+DSYSCONFDIR
+dtime
+dtn
+dtor
+dtv
+dtxn
+DUPSORT
+DUv
+dval
+DVGN
+dvi
+DVIUi
+dvp
+dvpoll
+dw
+Dwaoc
+DWITH
+DXagbsuz
+dylib
+DYNLINKFLAGS
+dynlistener
+dynloader
+dynmessenger
+dynmetrics
+dynmetricslock
+dzr
+eaaecdabe
+eab
+EABy
+eacdd
+EADDRINUSE
+EADDRNOTAVAIL
+EAFNOSUPPORT
+ebda
+EBERb
+Ebgy
+ebpfblock
+EBRACE
+ebuf
+EBUSY
+EByvht
+ECCGOST
+ecdh
+ECDHE
+ecdsap
+ecf
+ecgroup
+eckey
+ecount
+ECP
+ecparam
+ECSBy
+ecscachelimitttl
+ECSIn
+ECSIP
+ecsipv
+ecso
+ecsqueries
+ecsresponses
+ecsrules
+ECSTo
+ECSTTL
+ectx
+eddsa
+EDEADLK
+editttl
+edkey
+edn
+ednscookies
+ednsdomains
+EDNSECS
+EDNSFFI
+ednsflag
+EDNSIGNORANT
+ednsip
+ednslocalsubnets
+ednsmap
+ednsmask
+ednsnm
+EDNSNo
+EDNSOK
+ednsoptcode
+ednsoption
+ednsoptionrule
+EDNSR
+ednsrcode
+ednsremotesubnets
+EDNSRR
+ednsstatus
+EDNSUDP
+ednsversion
+ednsversionrule
+EDNSZ
+edo
+EDR
+EDSIGNORANT
+eecfe
+Eelapsed
+eevent
+EEXIST
+EEy
+efbd
+EFGH
+efmq
+EFw
+egrep
+EHADv
+Ei
+eid
+EILq
+EINPROGRESS
+EINVAL
+Eips
+EISCONN
+eiter
+Ejf
+ejkmcpqxot
+Ekfq
+Eky
+elabel
+elems
+elif
+Elr
+elseif
+elsif
+emailbx
+emap
+EMD
+EMERG
+EMFILE
+enableval
+Enames
+encaps
+endcode
+endianness
+endnode
+endpos
+endptr
+endswith
+ENETUNREACH
+Enj
+ENOBUFS
+ENODEV
+ENOSPC
+ENOSYS
+Enx
+enzo
+eol
+EOu
+EPIPE
+epita
+eply
+epollfd
+EPOLLIN
+epollmplexer
+EPOLLOUT
+eptr
+EPVHU
+eqd
+eqdnsmessage
+equivs
+Eqy
+Erc
+erca
+ercode
+ercursor
+ERKSs
+errcode
+errfds
+errlen
+errmsg
+Erroring
+errorresponses
+errorresult
+errorstring
+errstr
+esac
+escapedtext
+escdecb
+eso
+esow
+ESRCH
+esyscmd
+Etcu
+ETEQw
+ETIME
+ETIMEDOUT
+Eto
+etree
+etry
+eturn
+eui
+evah
+eventkey
+everytime
+EVFILT
+Evi
+evilapikey
+evilsecret
+evloop
+evp
+ewma
+EWOULDBLOCK
+EWPk
+Ewr
+examplenet
+exe
+execinfo
+execv
+execvp
+EXEEXT
+EXITCODE
+exitvalue
+exky
+Expat
+expectedlen
+expf
+expr
+expungebyname
+expungebynameandtype
+expungebynameother
+extdir
+extfile
+extracontext
+EXTRAOPTS
+extrasmn
+extrcode
+exu
+Eyd
+EZ
+EZg
+fabs
+FAFAFA
+FAHr
+FAILONERROR
+fakeroot
+faketime
+fallthrough
+FASTOPEN
+FBB
+Fbo
+fbr
+Fbx
+fclose
+fcnt
+fcntl
+fctx
+fdb
+fdf
+fdm
+fdmultiplexer
+fdset
+feb
+fernando
+fetchall
+FFEE
+ffilb
+ffipolicyfunc
+fflush
+fgets
+fh
+Fibl
+fieldname
+filebasename
+filenam
+fileno
+Filenum
+filestate
+filesystem
+FILETOK
+FILETYPE
+filtermap
+filtername
+filterpo
+findall
+findinit
+findnext
+FIRSTHDR
+firstquestiontime
+fixme
+fixperms
+FJHL
+Fk
+FKFy
+flawednsset
+FLHY
+FLJ
+flowinfo
+FLrjot
+flt
+flushname
+FLYU
+Fmajor
+fmod
+fmri
+fmter
+fnamearg
+fnd
+fndhemi
+FNHYc
+fno
+fns
+fnum
+followedbyanother
+FOLLOWLOCATION
+foob
+fooba
+fopen
+forcesafesearch
+formask
+formerr
+foundct
+fournosoa
+fournosoainfirst
+foursecondsoainsecond
+fpacket
+Fpb
+fpc
+Fpk
+fprintf
+fptype
+fq
+fqdn
+Fqgo
+FQk
+fread
+freeaddrinfo
+FREEBIND
+fri
+fromaddr
+fromlen
+fromport
+fromportstr
+fromserial
+fromstdin
+fromtimestamp
+fromtosockets
+fromvalue
+frontsbase
+fsanitize
+fsck
+FSDB
+fsf
+fsl
+fslu
+fslutest
+fstream
+fstrmlogger
+fsync
+ftf
+ftp
+FTXp
+funarg
+funcdb
+funcparam
+funcstats
+funkdb
+funkwithusage
+funnytext
+fuzzer
+FVM
+Fvn
+FVr
+FVrip
+Fvuj
+FWG
+fwopt
+fwparams
+fwrite
+fx
+FXc
+fxl
+Fxpgs
+FYd
+Fym
+Fyq
+gai
+gaierror
+gamesgiroll
+garblewarble
+gatewaytype
+gatherwildcard
+gbv
+Gcached
+GCCPACKATTRIBUTE
+gcda
+GClj
+gcno
+gcount
+Gcountry
+gcov
+gdata
+gdate
+gdb
+gecos
+Gemfile
+gencontrol
+GENERALIZEDTIME
+GENERR
+genhook
+genlog
+geoipdatabase
+geoipdosec
+geoipgraphic
+geoipinterface
+geoipkeydir
+geoiploader
+geoiplookup
+geoiprecord
+geoipregion
+geoipregionip
+geoname
+geosec
+getaftername
+getalldomains
+getatomics
+getattr
+getbeforeandafternames
+getbeforename
+getchar
+getcommonlabels
+getcontext
+getcursor
+getcwd
+getdiagrec
+getdn
+getegid
+getent
+getenv
+geteuid
+GETFL
+getgrnam
+gethostbyname
+getinfo
+getlastlabel
+getline
+getlock
+getnameinfo
+GETNEXT
+getopt
+getpagesize
+getpass
+getpeername
+getpid
+GETPIPE
+getpwnam
+getpwuid
+getqueries
+GETQUESTION
+getrawlabel
+getrlimit
+getroot
+getrusage
+getserial
+getsocket
+getsockname
+getsockopt
+getstack
+getstdout
+getupdatedmasters
+getvalue
+getvars
+gf
+GFi
+ghostscript
+gir
+gitlab
+GIZI
+GJPRf
+GK
+GLIBCXX
+globalconf
+globfree
+Glq
+glueless
+Glxb
+gmd
+gmt
+GMTOFF
+GMYSQLDB
+GMYSQLHOST
+gmysqlloader
+GMYSQLPASSWD
+GMYSQLUSER
+Gnomovision
+gnuc
+gnupg
+GOceania
+godbcloader
+godns
+GOMRf
+googledomains
+gost
+gotdomaindetails
+gotipdetails
+gotit
+gotoline
+gotsome
+GPflpm
+gpg
+GPGSQ
+GPGSQLDB
+gpgsqlloader
+GPGSQLUSER
+gpl
+GPLv
+GPOS
+GPRINT
+Gqhx
+Gqxdqt
+grabkeys
+GRAPHOPTS
+Graphviz
+greg
+grok
+groupadd
+groupinstall
+grp
+gruleactions
+gsort
+gsqlbackend
+gsqlitebackend
+gssctx
+gtar
+gtm
+GTNi
+GTXTk
+gtzero
+guido
+GVs
+GWa
+GWTy
+Gwy
+Gx
+GXX
+GYK
+GZha
+haas
+habbie
+halen
+HAPB
+hardlimit
+Hartvigsen
+hasattr
+hashedidx
+hashindex
+hashperturb
+hatype
+havedollarttl
+haveednssection
+haveednssubnet
+HAVENSEC
+havetsig
+HBB
+Hc
+HCID
+HCIEg
+HCNUM
+HCo
+hcode
+HCPD
+hctx
+Hcu
+hdr
+Hdv
+headerfmt
+HEADERFUNCTION
+headersize
+headl
+headr
+healththread
+helpmap
+helpstring
+herokuapp
+Heu
+hexlify
+hextodec
+HFtab
+hhc
+hhp
+hhx
+hiddencryptokeys
+hideinitializer
+hidesoaserial
+hightxt
+hightype
+HIHEe
+hijackme
+hintfile
+Hiso
+histfile
+histo
+histog
+HJpbmcg
+hk
+HKm
+hkw
+hlapi
+Hlatitudeh
+HLEN
+Hlll
+Hlocation
+hmacsha
+hmech
+Hmisix
+HMrc
+HMukilteo
+Hn
+HNk
+holelock
+homedir
+horiz
+horizpre
+hostconf
+hostlist
+hostmastercom
+hoststr
+Hpj
+hppa
+hqp
+HResearch
+hsa
+HSIZE
+HSmu
+hsould
+HSw
+htmlfiles
+htobe
+htole
+htonl
+htons
+htons'ed
+httpconnector
+httpconnects
+httpd
+HTTPHEADER
+httpversion
+HUr
+HVeu
+Hvw
+hwinfo
+Hx
+hxx
+HYrl
+HZFV
+HZXIZh
+iarchive
+IAustralia
+Iav
+ibfk
+IBMR
+IBRw
+ICANON
+ICASE
+ICNOKr
+icontent
+Icontinent
+idindex
+idl
+idmanager
+IDOID
+idonotexist
+IDont
+idpool
+idqueue
+Idret
+idserver
+idstate
+iends
+iequals
+IEUW
+iface
+ifarch
+ifeq
+ifindex
+ifl
+ifndef
+IFSOCK
+ifstream
+ifupport
+ifupurl
+ifw
+Iga
+ignorebogus
+igoy
+IH
+IHOST
+Ihw
+IImrg
+iinfo
+Iinputs
+iix
+IIY
+IJ
+Ilanguages
+ilexicographical
+ILggb
+Ilongitudeh
+imap
+IMEI
+Imiw
+impl
+inbytes
+Incd
+inceptionday
+incfiles
+includedir
+indexfunction
+inescape
+inext
+infd
+inflighter
+infodir
+infostream
+initctl
+initgroups
+initialrequestid
+initialrequestidstr
+initparams
+initrddir
+initval
+inkey
+inl
+inlined
+ino
+inp
+inprogress
+inputkey
+inputlen
+inputname
+inqueries
+insn
+insnonterm
+insserv
+installdeb
+installdox
+installexamples
+installinit
+INTERNETTRAFFIC
+intervalcount
+intptr
+intransaction
+ints
+inttypes
+intxn
+ioctl
+iomanip
+ioqueue
+iostate
+iothr
+iothropt
+iov
+iovec
+iovlen
+IOz
+ipaddress
+ipairs
+ipcipheripcipher
+IPDNS
+ipfromstr
+IPg
+iphdr
+ipi
+iplist
+ipmap
+ipo
+ipp
+ipparts
+ipport
+IPPROTO
+ipsum
+IPTo
+iptostr
+ipunitlist
+iqmp
+Iqw
+irz
+isa
+isalnum
+isalpha
+isatty
+isbase
+isdigit
+ISDIR
+isfile
+isinstance
+islandofsecurity
+isleap
+ISREG
+Isrqzjh
+isru
+isrunning
+iss
+isset
+isspace
+istream
+istreambuf
+istringstream
+isxdigit
+iteritems
+itf
+Itg
+ITIMER
+itimerval
+itmp
+Itni
+itoa
+Iu
+IUIu
+IUt
+ival
+Iwlx
+IWN
+IWR
+ixes
+IXF
+ixfrdiff
+IXFRDISTBIN
+ixfrdistcmd
+ixfrdistdomain
+ixfrdisttests
+ixfrinfo
+ixfrutils
+ixx
+iy
+Izd
+IZws
+JAmk
+jan
+JAVADOC
+Jb
+JBnu
+Jcj
+jcong
+JCUw
+jdnssec
+jdthood
+jelu
+JEQ
+JFo
+Jgeoname
+JGKj
+JGT
+Jhb
+Jhdz
+JHm
+Jhpj
+Jip
+Jiy
+Jjbq
+jlist
+JLQ
+jm
+Jmdll
+Jmj
+Jn
+JNE
+Jnode
+JNTJMMHZDO
+journalmode
+jpg
+JQTNLZDBh
+jquery
+jre
+JSGT
+jsonp
+JSONTSIG
+jsr
+JTf
+Jtv
+Juhf
+jul
+Jungermann
+Jwmtfu
+JWndz
+Jx
+JZ
+JZIA
+JZte
+kamago
+kauq
+kbcafe
+KBo
+Kbuild
+kce
+kdb
+kdbarg
+Kdescription
+Kdismbe
+keepnocd
+keeprd
+keeprecurse
+kevent
+keyalgorithm
+KEYBYTES
+keycache
+keycachelock
+keydata
+keyid
+keylen
+keylog
+keymeta
+keymetadb
+keyout
+keypos
+keyring
+keyserver
+keystr
+keystring
+keytag
+keyv
+Kfq
+Kgvf
+kh
+Khk
+killall
+killproc
+Kj
+KJPKEd
+kk
+Kkfp
+KKl
+KLa
+klen
+KMBT
+kmd
+KMP
+Knj
+Kok
+Kosnik
+KOw
+kq
+kqevent
+KQF
+Kqim
+kqueuefd
+kqueuemplexer
+Krecord
+KRIEGER
+Ksj
+kskds
+kskeys
+KStream
+Ksy
+kvo
+kvresp
+kwargs
+KWw
+kx
+KXu
+kz
+KZR
+labellen
+labelparts
+Labelreverse
+labelscount
+labelscountadvanced
+labeltok
+laddr
+LADIP
+largeanswerremotes
+largernumberofconnections
+largettl
+lastanswer
+lastclean
+lastcounts
+lastget
+lastline
+lastmod
+lastperc
+lastpos
+lastquestion
+lastreport
+lastsec
+lastsum
+lastval
+latdeg
+latdiff
+latexmk
+lathem
+latlonstrptr
+latmin
+latsec
+Launcheable
+lauxlib
+layj
+lber
+lbpolicies
+lcc
+lci
+Lcode
+lcontent
+lcrypto
+lczonename
+LDADD
+ldapadd
+ldapauthenticator
+LDAPBASEDN
+ldapbinddn
+ldapdelete
+LDAPHOST
+ldaploader
+ldapmodify
+LDAPNo
+LDAPPASSWD
+LDAPRELATTR
+ldapsecret
+ldapuris
+LDAPUSER
+ldaputils
+LDAPv
+LDB
+ldd
+ldnsutils
+leftcolumn
+leftiter
+lels
+LEMLD
+lenpos
+lentry
+lflag
+LGCe
+LGeo
+lgnutls
+LGPL
+LHav
+lhead
+LHq
+lhs
+Lhwzi
+LIBADD
+LIBASAN
+libauthbind
+libbindbackend
+libboost
+libbpf
+libcap
+libcdb
+libdl
+libexecdir
+libfaketime
+libgen
+libgeoipbackend
+libgmysqlbackend
+libgnutls
+libgodbcbackend
+libgpgsqlbackend
+libgsqlite
+libipcrypt
+libjson
+libkern
+libkrb
+libldap
+libldapbackend
+liblmdb
+liblmdbbackend
+liblua
+libluajit
+libnacl
+libnet
+libopie
+libp
+libpipebackend
+libprobds
+libprotobuf
+librandombackend
+libremotebackend
+libsnmp
+libsqlite
+libsqliteodbc
+libtestremotebackend
+libtinydnsbackend
+libtolua
+libtool
+libunbound
+libwslay
+libyahttp
+licensedir
+lightuserdata
+lineno
+linespoints
+lineterm
+linktype
+LISi
+listenaddress
+listerner
+listname
+listset
+listx
+Lj
+lk
+lkjhgf
+lld
+LLL
+lltemp
+llu
+LMDBQ
+Lmg
+lnc
+loaderdecaf
+loadersodium
+loadmodule
+locala
+localaddress
+localaddresses
+localbind
+localname
+localsock
+localstatedir
+loctext
+locwild
+lodbc
+logaction
+LOGGINGTOK
+Loginfo
+logline
+logprefix
+logscale
+logstream
+lol
+londeg
+londiff
+longenough
+longindex
+LONGLONG
+longopts
+LONGTEXT
+longttl
+lonhem
+lonmin
+lonsec
+Lookaside
+lookedup
+lookupdomain
+loopcount
+Lorem
+lowercasequery
+lowercasing
+Lpat
+LPi
+lptr
+lpz
+Lq
+Lqcha
+Lqm
+Lqr
+lrde
+lru
+LSA
+lsb
+LSH
+Lsl
+lssl
+lstr
+LString
+Lsubdivisions
+LSzyzd
+LTest
+LTGp
+lthread
+LTLIBRARIES
+ltmain
+ltoptions
+ltsugar
+Ltuxc
+ltversion
+LTvniq
+luacall
+luaconf
+luaconfpath
+luadnsrule
+luaffi
+luaffiactionfunction
+luaffiactionsettag
+luaffiroundrobin
+luaffirulefunction
+luahooks
+LUAJITPC
+lualib
+luamaintenance
+luamutex
+LUAPC
+luarulefunction
+luaruleparams
+luascript
+luascriptpath
+luasmn
+luaspoof
+luaspoofwithstats
+luatarget
+lw
+lwn
+lwr
+lwres
+lwslay
+LZrd
+lzrp
+LZz
+MACBYTES
+maclen
+madname
+mailinglist
+mailserver
+mainfilter
+mainloop
+mainpage
+mainthread
+maj
+makebackend
+makecontext
+makeindex
+makemetadataonly
+MAKEOPTS
+makerfunc
+MAKEVAR
+mallinfo
+Mallocated
+malloctrace
+malwareset
+manpath
+marvin
+maskl
+maskr
+mastercommunicator
+mastermake
+masterplan
+masterschanged
+MASTERTOK
+matchkey
+matchlen
+matthijs
+maxanswersize
+maxbodysize
+maxbogusttl
+maxcachesize
+maxcachettl
+maxchunkrecords
+maxcleaninterval
+maxconnsperclient
+maxcp
+maxent
+maxevents
+maxfd
+MAXHOSTNAMELEN
+maxminddb
+maxnegttl
+maxnsaddressqperq
+maxobjects
+maxqperq
+maxqueriesperconn
+maxreplylen
+maxrss
+maxthreads
+maxtid
+maxtotusec
+mbox
+MBZ
+mcgrof
+mch
+mcontext
+mcp
+Mcu
+Mdatabase
+mday
+mdb
+MDBIn
+MDBRO
+MDBRW
+mdctx
+mde
+mdiff
+mdname
+mdp
+MDQx
+MDSV
+mediumsizedlabel
+MEMB
+Memcheck
+memchr
+memcmp
+memcpy
+memfree
+memis
+memmove
+memset
+memzero
+MERCHANTAPILITY
+mesg
+mesgsize
+messageid
+messageidstr
+metacache
+metacachelock
+metadatadn
+metamap
+metavar
+MEY
+Mfc
+mflags
+mgmname
+Mgo
+MHd
+mic
+michel
+microsoft
+might've
+migweb
+mincleaninterval
+minicurl
+MINIMIZATON
+mintime
+minttl
+MIPSEB
+mipsel
+mistmatching
+Mixin
+Miy
+Mj
+Mjgw
+mkbindist
+mkdir
+mkinstalldirs
+mkpubsuffixcc
+mkquery
+mkstemp
+mktemp
+mktime
+mlen
+Mlj
+Mlkroefk
+mlock
+mman
+Mmax
+mmc
+mmsghdr
+Mngwcrq
+MNorth
+moadnsparser
+moby
+moduledirs
+modulelibs
+moduleobjects
+mohta
+moreutils
+moz
+MPkb
+Mpl
+Mps
+MPSC
+mq
+mqalatency
+Mqas
+MQlel
+Mqoh
+MRUBY
+MRw
+msdn
+msecmatch
+mseconds
+msgfree
+msgh
+msghdr
+msgid
+msocket
+mstr
+MSVC
+msysmsec
+MTASKERTIMING
+MTest
+mtime
+mtracer
+mul
+multialgo
+MULTIARCH
+multimaint
+multimap
+multiplecookies
+Multiplexermap
+multispoof
+multitext
+munlock
+musermsec
+mustlog
+mutexes
+mval
+MWJu
+MXB
+mxname
+mxtics
+MXZQm
+mydata
+mydb
+myemailhere
+myfile
+myinitlock
+MYMETA
+mynewkey
+mypool
+myproject
+mysecretauthkey
+mysecretenckey
+mysqladmin
+mysqldiff
+mytag
+mytics
+Mzlw
+NAGLE
+namealgoindex
+namebuf
+namecount
+namedconf
+namedfile
+namehash
+nameindex
+namelen
+namemap
+namenum
+namepositions
+Nameret
+nameser
+nameservername
+nameservice
+namesseen
+namestocheck
+nametoindex
+nametype
+namq
+nanosleep
+nargs
+narrowbool
+nbb
+nce
+NCx
+ndays
+NDEBUG
+NDELAY
+ndiff
+NDIy
+NDUx
+needcdb
+needldap
+needlmdb
+needres
+needsqlite
+Negativerustanchor
+negativetrustanchorserver
+negativettl
+negcached
+negcacheentries
+negindic
+NERUUDNz
+netdb
+netflix
+Netscape
+netstat
+netw
+newalgo
+newargv
+newauth
+newconnectioncb
+newdata
+newdh
+newdomains
+newfd
+newgid
+newid
+newkey
+newlate
+newlen
+newlyobserveddomain
+newmap
+newnames
+newnod
+newparams
+newpath
+newpos
+newqtype
+newquery
+newrecord
+newrrs
+newruleaction
+newserial
+newserver
+newsize
+newsock
+newsr
+newstat
+newtarget
+newttl
+newuid
+newval
+nextclean
+nextcloser
+nexthash
+nexthdr
+nextid
+nextpart
+nextupd
+NEZU
+nfc
+nfds
+NFLSd
+NGtr
+nh
+NHo
+nif
+nint
+Njd
+NJKw
+nju
+nk
+NKm
+nlaunch
+Nld
+nlen
+nline
+NLips
+nlnen
+nlnetlabs
+nmasters
+nmemb
+nmgrule
+nmin
+Nml
+nmmatch
+nmp
+nmpair
+nmt
+NNl
+NOACK
+noaddrs
+noalias
+noarch
+noaxfr
+nobackend
+NOBLOCK
+noc
+nocachevialua
+NOCASE
+NOCERTS
+nocheck
+nocn
+nocontext
+nocontinue
+nocreate
+nodcachedir
+noddb
+noddbp
+nodelegated
+nodist
+nodnssec
+NODNSTAPTESTS
+nodot
+nodownstream
+NODUPDATA
+nodyndns
+noecs
+noent
+noerroranswers
+noerrorcount
+noexcept
+NOEXIST
+nof
+nogpgcheck
+NOi
+noinline
+noinput
+noinst
+nologin
+nolua
+nometa
+Nominum
+nonarrow
+NONBLOCK
+NONCEBYTES
+noncnames
+noncopyable
+nonetheripudp
+noneup
+NONINFRINGEMENT
+nonleap
+nonsec
+nop
+nopacketcache
+noparse
+NOQUOTES
+nora
+norbert
+nord
+norecurse
+norecursionavailable
+noreplace
+noreply
+noreturn
+norrsig
+noserver
+noservfailstats
+nosetests
+nostale
+NOSUB
+NOSUCHOBJECT
+nosuchpool
+notallowed
+notdisabled
+NOTFOUND
+notimp
+notimpl
+notincludedir
+notoverridden
+notoverriddenprefixlength
+notpool
+notpowerdns
+NOTRUNNING
+notsetecsaction
+notwcard
+notyouroffset
+nounput
+nowait
+nowildcard
+NOWPLUSTEN
+nowrap
+noyy
+nparams
+npos
+NPxzx
+nq
+nqk
+nqueue
+nrc
+nread
+nrecords
+nrep
+Nro
+nrr
+NRus
+nsa
+nscount
+nsdname
+nsdomain
+nseconds
+nsecrecords
+NSECs
+nsecx
+nsecxrepo
+NSIDTCP
+NSIDUDP
+nsip
+nsiploop
+nsock
+nsone
+nsq
+nsrr
+nss
+nsspeed
+nsspeedsentries
+nst
+nstld
+ntk
+ntohl
+ntohs
+ntop
+nts
+Ntx
+numanswers
+numcores
+numdomains
+numentries
+NUMERICHOST
+numerrors
+numevents
+NUMFAILED
+numloops
+numread
+numreceiveddo
+numrecords
+numsigs
+numsocks
+NUMTESTS
+numthreads
+numwarnings
+numworkers
+Nury
+NUx
+NUxt
+Nvd
+nvect
+NVQ
+nw
+nwaiters
+NWSIW
+Nwv
+Nxbulf
+NXCFg
+nxdo
+NXDOMAI
+nxdomainanswers
+nxdomainme
+nxdomainsuffix
+NXNSECS
+NXQ
+nxqtype
+NXRR
+nxrrset
+nxt
+NXTHDR
+nxwithnorr
+Nyeq
+nz
+nzg
+nztest
+oa
+Oaccuracy
+OAfx
+oarchive
+obf
+OBJECTFILES
+OBJECTLIBS
+OBJECTPROPERTY
+OBJEXT
+OBRACE
+Obsu
+OCc
+ocn
+ocontent
+octetsin
+octetsout
+octx
+ODAx
+ODESC
+ODIy
+odl
+odo
+ODQ
+OEVLOOP
+Oew
+ofc
+ofconf
+Ofdvp
+OFFMASK
+offsetof
+ofile
+OFJJXk
+OFkp
+ofstream
+ofzone
+OGhh
+OGlnb
+OGR
+ohn
+oi
+oid
+OJ
+oknodo
+OKPERCENTAGE
+OKPERCENTAGEINT
+oks
+olc
+oldmode
+oldmsg
+oldnames
+oldrr
+oldsr
+oldt
+olen
+OMG's
+omoerbeek
+OMPQK
+Omqq
+oneline
+onenosoa
+oneshot
+onexit
+ONXJQQ
+Oo
+oob
+oobar
+OOO
+OOOTCP
+oopts
+oor
+Ooutputs
+oowerdns
+opacket
+opcodenotify
+opcodeupdate
+OPEI
+opendir
+opendns
+openf
+openldap
+openlog
+opensource
+openssllocks
+opensslsigners
+opensslv
+operat
+opie
+OPj
+opname
+opos
+optarg
+OPTIn
+optind
+OPTIONSTOK
+optname
+optret
+optsize
+optstring
+optvect
+oq
+oqtaen
+orderindex
+origanswers
+origbetter
+origbetterset
+origctx
+origdnserrors
+origfd
+origid
+originalrequestorsubnet
+originalttl
+origlate
+orignever
+origtimedout
+origunmatched
+osixia
+oss
+ostr
+ostream
+osubgrouping
+OTAy
+ote
+otherdatalen
+otherprovider
+otherrcode
+othersize
+othertag
+Otkjt
+otype
+OUh
+OUHh
+ourcount
+ourdomain
+ourhelpfulservice
+ournum
+ourpos
+oursr
+ourttl
+outerpost
+outfd
+outfile
+outgoingtimeouts
+outlen
+outofzone
+outsigned
+outsock
+overriddenprefixlength
+overriddenprefixlengthvialua
+overriddenvialua
+OWJ
+OXd
+OYna
+ozz
+packetcacheentries
+packetcachehits
+packetcachemisses
+packetcacheservfailttl
+packetcachettl
+packetcaching
+packetheader
+packetperc
+packetsize
+packetwriter
+padawan
+Paf
+pagefaults
+pak
+panix
+paramcount
+PARAMDOC
+paridx
+parm
+parnum
+parsecheck
+parsefail
+parsertest
+passhtru
+passlen
+passwd
+pathbuf
+pathc
+pathconf
+pathv
+patsubst
+PBDNS
+pbegin
+PBh
+Pbi
+PBKDF
+PBpq
+pbtag
+PCas
+PCDNSSEC
+PCKS
+PCmissing
+pcomp
+pcount
+PCPU
+pctx
+pddkcrxy
+pddrw
+pdflatex
+Pdk
+pdnsbackendmysql
+pdnsbackendpgsql
+pdnsconf
+pdnsdbi
+PDNSDEBUG
+pdnsdist
+pdnsdomains
+pdnsexception
+pdnsfeatures
+pdnsinfo
+pdnsload
+PDNSPB
+PDNSRECCONTROL
+PDNSRECURSOR
+PDNSSERVER
+pdnssocket
+pdp
+perc
+periodicall
+PERLMOD
+Perror
+pershard
+pertub
+pevents
+pex
+pfh
+PFm
+pfs
+pfsbox
+PGconn
+PGHOST
+PGPASSWORD
+pgpsigurlmangle
+PGr
+PGRES
+PGresult
+PGUe
+Pgv
+PGw
+pheader
+phitrate
+PICOTLS
+pident
+pidfname
+pipeconnector
+pipefail
+pipefd
+pipefunc
+pipeloader
+PIPESTATUS
+piter
+PKCKQAu
+pkey
+pkgconfig
+pkglib
+pkglibdir
+pkgname
+PKgogeu
+pkgv
+pkill
+pkthdr
+pktinfo
+pktlen
+pkttype
+pldap
+pleasequeryfunc
+pleasequit
+pleaseremotefunc
+pleft
+plen
+plenus
+pline
+plugin
+plumgrid
+pmap
+PMQ
+PMTU
+PMTUDISC
+Pn
+pname
+PNPr
+policyfunc
+policystr
+policytag
+POLLERR
+pollfd
+POLLHUP
+pollin
+pollitem
+POLLOUT
+POLLREMOVE
+polmap
+poolaction
+popen
+popisort
+portev
+portfd
+portsmplexer
+posbegin
+posend
+posix
+postdata
+POSTFIELDS
+POSTFIELDSIZE
+postoutquery
+postpol
+postprepare
+postqueries
+postun
+postvars
+potentialsupermasters
+powerdn
+poweroften
+pparent
+pparts
+ppid
+pply
+pprint
+PQclear
+PQconnectdb
+PQerror
+PQescape
+PQexec
+PQfinish
+PQfreemem
+PQftype
+PQgetisnull
+PQgetvalue
+PQnfields
+PQntuples
+PQreset
+PQresult
+PQsocket
+PQstatus
+prc
+PRecord
+precsize
+PREDEF
+preg
+PRELOAD
+preout
+preparse
+prereq
+presignatures
+presignedcontext
+preun
+prevprev
+prevqname
+prfds
+primev
+printargs
+printf
+printlogs
+printtable
+printvars
+PRIu
+privatedns
+privateoid
+proba
+probds
+probs
+processname
+progname
+PROGRAMLISTING
+programname
+progrm
+progsarray
+promtool
+propol
+PROT
+protbuf
+protoc
+protostr
+proxyheader
+proxymagic
+PROXYMAGICLEN
+PROXYPROTOCOLHEADER
+PRsm
+prv
+psbf
+pseudonymized
+PSEUDOSECTION
+Pstmt
+psy
+ptcp
+Pthv
+pton
+pubkey
+publabel
+publicdomain
+publickey
+PUBLICKEYBYTES
+publicsuffix
+pubsuffix
+pubsuffixloader
+puk
+pullrequest
+pushlightuserdata
+pvars
+pvect
+pw
+pwd
+pwent
+pwgen
+pwtkey
+pyc
+pycache
+pycurl
+pysnmp
+PYTHONUNBUFFERED
+qaint
+qalatency
+qame
+QBD
+qc
+qcachehits
+qcachemisses
+Qccuox
+qckzu
+qclasschaos
+QClasses
+qclassin
+QCmissing
+qcount
+qcounter
+qd
+QDDt
+qdomainwild
+qdomainzone
+qesc
+qf
+qfonh
+QGy
+qhash
+qi
+qids
+Qj
+qk
+qkey
+qla
+qlass
+qlog
+Qlolbd
+Qlq
+QLs
+qmail
+qmin
+Qmsa
+qnamefilter
+qnamelen
+qnamemap
+qnameminfallbacksuccess
+qnameminimization
+qnamewirelength
+QOP
+qoutq
+qowerdns
+qpacket
+qpe
+qpol
+qpos
+qpschart
+qpsgraph
+qpslimit
+qpsnone
+qpspoolaction
+qpsstart
+qpsy
+QPTk
+QPv
+qq
+qrate
+qrateactionnxd
+qrateactionrefused
+qrateactiontruncated
+qraterefused
+qrset
+QSarv
+qsock
+qstats
+qstr
+qstring
+QSvh
+QSy
+QTag
+qtid
+qtnull
+qttl
+qtun
+qtypecounters
+qtypenums
+quantcast
+queryb
+queryfd
+queryring
+querystr
+querytimesec
+querytimeusec
+Queuedo
+queuelength
+queuetimeout
+qufnk
+Qug
+quotedname
+QUOTEDWORD
+quux
+qvalue
+qw
+QWN
+qx
+Qxh
+raddr
+raisd
+randomid
+rarg
+rattr
+RAv
+rawpacket
+rbegin
+RBu
+rca
+rcc
+rclass
+rcodecount
+rcodecounters
+rcontent
+rcounts
+rcp
+rcpgenerator
+rcv
+rcvbuf
+RCVTIMEO
+rdacounts
+rdataclass
+rdataset
+rdatastr
+rdatatype
+rdclass
+rdev
+Rdl
+rdlen
+RDLENGTH
+rdlock
+rdnonra
+rdnonrafs
+rdoc
+rdomains
+RDONLY
+rdqaplot
+rdqcounts
+rds
+rdtype
+RDWR
+readdir
+readlock
+readn
+realinput
+realname
+realnow
+realpath
+realqps
+realreferral
+realrr
+realzone
+Rebm
+RECCONTROL
+reccount
+receiveerrors
+recloc
+recordbuffer
+recordcomment
+recordcontent
+recorddata
+recordheader
+recordlen
+recordname
+recordorder
+recordplace
+recordscount
+recordstart
+recordstorage
+recordttl
+recpacketcache
+recparts
+RECRUN
+recsig
+rectrc
+recursorcache
+recursorcmd
+recursorconf
+recursorlog
+recursortests
+recvbytes
+recvcounter
+RECVDSTADDR
+recvec
+recverrors
+RECVPKTINFO
+recvtv
+reczones
+redirectresponses
+redistributors
+refcnt
+refcursor
+referals
+refferals
+refuseemptyar
+refusefouran
+refusenoan
+refusens
+refuseoptinar
+refusetwoar
+regcomp
+regexec
+regexp
+regexstr
+regfree
+reginfo
+regist
+regm
+regmatch
+reinit
+reldir
+relqname
+remdomains
+remlat
+remlen
+remlong
+remotedosec
+remotelen
+remoteloader
+remotelogger
+remotename
+remotering
+remotesec
+remotetype
+rentry
+reparse
+replen
+reqinfo
+reqs
+requestbuilder
+requestorid
+requestorstr
+requestvb
+requeue
+requeueing
+resanswers
+resetring
+residx
+resizering
+resnum
+reso
+resolvconf
+resolveret
+RESOLVERIP
+resourcelimits
+responsebyterate
+RESPONSEIP
+responsestats
+resprulactions
+respsize
+resquestions
+ress
+restfunc
+restoreflags
+resv
+retargetcount
+retargeted
+retkeyset
+retlen
+retline
+retq
+retre
+returncode
+retval
+reuseaddr
+revents
+revsets
+revzone
+revzonedata
+rfds
+rfind
+Rfv
+RFZJVWl
+rgacogne
+rgba
+rhandle
+rhs
+rhscount
+rhsoopts
+rhspos
+ri
+rightcolumn
+rightiter
+ringmeta
+ringname
+ringsize
+riter
+Rj
+Rjd
+Rjk
+rk
+Rkc
+RKEe
+Rkx
+rl
+rlen
+rlim
+rlimit
+RLNl
+rlocks
+rmailbx
+RMCD
+rmd
+rmdir
+Rminor
+rmtree
+RMz
+rnameservers
+rnow
+RNWc
+rollbackmarker
+romap
+RONLY
+rootdnsname
+roothints
+roothintspath
+rootkey
+rootnodot
+rootoid
+rootptr
+rootupdate
+rootzone
+ROqu
+rotxn
+roundtrip
+roundtripped
+routingtag
+rowid
+roystgnr
+Rpa
+rpacket
+rpc
+rpi
+rplookup
+rpmbuild
+rpmdev
+rpmdevtools
+rpmtest
+Rprj
+Rptim
+rpu
+RPZIXFR
+rpzloader
+RPZNSDNAME
+RPZNSIP
+RPZXFR
+rqclass
+rqname
+rqtype
+RQw
+RRA
+rrc
+rrclass
+rrget
+rrhs
+RRIn
+rrout
+rrscount
+rrsigds
+RRSIGIn
+rrsigkey
+rrsigncomp
+RRSIGTTL
+rrterm
+rrthrowaway
+RRto
+rrudr
+rrvalue
+rsakey
+rsamd
+rsautl
+rset
+RShr
+RSIG
+Rsjs
+rsock
+rstrip
+rtag
+rtf
+rthreads
+RTLD
+rtr
+rttl
+rtype
+ruben
+rubenkerkhof
+rubygems
+RUFM
+rulaction
+ruleparams
+ruleresult
+Rumu
+runcond
+runing
+runlevel
+runtests
+RUNWRAPPER
+rusage
+RUTQ
+rval
+RVARS
+RVe
+RVel
+RVM
+RVpn
+RVSBz
+rwl
+rwtxn
+rwxr
+Rxgrk
+RXXw
+RY
+rz
+rzrp
+SAccept
+saccount
+saddr
+safesearch
+salen
+saltlen
+sanitizerflags
+sanitizers
+sargs
+sasl
+savederrno
+sbf
+SBINARYPATH
+SBind
+sbindir
+Sbn
+sbuf
+scalarmult
+scert
+sched
+schemaversion
+scl
+SCombo
+SConnect
+scontrols
+SCRIPTNAME
+SCSV
+sdata
+sdfdhhgj
+sdfsdfs
+sdfsdfsfd
+SDIGBUFSIZE
+sdist
+sdns
+sdomains
+sdynamic
+searchclass
+SEARCHENGINE
+searchkey
+seckey
+secp
+secpollthread
+secretapikey
+secretbox
+secretcommunity
+SECRETKEYBYTES
+secretpassword
+secretuser
+secsfrac
+sectionname
+SECUREDOFFERS
+securezone
+SEEDBYTES
+seekg
+seenauthsoa
+selfanswered
+selfansweredresprulactions
+selfstat
+sendall
+sendfromto
+sendit
+sendlines
+sendmmsg
+sendout
+SENDSRCADDR
+sendto
+sendupdate
+Sensi
+sepa
+seqinit
+seqnext
+Sequanux
+serialtweaker
+servercmd
+serverdiff
+serverdownmaxfails
+serverdownthrottletime
+serverid
+serveridentity
+serveridstr
+serverlist
+serverparseerrors
+serverproc
+serverset
+servfailanswers
+servfailps
+servfailqueryring
+servfailrate
+servfailratio
+servfailremotering
+servfailremotes
+Servlet
+setbuf
+setcd
+setcdviaaction
+setecsaction
+setenv
+setf
+SETFD
+SETFL
+setgroups
+sethook
+SETID
+setitimer
+setmetatable
+setname
+setnegativeandsoa
+SETOF
+setopt
+setprecision
+setrlimit
+setscheduler
+setsid
+SETSIZE
+setsockopt
+settimeout
+settsigkey
+setuptree
+SEv
+SEZ
+sformat
+SHBt
+SHELTEK
+SHFP
+Shk
+shlibs
+shm
+sholder
+showinitializer
+showserversopts
+showvar
+shrinked
+shuf
+shutil
+Shutterstock
+sideeffect
+sidx
+SIGABRT
+SIGALARM
+SIGALRM
+SIGCHLD
+sigdr
+sigexpire
+sigfigs
+SIGFPE
+SIGHUP
+siginception
+SIGKILL
+siglen
+signaturecache
+signingpipe
+Sigoure
+SIGSEGV
+sigset
+sigterm
+SIGVTALRM
+sillyrecords
+simplea
+simplebind
+siz
+sizecounters
+Sj
+skb
+skeyset
+SKIPIT
+skiplua
+skipreasons
+skiprow
+skiptests
+SKnd
+slavecommunicator
+slaveport
+slaveschanged
+slen
+slist
+SListen
+slo
+Slp
+SLQ
+smallquerylargeresponse
+smaps
+smech
+Smirnov
+smlen
+smokeyjoe
+smp
+smt
+smtarg
+SMy
+smysql
+Smz
+sname
+snaplen
+snd
+SNDTIMEO
+sni
+snmpv
+Snv
+SOAAXFR
+soacount
+SOAIn
+SOANo
+SOANXD
+soaret
+soarr
+soatimes
+SOATTL
+socat
+socketaddress
+socketclose
+socketfamily
+socketname
+socketpair
+SOCKETPATH
+socketprotocol
+sockfd
+sockgroup
+socklen
+sockmode
+sockname
+sockowner
+socktype
+sodbc
+sodcrypto
+sodiumsigners
+sokolov
+somedata
+someiostream
+somekey
+sor
+sortcname
+sourceip
+spacelen
+spacket
+sparam
+sparc
+specifictest
+spectest
+speedtest
+SPg
+spgsql
+splitlines
+splot
+spm
+spongerng
+spoofaction
+spoofedcname
+spoofrawrule
+spoofrule
+spos
+SQda
+SQLCHAR
+sqlcmd
+sqlext
+SQLHANDLE
+SQLHDBC
+SQLHENV
+SQLHSTMT
+SQLINTEGER
+SQLLEN
+SQLPOINTER
+SQLRETURN
+SQLSMALLINT
+sqlstate
+sqlstr
+SQLTCHAR
+SQLULEN
+sqname
+sqt
+sqtype
+srcdir
+srcmask
+SRd
+sresult
+srl
+Sro
+SRPMS
+SRSLY
+SRx
+Ssb
+sscanf
+SSetsockopt
+ssh
+ssize
+sslctx
+SSLECDSADNS
+SSLEDDSADNS
+SSLRSADNS
+sslsock
+SSLTLS
+SSLTLSIO
+SSLv
+SSocket
+ssql
+ssqlite
+ssr
+sstorage
+sstream
+sstuff
+Ssystem
+stackoverflow
+stacktrace
+Starovoitov
+startdir
+startpos
+startqueries
+startrecord
+startrecordpos
+startswith
+starttime
+stateenum
+statemachine
+statesbase
+statfmt
+statfunction
+staticmethod
+statmap
+statnames
+statnode
+statnodesince
+statnumentries
+statnumhit
+statnummiss
+Statsv
+stattid
+statuscode
+statvisitor
+stdcxx
+stddev
+stdev
+stdexcept
+stdint
+stdlib
+stest
+stex
+Stogner
+stoi
+stoll
+stormap
+storvect
+storvector
+stoull
+strart
+strbind
+strcasecmp
+strchr
+strcmp
+strdup
+strerr
+strerror
+strftime
+stringappend
+stringbuffer
+stringerror
+stringfgets
+stringstream
+stringtok
+stringvalue
+STRLIT
+strncasecmp
+strncmp
+strncpy
+strptime
+strptr
+strres
+strsignal
+strstr
+strtod
+strtol
+structs
+sttl
+STX
+stype
+subdir
+subgrouping
+Subnetcheck
+subnetlist
+subqueries
+substr
+SUBSTVARS
+subsys
+suckdomains
+suffixmatch
+suffixother
+suffixtype
+SUh
+SUPERMASTERCONF
+suppliedrecords
+suse
+suseconds
+sval
+svg
+SVj
+svn
+svr
+svstat
+Swfi
+SWUOQ
+SXRFNm
+SYlm
+symlink
+Syq
+sysconf
+sysconfdir
+sysmsec
+sysobjects
+sysperc
+systemdsystemunit
+systemdsystemunitdir
+systm
+sz
+Szd
+Szps
+tac
+TAEw
+tagfile
+tagp
+tagsstr
+tagthis
+tahoe
+taiepoch
+TAMg
+tanhqgv
+TARBALLFILE
+TARBALLPREFIX
+TARBALLVERSION
+tardirname
+targetlen
+tbaggery
+Tbz
+tcache
+tcbit
+TCEDNS
+tcgetattr
+TCO
+tcpa
+tcpaaaa
+tcpavgconnduration
+tcpavgconnectionduration
+tcpavgqueriesperconn
+tcpavgqueriesperconnection
+tcpbench
+tcpbytesanswered
+tcpclient
+tcpclientimeouts
+tcpclientthreads
+tcpcurrentconnections
+tcpdiedreaddingresponse
+tcpdiedreadingquery
+tcpdiedreadingresponse
+tcpdiedsendingquery
+tcpdiedsendingresponse
+tcpdownstreamtimeouts
+tcpdrops
+TCPEDNS
+tcpgaveup
+tcphdr
+TCPIO
+tcpiohandler
+tcpka
+tcpnameser
+TCPNo
+tcpnumanswered
+tcpoutqueries
+tcppacket
+tcpqcounter
+tcpquestions
+tcprange
+tcpreadimeouts
+tcpreadtimeouts
+tcpreceiver
+tcpspeeds
+TCPTLS
+tcpwritetimeouts
+TCSANOW
+tcsetattr
+tdate
+tdi
+TDIBy
+TDja
+TDn
+tdomains
+tdsmap
+Tdsza
+TDt
+TDVXa
+Telekom
+tellg
+tempbf
+tempdi
+tempfailure
+tempfailurettlbinding
+tempfile
+temphash
+templ
+templatecounter
+templateline
+templateparts
+templatestep
+templatestop
+templatize
+termios
+testcase
+testcmd
+TESTDRIVER
+testinstance
+testkeysset
+TESTLIST
+testlock
+testmaster
+testmsg
+testname
+testnonzone
+testnum
+testnz
+testresults
+testring
+testringdnsname
+testschema
+testsuffix
+testsuite
+testt
+testuser
+texlive
+tf
+tfh
+TFILE
+TFN
+tfo
+tfunc
+THandler
+theirserial
+thislock
+thisupd
+thiszone
+thr
+threadcloser
+THREADFLAGS
+threadname
+threadwrapper
+throttledout
+throttledqueries
+throttleentries
+THTM
+thu
+tickinterval
+tidx
+timedelta
+timediff
+timegm
+timeoutsec
+timeoutspec
+timersonly
+timesec
+Timespan
+timespec
+timespent
+timeusec
+tinfo
+TINYDNSDATA
+tinydnsloader
+TINYINT
+TIsy
+Tj
+TJR
+tkdb
+tkey
+TKllk
+tkrc
+tl
+tlen
+Tlh
+TLKA
+tlsdhkeytoosmall
+tlsext
+tlshandshakefailures
+tlsinactiveticketkeys
+tlsinappropriatefallback
+TLSIO
+tlslocals
+tlsnewsessions
+tlsnosharedcipher
+tlsqueries
+tlsresumptions
+TLSSNI
+tlsunknownciphertype
+tlsunknownkeyexchangetype
+tlsunknownprotocol
+tlsunknownticketkeys
+tlsunsupportedec
+tlsunsupportedprotocol
+TLSv
+tlsversion
+tlv
+tmeta
+TMi
+Tmk
+tmout
+tmpbuf
+tmpdh
+tmpdir
+tmpfd
+tmpfile
+tmpkey
+tmpnam
+tmpname
+tmpstr
+tmsg
+tname
+tnameservers
+Tno
+tnode
+tnow
+tns
+toaddr
+tocheck
+TODOLIST
+toh
+tok
+tokenizer
+tokill
+TOLO
+tonumber
+toolong
+toolongtobevalid
+toomuchinfo
+topbar
+toport
+toportstr
+toroot
+TOSBy
+toserial
+toskip
+tosql
+totadd
+totar
+totcount
+totcumul
+totlat
+totmperc
+totpairs
+totpcache
+totrdatalen
+totremove
+tottime
+toupper
+toxml
+toysdig
+tozero
+TPL
+tpos
+tptr
+TQBv
+TQJv
+transactiondomain
+transactiondomainid
+trc
+trecords
+treeview
+trilab
+trillian
+trl
+tro
+trollololoooolll
+Truncateds
+truncatemarker
+trustanchorserver
+trustedkeys
+TRw
+tryrdlock
+tryrwlock
+trysuperdomains
+trywait
+trywrlock
+tscomp
+tsdelta
+tsigalgname
+tsigalgorithm
+tsigkeyname
+tsigprevious
+TSIGTCP
+tsigtimersonly
+tsigutils
+tsigverifier
+tsstorage
+Tstbt
+tsuna
+ttd
+ttdi
+ttdindex
+ttdwatch
+ttllimit
+TTLNo
+TTLNX
+TTLRPZ
+ttltooshort
+ttsig
+Tuk
+TUz
+Tvq
+TVU
+TWk
+TWl
+twopt
+txn
+ty
+typedef
+typedns
+typeenum
+typeid
+typeinfo
+typemap
+typename
+typestr
+TYPETOK
+TYX
+tz
+TZDU
+TZOFF
+TZud
+uapi
+ub
+UBIGINT
+ubsan
+UBXc
+ucf
+ucfq
+ucfr
+uchar
+UCLIBC
+ucontext
+UCPEd
+ucspi
+Uctchk
+udiff
+Udipd
+Udm
+udpanswers
+udpbytesanswered
+udpclientsocks
+UDPECS
+UDPEDNS
+udphdr
+udpin
+udpnumanswered
+udpoverruns
+udpsize
+udpsock
+udpspeeds
+udpv
+udrdbp
+UDRYNm
+UDWORD
+ue
+ueberbackend
+Ueuwr
+ufc
+UFt
+ufx
+uglifyjs
+uhry
+uintptr
+uio
+uitoa
+Ujd
+uki
+Ukj
+Ukvz
+UKyg
+ulen
+ulong
+Ulws
+UML
+ums
+uname
+unauthtcp
+unauthudp
+unavailables
+unboundhost
+uncanon
+uncomment
+uncompress
+unconfigured
+undef
+UNDOC
+unescape
+unhexlify
+uninstall
+uniq
+uniquw
+UNIREGISTRYMARKET
+unitdir
+unittest
+unixconnector
+Unixsocket
+Unknownqueries
+unknownrecordcontent
+unlicense
+unloadable
+unmap
+Unpublishing
+unquotify
+unregist
+unreport
+UNsockaddr
+UNSPEC
+Unthrottling
+UNUTTe
+uo
+UOHk
+uom
+Uor
+uordblks
+UPi
+Uploaders
+UPnhs
+uppercasing
+upq
+upto
+urc
+urljoin
+urllib
+urlmap
+urlparse
+urlsafe
+uroot
+usazzz
+useconds
+uselessdrc
+useradd
+userdata
+userfriendly
+usermsec
+userperc
+USHRT
+usleep
+usm
+USs
+ustar
+utexas
+utils
+utime
+Utmc
+utype
+uupdate
+Uuser
+UVARIABLES
+Uwhjf
+uwisza
+uwopt
+UXsnr
+Uxt
+Uy
+UYx
+vab
+validator
+validkeys
+validns
+validpacket
+validresponses
+validrrsets
+valign
+valiter
+vallid
+valmask
+valr
+valrandom
+vals
+varbinary
+varbind
+varchar
+varlist
+varmap
+varval
+Vbi
+vby
+Vcs
+VDLs
+vec
+vect
+veorq
+verboselog
+verhaaltje
+VERIFYHOST
+VERIFYPEER
+verifyzone
+verisign
+verisignlabs
+VERq
+versionbind
+versioncommand
+versionmangle
+versionpdns
+vertpre
+verylongstringlongerthan
+verysecret
+vf
+vfree
+VInf
+vinfolog
+VIuk
+VIW
+Vj
+Vjda
+VK
+vkuiv
+vla
+vlen
+Vlh
+Vll
+vmbe
+vml
+Vmm
+Vmp
+VPA
+vpacket
+vpos
+VPQ
+vptr
+VQBWQ
+VQda
+Vra
+Vrbp
+Vre
+vrooooom
+vrr
+Vscj
+vstate
+vstring
+vstringtok
+vtable
+VTd
+vtnq
+Vuux
+Vw
+VWlr
+vww
+Vx
+vy
+VYBP
+Vyl
+Vz
+Vzd
+WAITALL
+waitpid
+waitpoint
+waitstatus
+waitstatusenum
+waitval
+WAja
+wantsnsid
+wastcp
+wattr
+Waxyl
+wbaw
+wcard
+wcarddomain
+wcmatch
+WCN
+wcname
+WCOREDUMP
+wcplusencloser
+wctx
+wday
+weanswers
+webetter
+webhndlr
+webkit
+WEBPORT
+webrick
+webserv
+webserveropts
+wednserrors
+weekno
+weirdtxt
+wenever
+Werror
+wetimedout
+weunmatched
+WEV
+WEXITSTATUS
+wfile
+WFQTVE
+Wfxq
+wget
+Wgp
+WGqnuy
+WHg
+Whij
+whitelisting
+whoami
+wichert
+WIFEXITED
+WIFSIGNALED
+wildcarddnsname
+wildcardname
+wildzone
+wiplist
+wirelen
+wireshark
+withecs
+withedns
+withednsecs
+withednsnoecs
+withoutedns
+withport
+withval
+Wj
+WJj
+WJO
+Wklm
+wlat
+wli
+wlist
+Wll
+wlocks
+wlon
+Wmpt
+Wmpx
+WNfpw
+Wno
+WNOHANG
+Woi
+Wor
+wordpad
+workdir
+workflow
+Wowie
+Wpv
+WQ
+wrand
+wret
+Wri
+writeability
+WRITEDATA
+WRITEFUNCTION
+writen
+wrlock
+WRONLY
+Wsg
+wshash
+WSIZE
+Wsqvx
+Wswaps
+WTCSr
+WTERMSIG
+wtest
+Wtl
+Wtqlj
+wv
+WVZHd
+wwa
+wwho
+wwjb
+WWo
+wwv
+wwwds
+wwwezdnsit
+wwwpowerdnscom
+WWWPREFIX
+wwx
+wwz
+WX
+WXA
+Wxb
+Wxe
+Wxm
+WXTAH
+Wyc
+WYjld
+wz
+WZERM
+xa
+xaa
+xaaa
+xad
+XADD
+xaf
+XAPI
+xargs
+Xautonomous
+Xbz
+xca
+xce
+xception
+xchange
+Xcml
+xcrypt
+xcy
+xdigits
+XDqdg
+XEz
+XFDj
+xff
+xffverylongstring
+XFRM
+xfrserver
+XFv
+xfz
+xg
+Xga
+xgp
+XHhl
+xhr
+xib
+xit
+xj
+Xk
+XKpw
+Xkqi
+xlabel
+xluajit
+xm
+XMy
+xn
+XNGCH
+xno
+Xnv
+Xo
+Xof
+xor
+xpfcode
+XPFDATA
+xpfdst
+xpfproto
+xpfsrc
+xpfversion
+Xpk
+xpong
+Xq
+xrange
+XRL
+XRq
+xsalsa
+XSKk
+XSyam
+Xtext
+xtics
+xtrue
+xugtk
+XUgz
+xunit
+xv
+xvf
+XXh
+XXxqrt
+XXXXg
+xy
+xyes
+XYk
+xz
+xzvf
+ybndrfg
+YBSI
+yc
+yday
+Yegst
+YEQRBVK
+yetanother
+Yf
+yfb
+YFLAGS
+YGu
+yh
+Yhh
+yiss
+YJou
+yjxe
+YKMY
+ykyb
+YLa
+ylabel
+YLGg
+Ylh
+ylwrap
+Ymf
+YNBIs
+Yng
+Yogz
+YOia
+yop
+YOUNAMEIT
+Yoyodyne
+yq
+Yqi
+YSa
+YSfp
+YTg
+ytics
+YTyj
+YUTh
+YVs
+Yw
+YWA
+YWJx
+YWJYRXp
+YWls
+YWQ
+ywu
+YWVJf
+yx
+yxdomain
+YXos
+YXRR
+yxrrset
+yydebug
+yyerror
+yyin
+yylex
+yylval
+yyparse
+yyrestart
+YYSTYPE
+yyterminate
+yytext
+yywrap
+yyy
+YYYYMMDDH
+yz
+zackw
+ZAWs
+zbefore
+zc
+Zcdnskey
+ZCLASS
+Zd
+Zdelegated
+zdf
+ZDFi
+Zdnssec
+ZDV
+zeromqrb
+zeroport
+Zexample
+Zg
+ZGq
+Zgta
+Zgw
+Zhc
+ZHE
+ZHJp
+ZHml
+ZIf
+Zinsecure
+ziter
+ZJA
+Zjmco
+Zjq
+zl
+zlib
+Zm
+zmakerfunc
+zmakermap
+Zminimal
+zmq
+zmqconnector
+zname
+Znztest
+ZOMG
+zonecontent
+zonecount
+zonecut
+zonedata
+zonedataline
+zonedomain
+zonefilename
+zoneid
+zoneinfo
+zonekind
+zonelevel
+zonelist
+zonemaster
+zoneparsertng
+zonestring
+ZONETOK
+zp
+zpt
+ZQ
+ZQOZ
+ZQSUOf
+zr
+Zrm
+zrr
+Zsecure
+zskds
+zskeys
+Zstest
+Zsu
+Zsub
+zt
+Ztest
+ZTh
+Ztsig
+ztype
+zu
+zugschlus
+Zuhz
+Zun
+Zv
+ZVNIQn
+Zw
+Zwtest
+ZWTQ
+ZWxz
+Zx
+ZXJETl
+ZZj
+zzz
diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt
new file mode 100644 (file)
index 0000000..7871a32
--- /dev/null
@@ -0,0 +1,60 @@
+(?:^|/)ext/
+(?:^|/)go\.mod$
+(?:^|/)go\.sum$
+(?:^|/)m4/
+(?:^|/)package-lock\.json$
+/expected_result
+/test-dnsrecords_cc\.cc$
+SUMS$
+\.ai$
+\.asc$
+\.bmp$
+\.cer$
+\.class$
+\.crl$
+\.crt$
+\.csr$
+\.dll$
+\.DS_Store$
+\.eot$
+\.eps$
+\.exe$
+\.gif$
+\.graffle$
+\.gz$
+\.icns$
+\.ico$
+\.jar$
+\.jpeg$
+\.jpg$
+\.keys?$
+\.lib$
+\.lock$
+\.map$
+\.min\..
+\.mp3$
+\.mp4$
+\.nsec3(?:-optout|)$
+\.otf$
+\.pdf$
+\.pem$
+\.png$
+\.psd$
+\.sig$
+\.so$
+\.svg$
+\.svgz$
+\.tar$
+\.tgz$
+\.ttf$
+\.woff
+\.xcf$
+\.xls
+\.xpm$
+\.yml$
+\.zip$
+^modules/remotebackend/example\.rb$
+^modules/tinydnsbackend/data\.cdb$
+^pdns/dnsdistdist/src_js/
+^pdns/recursordist/html/js/
+^\.github/actions/spell-check/
diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
new file mode 100644 (file)
index 0000000..67f36f2
--- /dev/null
@@ -0,0 +1,2245 @@
+aaaarecord
+aadaf
+aadceba
+aae
+aaldering
+ababd
+abbb
+ABCD
+abcde
+abcdef
+abfe
+abi
+aborttransaction
+ABQ
+Abraitis
+abspath
+acb
+acd
+acf
+acl
+ACPI
+activatedomainkey
+ada
+adadab
+adb
+adcb
+addc
+adddomainkey
+addingrecords
+addprefix
+addsuffix
+adfdffa
+Adiscon
+aee
+aeefcf
+aef
+afdf
+afdlength
+afe
+Afek
+afl
+afnic
+afsdb
+AFYER
+agentx
+agentxperms
+AHM
+ahupowerdns
+Aips
+Aki
+Alenichev
+alexa
+algo
+aliceblue
+allocs
+Altpeter
+amd
+ANCOUNT
+Anderton
+anewid
+anid
+anonymization
+Anonymize
+anotherid
+ANOTHERIPADDRESS
+anothertype
+ansible
+ANSSI
+Antoin
+anycast
+api
+apikey
+APIv
+AQAB
+AQTQ
+ARCHFLAGS
+ARCOUNT
+arecord
+arecvfrom
+Arentz
+ARGS
+arial
+Arjen
+Arjo
+Arnoud
+arpa
+Arsen
+aruba
+asc
+Ascio
+ASd
+Asenov
+ASEP
+Ashish
+ASIN
+asnum
+aspx
+associateddomain
+asyncresolve
+Atlassian
+atoi
+Atomia
+aton
+attr
+atype
+AUTHIP
+authmethod
+Authoritativedoc
+auths
+authzone
+autobuilt
+autocalculation
+autocomplete
+autoconf
+autodetect
+autodetecting
+autodoc
+autogenerated
+automagically
+automake
+Automattic
+autoptr
+autoreconf
+autoserial
+autoslave
+autotools
+AXF
+axfer
+axfr
+axfrfilter
+Baan
+bacc
+backend
+backgrounding
+backport
+Backtick
+backtraces
+BADALG
+BADCOOKIE
+badips
+BADKEY
+BADMODE
+BADNAME
+badserver
+BADSIG
+BADTIME
+BADTRUNC
+BADVERS
+baf
+Baj
+Bakker
+Baltus
+basedn
+basepath
+Bastiaan
+bayour
+bba
+bbb
+bbc
+bbcbbbe
+bbd
+bc
+bca
+bcb
+bcc
+bccd
+bcce
+bce
+bda
+bdd
+bddd
+bded
+bea
+bearggg
+beb
+beda
+beenthere
+bellis
+Belyshev
+benchmarketing
+Benetasso
+Bernd
+bert
+Besselink
+bestwho
+bfa
+bfb
+bfc
+bfcada
+bfe
+bffa
+bgcolor
+Bheca
+Biege
+bigbank
+bigint
+BIGSERIAL
+Bilik
+bindbackend
+binddn
+BINDTODEVICE
+Binero
+binlog
+bla
+Bleker
+blockfilter
+blockquote
+blog
+blogpost
+blogspot
+bmigrate
+bodyfont
+bodysize
+bodywrapper
+bolditalic
+bonafide
+Bortzmeyer
+botnet
+bpf
+bpo
+Brainspark
+Braunoeder
+breadcrumb
+Bremler
+brendangregg
+Briley
+Broens
+broer
+Bromwich
+Brownworth
+Brynjar
+Brzeski
+bsd
+Btw
+Buf
+bufsize
+bugfix
+bugfixes
+bugzilla
+BUILDDIR
+bulc
+bulletinc
+burstable
+bw
+BXvs
+Byterate
+bytestring
+bzero
+bzip
+caa
+caad
+cachekey
+cae
+Cairney
+calculatesoaserial
+calidns
+Cauquil
+cbb
+cbc
+CBF
+Cbjr
+ccac
+ccache
+ccb
+ccbd
+ccc
+ccd
+cce
+ccounts
+cdb
+cdbe
+CDBKV
+cde
+cdece
+cdeede
+cdnskey
+cds
+ceb
+cec
+cece
+cef
+cefcf
+Cegetel
+Cerb
+certusage
+cfe
+cfea
+cfeb
+CFLAGS
+cgi
+CGroup
+changelog
+changeme
+changeset
+changetype
+charset
+chashed
+chbruyand
+chdir
+Chiavacci
+chmod
+chopoff
+chown
+Chqt
+Christof
+chroot
+chrooting
+CHz
+ci
+CIDR
+cjf
+classmethod
+CLASSNUM
+Cloos
+closesocket
+clusions
+cmouse
+cmsg
+cmsghdr
+cn
+cname
+cnamechainresolution
+CNAMEd
+CNAMEDNS
+cnamerecord
+cnf
+Cnma
+cnn
+cockroachlabs
+Cockroft
+codebgcolor
+codeninja
+codetextcolor
+Colemarcus
+colgroup
+collapsiblesidebar
+colm
+comboaddress
+commandline
+committransaction
+conaxis
+config
+configfile
+configname
+configsetting
+configurability
+conntrack
+Conntracking
+Consolas
+constexpr
+controllen
+controlsocket
+coprocess
+coprocesses
+coredumps
+cornercases
+corpit
+CORS
+costypetrisor
+cout
+coverity
+cpp
+cppcheck
+createdb
+createslavedomain
+Cremers
+CRn
+cron
+Cruft
+crv
+cryptokey
+Cryptoki
+cryptopp
+cryptoshop
+css
+csv
+ctime
+ctor
+ctx
+Cuz
+cve
+cvename
+cvs
+cvstrac
+CWD
+CXXFLAGS
+cz
+daa
+dac
+daee
+daemonizing
+daemontools
+daf
+Daganoto
+Danerklint
+dankamongmen
+Darilion
+darix
+Darron
+dataformat
+datasource
+datastore
+datatracker
+Daugaard
+Davids
+Dayneko
+dbaec
+dbe
+dbedfc
+dbf
+dbfile
+dblfilename
+dbname
+dbpf
+dbr
+DBX
+dccc
+dcd
+dcde
+dce
+DCF
+dcobject
+ddaab
+dde
+ddf
+ddns
+DDo
+deactivatedomainkey
+debian
+deboynepollard
+decls
+ded
+Deduktiva
+dedup
+Deduplicate
+defcontent
+defpol
+defttl
+DENIC
+deref
+descclassname
+descname
+Dessel
+dest
+destname
+Detlef
+devicename
+devtoolset
+df
+dfb
+dfd
+dff
+dh
+DHCID
+DHCP
+dhcpd
+dhcpdupdate
+diffs
+DIGESTALGOS
+Digitalus
+dijk
+dilinger
+Directi
+Disqus
+distro
+djbdns
+DKIM
+dlerror
+dlg
+DLLs
+dlmalloc
+DLV
+dmesg
+Dmitry
+dname
+Dnn
+dns
+dnsapi
+dnsbulktest
+dnscache
+dnscrypt
+dnsdemog
+dnsdist
+dnsdistconf
+dnsdistdist
+dnsdistdoc
+dnsdomain
+dnsext
+dnsgram
+dnsheader
+dnskey
+dnsmessage
+dnsname
+dnsnameset
+dnsop
+dnspacket
+dnsparser
+dnspcap
+DNSQ
+dnsquestion
+DNSR
+dnsrecord
+dnsreplay
+dnsresourcerecord
+dnsscan
+dnsscope
+dnssec
+dnssecfromexisting
+DNSSERVER
+dnsspoof
+dnstap
+dnstcpbench
+dnstree
+dnsttl
+dnsupdate
+dnswasher
+dnszone
+dnt
+Dobrawy
+docnamecachelookup
+doctrees
+documentclass
+documentwrapper
+docutils
+doesnotexist
+dofile
+Dohmen
+domaininfo
+domainmetadata
+domainname
+domainrelatedobject
+Donatas
+dontcare
+downsides
+downstreams
+dport
+dq
+drafiei
+Draschl
+droprate
+DRR
+dscontent
+dsrecord
+DSs
+dst
+DTS
+Dufberg
+dumresp
+dynblock
+dynblocklist
+dynblocksref
+dynbpf
+dyndns
+dynhandler
+dynmodules
+eaa
+eac
+eachother
+EAGAIN
+easydns
+eb
+ebaf
+ebd
+ebe
+ebeb
+ebf
+ebfd
+ebpf
+ebpfblocklist
+EBXN
+ecbf
+ecc
+ECCN
+ecdsa
+ECDSAP
+econds
+ECONNRESET
+ecs
+ECSDA
+ecswho
+eda
+edb
+edc
+ede
+edfa
+editline
+edns
+ednsoptions
+ednsoptionview
+ednssubnet
+EDNSTo
+edu
+eea
+eeb
+eec
+EED
+eef
+efb
+efbf
+efc
+efd
+Eieb
+EINTR
+ejones
+EJUGg
+ek
+Ekkelenkamp
+elgoog
+Emph
+endblock
+Enden
+endian
+endif
+endl
+ENOENT
+ENOTCONN
+ent
+entrypoint
+enum
+envoutput
+EOL
+epel
+epoll
+epub
+eqno
+Eriksson
+errlog
+errno
+errorlevels
+esr
+EUI
+EUips
+evildomain
+EVMu
+EWMA
+examplekey
+exceedfuncs
+execfile
+Exort
+externalrefs
+extrahead
+Ezb
+Ezbu
+ezdns
+fabf
+fadec
+FAEEBC
+Faerch
+faf
+failedservers
+failover
+favicon
+FBAE
+fbc
+fbe
+fbf
+fcbd
+fcc
+fcd
+fcde
+fcf
+fcff
+fcgi
+fcontext
+fd
+fda
+fdc
+fdce
+fdd
+fdde
+fdopen
+fedc
+fedoraproject
+feedents
+feedrecord
+feef
+ffaae
+ffb
+ffd
+ffdd
+fff
+ffi
+ffipolicy
+filedescriptor
+Filesystem
+findclientpolicy
+Firefox
+firewalled
+firewalls
+fixednow
+FIXME
+FJZ
+Fki
+FLln
+Florus
+flto
+FNs
+fontname
+footerbgcolor
+footertextcolor
+forfun
+FORMERR
+Fortiguard
+Fortinet's
+forwardzone
+framestream
+freakshow
+freebsd
+freedesktop
+Freenet
+freesans
+freetds
+freshports
+Froemel
+frontend
+fstrm
+fullname
+fulltoc
+func
+Furnell
+Fusl
+FYhvws
+FZq
+gaba
+gacogne
+gatech
+Gavarret
+gcc
+Gci
+gdpr
+Geijn
+genindex
+geobackend
+geoip
+geoipbackend
+geolocated
+Gergely
+Gerritsen
+Gervai
+Gerwin
+getaddrinfo
+getaddrs
+getalldomainmetadata
+getbeforeandafternamesabsolute
+getdomaininfo
+getdomainkeys
+getdomainmetadata
+gethostname
+getlocaladdress
+getn
+getrandom
+getregisteredname
+gettag
+gettext
+gettime
+gettimeofday
+gettsigkey
+Geuze
+GFm
+gh
+Gibheer
+Gieben
+Gillstrom
+github
+githubusercontent
+Gkey
+Gkkq
+glibc
+gmail
+gmake
+Gmb
+gmtime
+GMy
+gmysql
+gmysqlbackend
+gnutls
+godbc
+godbcbackend
+goodmatch
+google
+googleapis
+goracle
+goraclebackend
+GOST
+gouv
+Goxz
+gpgsql
+gpgsqlbackend
+gprof
+gpsqlbackend
+GQj
+GQNy
+grep
+grepping
+grepq
+GSQ
+gsql
+gsqlite
+gss
+gssapi
+gsub
+gtld
+guilabel
+gy
+Gyh
+Gyselinck
+gz
+gzip
+gzipped
+hackerone
+Hakulinen
+Hannu
+haproxy
+hardcode
+hardcoded
+hardcoding
+hardlink
+Harker
+headbgcolor
+headerlink
+headfont
+headlinkcolor
+headtextcolor
+healthcheck
+Heimhilcher
+Helbekkmo
+HELO
+Hendriks
+Henk
+Hensbergen
+Heredoc
+Heuer
+Hev
+hh
+hidesoadetails
+hidettl
+highlighttable
+Hiljanen
+hinfo
+hitrate
+hkraal
+HKSKRWu
+hll
+hlmann
+hmac
+Hmi
+Hoentjen
+Hofstaedtler
+homepage
+Hooimeijer
+hostmaster
+hostname
+Hotmail
+howto
+hpecorp
+hpiers
+hpp
+HPx
+href
+hsm
+htbp
+htm
+html
+htmlescape
+htmlhelp
+http
+httpapi
+httpdomain
+hubert
+hyperlink
+HZQ
+iana
+icann
+ico
+ict
+idprotect
+idq
+idx
+iers
+ietf
+ifdef
+ifportup
+ifurlup
+IFV
+ihsinme
+IJajghd
+IKOYz
+illumos
+img
+Imhard
+incbin
+includeboilerplate
+includerings
+indexa
+indexassociated
+indextable
+inet
+infolog
+infosecinstitute
+ini
+initscript
+Inno
+innodb
+inode
+installable
+interop
+interoperability
+interoperation
+inzk
+iostream
+iowait
+ip
+IPADDRESS
+IPbackend
+ipc
+ipcipher
+ipcom
+ipcrypt
+ipdecrypt
+ipencrypt
+ipfilter
+IPSECKEY
+iptables
+iputils
+ipv
+IQIT
+IQuery
+irc
+isane
+isc
+ismaster
+isoc
+isp
+ispell
+isql
+isse
+issuecomment
+ixfr
+ixfrdist
+ixplore
+Jakub
+Jakum
+janeczku
+Jatko
+Jaury
+Jauvin
+javascript
+JCf
+Jck
+Jeftovic
+Jelte
+Jermar
+Jeroen
+jessie
+jj
+Joaqu
+Jong
+Jorn
+journalctl
+journald
+jp
+jpmens
+jq
+json
+jsondomain
+JSONP
+jsonstat
+ju
+Juergen
+jumpbox
+Juraj
+jye
+Kaminsky
+Kaseorg
+KCtsq
+kd
+Kdhcp
+Kdhcpdupdate
+Kees
+kerberos
+Kerkhof
+KEYBITS
+keyblock
+keydir
+keyfile
+keygen
+keyname
+keypair
+keypairgen
+keyroll
+keysearch
+keysize
+keytab
+KEYTAG
+keytype
+keywordmatches
+kickdaddy
+Kirill
+Klebermass
+koch
+Kockum
+Kolkman
+kom
+Konqueror
+Koos
+koqv
+Kovacic
+kp
+kqueue
+KQZX
+krb
+Krist
+Krul
+ksk
+kskroll
+kskrollcdnskey
+Kuehrer
+KUXs
+kvs
+KX
+Kxxnux
+Kyc
+Ladot
+Lafon
+Lakkas
+largeanswer
+Laros
+lastcheck
+Lastdrager
+lastnotified
+latexpdf
+latlon
+latlonloc
+latomic
+lauch
+Laurient
+Laursen
+Lbackend
+LCUP
+LDA
+ldap
+ldapbackend
+ldflags
+ldif
+ldns
+LDR
+Leen
+Lemoine
+len
+lessthan
+Lesuisse
+lethalgroup
+letsencrypt
+letterpaper
+LFya
+libatomic
+libc
+libcrypto
+libcryptopp
+libcurl
+libdecaf
+libdir
+libedit
+libfstrm
+libgcc
+libgeoip
+libh
+libmaxminddb
+libmongo
+libmysqlclient
+libnss
+libpcap
+libpq
+libpqpp
+libresolv
+libressl
+librt
+libsodium
+libsofthsm
+libssl
+libsystemd
+libtdsodbc
+libyaml
+libzmq
+Lindqvist
+linenos
+linenum
+linkcolor
+lintian
+linux
+linuxnetworks
+Lior
+listinfo
+literalinclude
+llvm
+lmdb
+lmdbbackend
+LMDBKV
+lnsl
+loadbalancer
+loadbalancing
+localaddr
+localhost
+localip
+LOCALSTATEDIR
+localtime
+localtoc
+locaweb
+lochiiconnectivity
+logfile
+loglevel
+logmessage
+logrotate
+lon
+Loopia
+Lorbach
+lordievader
+Louwers
+loweralpha
+lowerroman
+lresolv
+Lrhazi
+lrt
+lsock
+lsocket
+lte
+lua
+luaaction
+luabackend
+luac
+luajit
+luaroundrobin
+luarule
+luawrapper
+Lutter
+Luuk
+Lwc
+Lwz
+LYg
+lz
+Maik
+Maikel
+MAILA
+MAILB
+Majer
+Makefiles
+malcrafted
+malloc
+malware
+Mamane
+Mandriva
+manpage
+mapasync
+mariadb
+Markmann
+maskv
+Massar
+matchtype
+Matthijs
+maxdepth
+MAXINT
+maxlistdepth
+maxmind
+maxqps
+MAXVALUE
+mbed
+mbedtls
+MBOXFW
+mbytes
+MDB
+Meerwald
+Mekking
+MEMLOCK
+Memusage
+menuselection
+metadata
+metadatabase
+metainformation
+metricnames
+metricscarbon
+Meulen
+Michiel
+Microsoft
+Miek
+Miell
+Mieslinger
+Milas
+Mimimization
+minbody
+mindex
+MINFO
+minipatch
+misconfigured
+mjt
+mkuchar
+mmap
+mmdb
+mname
+mnordhoff
+MOADNS
+Modderman
+modifyingpolicydecisions
+modindex
+monshouwer
+Moq
+motherboards
+mozilla
+mplexer
+Mpqhbg
+MQ
+mrtg
+msdcs
+MSDNS
+msphinx
+mssql
+mtasker
+MTEUl
+mthread
+MUar
+Mulholland
+multiline
+multimaster
+multithreading
+mundsson
+munmap
+Muraro
+musl
+mutex
+Mwaikambo
+mx
+mxrecord
+mybackend
+mycompany
+mydns
+mydnsbackend
+mydomain
+MYec
+myhost
+myinstance
+myname
+mypassword
+mypgsql
+mysecretpassword
+myset
+myspecialmetric
+mysql
+mysqlbackend
+mysqld
+mytsigkey
+myuser
+mywebapp
+NAi
+namedroppers
+nameserver
+nameserving
+namespace
+namserver
+naptr
+Nauck
+Navarrete
+nc
+Ndd
+nearmiss
+nearmisses
+Nederlandse
+nedmalloc
+negativetrustanchor
+negcache
+negquery
+neheb
+Nelless
+neosystem
+Netblock
+netfilter
+netherlabs
+netinet
+netmask
+netmaskgroup
+netsnmp
+NETWORKMASK
+Neue
+Neuf
+newcontent
+nextval
+nf
+nic
+nimber
+Nixu
+nkey
+nmg
+nn
+Nncqx
+noaction
+noad
+noall
+nocache
+nocookie
+nodata
+NODELAY
+noedns
+noerrors
+nometasync
+Nominet
+nonexist
+Nonnekes
+noout
+noping
+noport
+nosniff
+nostrip
+NOSUBDIR
+nosync
+Notaras
+NOTAUTH
+NOTIMP
+NOTPARALLEL
+notrack
+NOTZONE
+Novell
+nproxy
+NPTL
+NSCOUNT
+NSCx
+nsd
+NSECx
+NSes
+nsid
+nsis
+nsname
+NSQ
+nsrecord
+NSS
+nsset
+nsspeeds
+NSTTL
+nsupdate
+nta
+ntei
+ntlworld
+nullptr
+NULs
+NUMA
+numreceived
+nx
+nxd
+NXDATA
+nxdomain
+NXRRSET
+oarc
+oauth
+Obermayer
+obidos
+objectclass
+Obser
+obspm
+ocsp
+odbc
+odbcbackend
+odbcinst
+Oddy
+Odintsov
+Oestreicher
+offsite
+Ofpy
+oftc
+OIDs
+Olafur
+Omroep
+openapi
+openbsd
+opendbx
+openpgpkey
+opensc
+openssl
+opensuse
+openwall
+Opmeer
+optcode
+Opteron
+optmem
+optout
+oraclebackend
+ordername
+orsn
+Oservers
+ostringstream
+OSX
+otherdomain
+otherpool
+ou
+OUHTU
+ourname
+ourserial
+ourtime
+OUTFILE
+outpacket
+outputbuffer
+outqueries
+PACKAGEVERSION
+packetcache
+packethandler
+papersize
+paramater
+PARAMKEYWORDS
+params
+passphrase
+passthrough
+passthru
+PATC
+patchlevels
+pathconfig
+pathto
+pawal
+pb
+Pbackend
+pcap
+PCAPFILE
+pdf
+pdns
+pdnsbackend
+pdnscontrol
+pdnsldap
+pdnslog
+pdnsmgrd
+pdnsodbx
+pdnsrandom
+pdnssec
+pdnstest
+pdnsutil
+Peeters
+Pels
+pem
+Penev
+perl
+Perroud
+Pertubation
+pez
+Pfetzing
+pgmysql
+pgmysqlbackend
+pgp
+pgpsql
+pgsql
+phishing
+phonedph
+php
+pickclosest
+pickrandom
+pickwhashed
+pickwrandom
+pid
+piddir
+pidfile
+pidof
+pieter
+pieterlexis
+pilindex
+Pinski
+pipebackend
+pipermail
+PIV
+pkcs
+PKGLIBDIR
+PKI
+PKTINFO
+placeholders
+PLt
+Plusnet
+plzz
+pmtmr
+pnds
+png
+Poelov
+pointsize
+polarssl
+policyactions
+policyevent
+policykinds
+policyname
+policytypes
+pollmplexer
+Ponomarev
+poolers
+poolname
+portnum
+portnumber
+postgre
+postgresql
+postinst
+postresolve
+powerdns
+powerdnsrecursor
+powerdnssec
+powerldap
+powerpc
+ppc
+pragma
+Predota
+preoutquery
+Preproc
+prequery
+prereleases
+prerpz
+presigned
+presignedness
+PRId
+primetime
+princ
+prioritization
+privatekey
+privs
+PRNG
+Proba
+progid
+protobuf
+PROTOC
+providername
+proxyprotocol
+proxyprotocolvalues
+PROXYv
+pseudonymize
+pseudorecord
+psql
+pthread
+ptr
+ptrrecord
+Publieke
+publishdomainkey
+pullreq
+pwfm
+px
+py
+pygments
+pypi
+Pyry
+PYv
+PZFX
+qa
+Qag
+qclass
+qdcount
+qdomain
+qgen
+Qkj
+qlen
+Qlim
+Qll
+qname
+qperq
+Qpkv
+qps
+QPSIP
+qpslimits
+QRate
+qsize
+QSLj
+qthread
+qtype
+qtypelist
+querycache
+querycount
+quickstart
+QYCIHp
+qytpe
+ragel
+randombackend
+randombit
+randombytes
+randomisation
+randomises
+randomloader
+rapidjson
+raspbian
+rb
+RBL
+RBUb
+rcode
+rcodezero
+Rcvbuf
+rcvmmsg
+rdata
+rdqueries
+rdynamic
+readline
+README
+readonly
+realtime
+reconnections
+recursor
+recursordist
+Recursordoc
+Recuweb
+recv
+recvbuf
+recverr
+recvfrom
+recvmmsg
+recvmsg
+redelegations
+redhat
+redjack
+reentrantly
+refactor
+Refactoring
+refcount
+refman
+refreh
+refuseds
+regex
+reid
+reimplementation
+Reinier
+Rejo
+relbar
+relbarbgcolor
+relbarlinkcolor
+relbartextcolor
+relro
+Remco
+remi
+remoteaddr
+remotebackend
+remoteip
+remoting
+removedomainkey
+replacerrset
+requery
+resolv
+respawn
+respawning
+respout
+respsizes
+Resync
+resynchronise
+retransfering
+reuseds
+reuseport
+Reuwiei
+rfc
+rhel
+Rietz
+rightsidebar
+Rijsdijk
+ringbuffer
+rkey
+RLIMIT
+rname
+rng
+rocommunity
+Roel
+Rosmalen
+roundrobin
+rp
+RPATH
+rping
+RPMS
+rpz
+rpzstatistics
+Rqvg
+rr
+rrcontent
+rrd
+rrdata
+rrdtool
+rrname
+rrset
+rrsig
+rrtype
+rsa
+rsasha
+RSP
+rst
+rsync
+ru
+Rueckert
+Rul
+rulesets
+Ruthensteiner
+rv
+Rvd
+rw
+Rwgj
+rwlock
+Rzs
+Sakaguchi
+saltsa
+sandboxing
+Sangwhan
+SASL
+Saunalahti
+saxfr
+SBF
+sbin
+Sbvka
+scalability
+SCHED
+Scheffler
+Schlich
+Scholten
+Schryver
+Schueler
+schwer
+SCn
+scopebits
+scopemask
+scriptable
+scriptlets
+sdb
+sdfoijdfio
+sdig
+secpoll
+securesphere
+securitypolicy
+securitypolling
+seealso
+segfault
+selectmplexer
+selinux
+senderrors
+Sendetzky
+sendmsg
+sensistive
+Sergey
+servername
+serverpools
+serverselection
+servfail
+setaffinity
+setcontent
+setdomainmetadata
+setgid
+seting
+setkey
+setnotified
+SETPIPE
+settting
+setuptools
+setvariable
+Sgs
+Shafir
+shantikulkarni
+shinsterneck
+Shm
+showdetails
+showflags
+Shukla
+sid
+SIddm
+sidebarbgcolor
+sidebarbtncolor
+sidebarbutton
+sidebarlinkcolor
+sidebarlogo
+sidebarsourcelink
+sidebartextcolor
+sidebarwidth
+sidn
+SIGABR
+sigint
+Sigmod
+signedness
+Signingpiper
+signpipe
+signttl
+signzone
+SIGPIPE
+sigs
+SIGTERM
+SIGUSR
+singlethreaded
+Sinstallation
+Sipek
+sizeof
+Sjoerd
+slapd
+slapindex
+slaveness
+SLES
+smartcard
+smb
+smellyspice
+smfgo
+smimea
+smn
+smtp
+Smurthwaite
+Snarked
+sndbuf
+SNI
+snmp
+snmpd
+SNMPv
+snprintf
+soa
+soadata
+soarecord
+sockaddr
+socketdir
+softhsm
+solaris
+SOLc
+Soldaat
+SOMAXCONN
+somedomain
+Sonix
+Soref
+Soroceanu
+sortlist
+sourcecode
+SOURCEDIR
+sourceforge
+sourceware
+Spaans
+Spackages
+spam
+Sparc
+spf
+SPHINXBUILD
+sphinxcontrib
+sphinxjsondomain
+SPHINXOPTS
+SPHINXPROJ
+sphinxsidebar
+sphinxsidebarwrapper
+splitsetup
+sprezzos
+Spruyt
+SQk
+sql
+sqlite
+srandom
+src
+srcname
+SRecord
+Srule
+srv
+SSH
+sshfp
+ssi
+ssl
+sslmode
+sslrootcert
+SSQ
+SSql
+stacksize
+standalone
+starttls
+starttransaction
+Stasic
+statbag
+statbas
+statisticitem
+statm
+stbuehler
+stderr
+stdin
+stdio
+stdout
+Stef
+Steinbuch
+Stichting
+stickysidebar
+Stillaway
+Stirnimann
+stmt
+Stol
+Storbeck
+stou
+stoul
+strcasestr
+stringmatch
+strlen
+strpos
+stubquery
+stubresolver
+stunnel
+Stussy
+stutiredboy
+stylesheet
+subdomain
+subkey
+subnetmask
+sudo
+suffixmatchtree
+supermaster
+supermasterbackend
+supernotification
+supersecretpassword
+superslave
+superslaving
+supervisord
+Surfnet
+SUSE
+swapcontext
+swe
+swoga
+sx
+Symlink
+syncres
+sys
+sysadmin
+syscall
+SYSCONFDIR
+sysctl
+Sysdream
+syslog
+systemcall
+systemctl
+systemd
+sysv
+SYW
+SZ
+szw
+tarball
+Tarjei
+Tarnell
+tbhandler
+tbody
+tcely
+tcp
+tcpdump
+TCPKEEPALIVE
+TCPv
+Tctk
+td
+teeaction
+Telenet
+testrunner
+testsdir
+texinfo
+textcolor
+tfoot
+Tful
+TGJGVc
+thead
+thel
+thelog
+Thessalonikefs
+Thiago
+thinko
+Thomassen
+threadsafe
+throttlemap
+thrysoee
+Thu
+timedipsetrule
+timedout
+timeframe
+timeline
+timesource
+timeval
+timezone
+tinycdb
+tinydns
+tinydnsbackend
+tisr
+TKEY
+tld
+tls
+tlsa
+tmp
+tmpfs
+tobool
+toc
+toctree
+todo
+toint
+tokenuser
+tolower
+Tolstov
+Toshifumi
+tostring
+Travaille
+tribool
+trunc
+trustanchor
+trusteer
+trx
+trxid
+tsc
+tsig
+tsigalgo
+tsigkey
+tsigname
+tsigsecret
+tstamp
+TSU
+tt
+ttl
+Tuinder
+tunables
+tuomi
+Tushuizen
+Tuu
+Tuxis
+TVJRU
+tw
+tylerneylon
+typedefs
+typenames
+tyu
+TZ
+ualberta
+Ubo
+ubuntu
+udp
+udpqueryresponse
+UDPv
+udr
+Ueber
+Ueli
+uid
+uint
+Uisms
+uj
+uk
+ul
+ulimit
+Umq
+unauth
+unbreak
+uncached
+unescaping
+unfresh
+unhash
+unicode
+uninett
+uninitialised
+Uninstaller
+unistd
+unitialized
+unixodbc
+unixtime
+unparseable
+Unprocessable
+unpublish
+unpublishdomainkey
+unreachables
+unregister
+unshadowing
+untruncated
+unzero
+upd
+updatepolicy
+upperalpha
+upperroman
+urandom
+uri
+url
+urlencoded
+usec
+usecase
+useragent
+userbase
+username
+userspace
+usr
+utf
+UUHJWZg
+uuid
+uwaterloo
+Uwcjbp
+Uwi
+Uyypn
+Valentei
+Valentini
+valgrind
+validationstates
+validators
+Valkenburg
+Vandalon
+vandergaast
+Vandoren
+VARCHAR
+varname
+Vasiliy
+VBG
+Vbt
+VDRAW
+VDz
+Veldhuyzen
+venv
+Verheijen
+Verisign
+Verschuren
+versionadded
+versionchanged
+versionmodified
+vh
+viewcode
+virtualenv
+virtualized
+visitedlinkcolor
+vixie
+vm
+Vn
+Voegeli
+Volker
+voxel
+Vranken
+Vsgoi
+Vwgbclzx
+VY
+vzu
+WAITFORONE
+wal
+wallclock
+warnlog
+Wascwa
+Wbq
+webbased
+webdocs
+webhandler
+webpassword
+webserver
+website
+Webspider
+weightparams
+Weimer
+Welzel
+Wessels
+westes
+Wevers
+wextra
+Wgx
+whashed
+whitelist
+Wia
+Wichert
+Wieger
+Wielicki
+Wijk
+Wijnand
+Wijngaards
+wiki
+wikipedia
+wil
+wildcarded
+wildcards
+Willcott
+windr
+Winfried
+wireformat
+wirelength
+Wisiol
+wmc
+Wmissing
+Wojas
+workaround
+Worldnic
+would've
+wouter
+wpad
+wproduction
+wrandom
+Wrange
+Wredundant
+writev
+Wshadow
+wuh
+ww
+www
+WYbw
+Wzqf
+Wzs
+Xander
+xchacha
+Xchange
+xdb
+Xek
+Xeon
+xf
+xfr
+xh
+xhtml
+XM
+xml
+xorbooter
+xpf
+XPFCODE
+XPFDST
+XPFPROTO
+XPFSRC
+XPFVERSION
+Xpw
+XRecord
+XSalsa
+xss
+XYR
+yahttp
+yaml
+yb
+YDyzc
+Yehuda
+YG
+YHk
+YIBo
+Yiu
+Yj
+yk
+YLCOy
+Ylitalo
+yml
+YMMV
+yourcompany
+yourdomain
+yourorganization
+yoursecret
+yp
+YQ
+yubikey
+Yx
+YXDOMAIN
+YXRRSET
+yy
+YYYYMMD
+YYYYMMDD
+YYYYMMDDSS
+Zealey
+zeha
+Zengers
+Zengin
+zeromq
+Zfndz
+Zhf
+zilopbg
+ZJad
+Zmd
+ZNLY
+Zoag
+zonecryptokey
+zonefile
+zonemetadata
+zonename
+zoneparser
+zonetransfer
+Zonneveld
+ZRev
+zsk
+zskroll
+Zt
+Zumstrull
+Zwane
+zz
+zzyzz
diff --git a/.github/actions/spell-check/only.txt b/.github/actions/spell-check/only.txt
new file mode 100644 (file)
index 0000000..b8e3d53
--- /dev/null
@@ -0,0 +1,2 @@
+/docs/
+^docs/
diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt
new file mode 100644 (file)
index 0000000..cf83b34
--- /dev/null
@@ -0,0 +1,6 @@
+(?:0[Xx]|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
+addNSECRecordToLW.*DNSName.*powerdnt.com.*QType::NS.*res->d_records
+data:[a-zA-Z=;,/0-9+]+
+BOOST_CHECK_EQUAL\(b64, "[a-zA-Z=;,/0-9+]+"
+\b([A-Za-z])\1{3,}\b
+C(?:XX|)FLAGS="[^"]*"
index 9083017e8581f82d28408bca930616c736515062..52a2f397f9f8ba3dbd09e0dd1c5ad706e93d9cc9 100644 (file)
@@ -8,13 +8,13 @@ jobs:
       uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
       with:
         oss-fuzz-project-name: 'powerdns'
-        dry-run: true
+        dry-run: false
     - name: Run Fuzzers
       uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
       with:
         oss-fuzz-project-name: 'powerdns'
         fuzz-seconds: 600
-        dry-run: true
+        dry-run: false
     - name: Upload Crash
       uses: actions/upload-artifact@v1
       if: failure()
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
new file mode 100644 (file)
index 0000000..d3dcbfa
--- /dev/null
@@ -0,0 +1,33 @@
+name: Spell checking
+on:
+  push:
+    branches:
+      - "**"
+    tags-ignore:
+      - "**"
+    paths:
+      - "docs/**"
+      - "**/docs/**"
+  pull_request:
+    branches:
+      - "**"
+    tags-ignore:
+      - "**"
+    paths:
+      - "docs/**"
+      - "**/docs/**"
+    types: ['opened', 'reopened', 'synchronize']
+
+jobs:
+  build:
+    name: Spell checking
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2.0.0
+      with:
+        fetch-depth: 5
+    - uses: check-spelling/check-spelling@0.0.16-alpha
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        bucket: .github/actions
+        project: spell-check
index ffc7603b768543328e422f9567103f3d81f06dc3..7fedb7a197ae370b3c6fd29fb1325d7b736f3c8f 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,10 +5,10 @@ exact license and exception used).
 All documentation can be found on https://doc.powerdns.com/
 
 This file may lag behind at times. For most recent updates, always check
-https://doc.powerdns.com/md/changelog/.
+https://doc.powerdns.com/authoritative/changelog/
 
 Another good place to look for information is:
-https://doc.powerdns.com/md/appendix/compiling-powerdns/
+https://doc.powerdns.com/authoritative/appendices/compiling.html
 
 To file bugs, head towards:
 https://github.com/PowerDNS/pdns/issues
@@ -42,7 +42,7 @@ This will bring up a USAGE-page which will explain how to build the different re
 
 COMPILING Authoritative Server
 ------------------------------
-The PowerDNS Authoritative Server depends on Boost, OpenSSL and requires a
+The PowerDNS Authoritative Server depends on Boost, OpenSSL and Lua, and requires a
 compiler with C++-2011 support.
 
 On Debian 9, the following is useful:
@@ -119,62 +119,6 @@ Building the HTML documentation
 
 The HTML documentation (as seen [on the PowerDNS docs site](https://doc.powerdns.com/authoritative/)) is built from ReStructured Text (rst) files located in `docs`. They are compiled into HTML files using [Sphinx](http://www.sphinx-doc.org/en/master/index.html), a documentation generator tool which is built in Python.
 
-**Using a normal Python installation**
-
-For those simply contributing to the documentation, this avoids needing to install the various build
-tools and other dependencies.
-
-Install Python 2.7 or Python 3 (preferable) if you don't yet have it installed. On some operating
-systems you may also have to install `python3-pip` or similarly named.
-
-Ubuntu 16.04 / 18.04
-
-```sh
-apt update
-apt install python3 python3-pip python3-venv
-```
-
-macOS (using homebrew)
-
-```sh
-brew install python3
-```
-
-Update your `pip` and install/update `virtualenv` to avoid problems:
-
-```sh
-# for python2, use "pip" instead of "pip3"
-pip3 install -U pip
-pip3 install -U virtualenv
-```
-
-Enter the repository's `docs` folder, set up the virtualenv, and install the requirements
-
-```sh
-cd docs
-# for python2, use "virtualenv .venv" instead
-python3 -m venv .venv
-source .venv/bin/activate
-# The virtualenv may use an older pip, so upgrade it again
-pip3 install -U pip setuptools setuptools-git
-# Now you can install the requirements
-pip3 install -r requirements.txt
-```
-
-Finally, you can build the documentation:
-
-```sh
-sphinx-build . html-docs
-```
-
-Note: If your shell has problems finding sphinx-build, try using `.venv/bin/sphinx-build` instead.
-
-The HTML documentation is now available in `html-docs`.
-
-**Using the build tools**
-
-This method is preferable for those who already have a working build environment for PowerDNS.
-
 Install the dependencies under "COMPILING", and run autoreconf if you haven't already:
 
 ```sh
@@ -190,12 +134,6 @@ make html-docs
 
 The HTML documentation will now be available in `html-docs`.
 
-Solaris Notes
--------------
-Use a recent gcc (and other build tools), possibly from Solaris 11 IPS.
-
-If you encounter problems with the Solaris make, gmake is advised.
-
 FreeBSD Notes
 -------------
 You need to compile using gmake - regular make only appears to work, but doesn't in fact. Use gmake, not make.
index adfc8023d60da495c3b983301f61173070f4ca4f..7ec7c58e40c248284bee2b21b64799239eccd927 100644 (file)
@@ -1,7 +1,7 @@
 PowerDNS and dnsdist Security Policy
 ====================================
 
-If you have a security problem to report, please email us at both security@powerdns.com and ahu@ds9a.nl.
+If you have a security problem to report, please email us at both peter.van.dijk@powerdns.com and remi.gacogne@powerdns.com.
 In case you want to encrypt your report using PGP, please use: https://www.powerdns.com/powerdns-keyblock.asc
 
 Please do not mail security issues to public lists, nor file a ticket, unless we do not get back to you in a timely manner.
index 981f646bc0c2e2fc20ed1ce71c91967e4daf1491..174c0fa686848a58269883f113efdca55ac55dc5 100755 (executable)
@@ -35,7 +35,8 @@ for pr in arguments.pullrequest:
     try:
         if access_token:
             res = requests.get('https://api.github.com/repos/PowerDNS/pdns/pulls/'
-                               '{}?access_token={}'.format(pr, access_token))
+                               '{}'.format(pr),
+                               headers={'Authorization': 'token ' + access_token})
         else:
             res = requests.get('https://api.github.com/repos/PowerDNS/pdns/pulls/'
                                '{}'.format(pr), auth=httpAuth)
@@ -69,13 +70,14 @@ for pr in arguments.pullrequest:
                                                 'omoerbeek']:
         try:
             if access_token:
-                user_info = requests.get(pr_info['user']['url'] + '?access_token=' + access_token, auth=httpAuth).json()
+                user_info = requests.get(pr_info['user']['url'],
+                                         headers={'Authorization': 'token ' + access_token}).json()
             else:
                 user_info = requests.get(pr_info['user']['url'], auth=httpAuth).json()
         except (requests.exceptions.HTTPError, ValueError) as e:
             print(e)
             sys.exit(1)
-        if 'name'in user_info:
+        if 'name' in user_info:
             out += ' ({})'.format(user_info['name'])
         else:
             out += ' (@{})'.format(user_info['login'])
index 9026e32755cdeb9a996ec7f42e26f4e19c6d1000..7b7822d1b282f7e0cd564d754aa76333e1c8e86e 100755 (executable)
@@ -3,13 +3,30 @@
 # - `docker build --no-cache --pull --file Dockerfile.auth-41.ubuntu-bionic --tag auth-41.ubuntu-bionic .`
 # - `docker run -it auth-41.ubuntu-bionic`
 # - `docker run -it auth-41.ubuntu-bionic /bin/bash`
+#     - `dnsdist --verbose 9.9.9.9`
 #     - `pdns_recursor`
+#     - `pdns_server`
+#
+# Remi contributed this snippet:
+#
+#     #!/bin/bash
+#
+#     readonly product=dnsdist-15
+#
+#     for version in centos-6 centos-7 centos-8 debian-buster debian-stretch ubuntu-bionic ubuntu-xenial; do
+#       docker build --no-cache --pull --file Dockerfile.${product}.${version} --tag ${product}.${version} .
+#     done
+#
+#     for version in centos-6 centos-7 centos-8 debian-buster debian-stretch ubuntu-bionic ubuntu-xenial; do
+#       docker run -it ${product}.${version} dnsdist -v 9.9.9.9
+#     done
 
 if [ "$1" = "" -o "$1" = "-?" -o "$1" = "-h" -o "$1" = "--help" ]; then
     echo "Usage: generate-repo-files.sh RELEASE"
     echo
-    echo "  • RELEASE: [ auth-40 | auth-41 | auth-42 | auth-43 |"
-    echo "               rec-40 | rec-41 | rec-42 | rec-43 ]"
+    echo "  • RELEASE: [ auth-40 | auth-41 | auth-42 | auth-43 | auth-master |"
+    echo "               rec-40 | rec-41 | rec-42 | rec-43 | rec-44 | rec-master |"
+    echo "               dnsdist-15 | dnsdist-master ]"
     exit 1
 fi
 
@@ -21,6 +38,12 @@ write_centos()
     PKG=$2
     CMD=$3
 
+    if [ "$VERSION" = "8" ]; then
+        CENTOS8_FLAGS="--nobest"
+    else
+        CENTOS8_FLAGS=""
+    fi
+
     cat <<EOF > Dockerfile.$RELEASE.$OS-$VERSION
 FROM $OS:$VERSION
 
@@ -30,15 +53,20 @@ EOF
     if [ "$VERSION" = "6" -o "$VERSION" = "7" ]; then
         cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 RUN yum install -y yum-plugin-priorities
+EOF
+    elif [ "$RELEASE" = "dnsdist-15" -a "$VERSION" = "8" ]; then
+        cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
+RUN dnf install -y 'dnf-command(config-manager)'
+RUN dnf config-manager --set-enabled PowerTools
 EOF
     fi
 
     cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 RUN curl -o /etc/yum.repos.d/powerdns-$RELEASE.repo https://repo.powerdns.com/repo-files/$OS-$RELEASE.repo
-RUN yum install -y $PKG
+RUN yum install --assumeyes $CENTOS8_FLAGS $PKG
 EOF
 
-    if [ "$RELEASE" = "rec-43" ]; then
+    if [ "$RELEASE" = "rec-43"  -o "$RELEASE" = "rec-44" ]; then
     cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 
 RUN mkdir /var/run/pdns-recursor
@@ -61,6 +89,16 @@ write_debian_or_ubuntu()
 
     cat <<EOF > pdns.list.$RELEASE.$OS-$VERSION
 deb [arch=amd64] http://repo.powerdns.com/$OS $VERSION-$RELEASE main
+EOF
+
+    # For the following two maybe only create depending on package, but
+    # it's not really a big deal.
+
+    # if not exists
+    cat <<EOF > dnsdist.debian-and-ubuntu
+Package: dnsdist*
+Pin: origin repo.powerdns.com
+Pin-Priority: 600
 EOF
 
     # if not exists
@@ -74,18 +112,20 @@ EOF
 FROM $OS:$VERSION
 
 RUN apt-get update
-RUN apt-get install -y curl gnupg dnsutils
+RUN apt-get install -y curl gnupg dnsutils apt-transport-https
 
+COPY dnsdist.debian-and-ubuntu /etc/apt/preferences.d/dnsdist
 COPY pdns.debian-and-ubuntu /etc/apt/preferences.d/pdns
 COPY pdns.list.$RELEASE.$OS-$VERSION /etc/apt/sources.list.d/pdns.list
 
 RUN curl https://repo.powerdns.com/FD380FBB-pub.asc | apt-key add -
+RUN curl https://repo.powerdns.com/CBC8B383-pub.asc | apt-key add -
 RUN apt-get update
 RUN apt-get install -y $PKG
 EOF
 
-    if [ "$RELEASE" = "rec-43" ]; then
-    cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
+    if [ "$RELEASE" = "rec-43" -o "$RELEASE" = "rec-44" ]; then
+        cat <<EOF >> Dockerfile.$RELEASE.$OS-$VERSION
 
 RUN mkdir /var/run/pdns-recursor
 EOF
@@ -115,19 +155,23 @@ RELEASE=$1
 if [ "$RELEASE" = "auth-40" ]; then
     write_centos 6 pdns pdns_server
     write_centos 7 pdns pdns_server
-    write_debian jessie pdns-server pdns_server
     write_debian stretch pdns-server pdns_server
-    write_ubuntu trusty pdns-server pdns_server
     write_ubuntu xenial pdns-server pdns_server
 elif [ "$RELEASE" = "auth-41" ]; then
     write_centos 6 pdns pdns_server
     write_centos 7 pdns pdns_server
-    write_debian jessie pdns-server pdns_server
     write_debian stretch pdns-server pdns_server
-    write_ubuntu trusty pdns-server pdns_server
     write_ubuntu xenial pdns-server pdns_server
     write_ubuntu bionic pdns-server pdns_server
-elif [ "$RELEASE" = "auth-42" -o "$RELEASE" = "auth-43" ]; then
+elif [ "$RELEASE" = "auth-42" ]; then
+    write_centos 6 pdns pdns_server
+    write_centos 7 pdns pdns_server
+    write_centos 8 pdns pdns_server
+    write_debian stretch pdns-server pdns_server
+    write_debian buster pdns-server pdns_server
+    write_ubuntu xenial pdns-server pdns_server
+    write_ubuntu bionic pdns-server pdns_server
+elif [ "$RELEASE" = "auth-43" -o "$RELEASE" = "auth-master" ]; then
     write_centos 6 pdns pdns_server
     write_centos 7 pdns pdns_server
     write_centos 8 pdns pdns_server
@@ -135,22 +179,27 @@ elif [ "$RELEASE" = "auth-42" -o "$RELEASE" = "auth-43" ]; then
     write_debian buster pdns-server pdns_server
     write_ubuntu xenial pdns-server pdns_server
     write_ubuntu bionic pdns-server pdns_server
+    write_ubuntu focal pdns-server pdns_server
 elif [ "$RELEASE" = "rec-40" ]; then
     write_centos 6 pdns-recursor pdns_recursor
     write_centos 7 pdns-recursor pdns_recursor
-    write_debian jessie pdns-recursor pdns_recursor
     write_debian stretch pdns-recursor pdns_recursor
-    write_ubuntu trusty pdns-recursor pdns_recursor
     write_ubuntu xenial pdns-recursor pdns_recursor
 elif [ "$RELEASE" = "rec-41" ]; then
     write_centos 6 pdns-recursor pdns_recursor
     write_centos 7 pdns-recursor pdns_recursor
-    write_debian jessie pdns-recursor pdns_recursor
     write_debian stretch pdns-recursor pdns_recursor
-    write_ubuntu trusty pdns-recursor pdns_recursor
     write_ubuntu xenial pdns-recursor pdns_recursor
     write_ubuntu bionic pdns-recursor pdns_recursor
-elif [ "$RELEASE" = "rec-42" -o "$RELEASE" = "rec-43" ]; then
+elif [ "$RELEASE" = "rec-42" ]; then
+    write_centos 6 pdns-recursor pdns_recursor
+    write_centos 7 pdns-recursor pdns_recursor
+    write_centos 8 pdns-recursor pdns_recursor
+    write_debian stretch pdns-recursor pdns_recursor
+    write_debian buster pdns-recursor pdns_recursor
+    write_ubuntu xenial pdns-recursor pdns_recursor
+    write_ubuntu bionic pdns-recursor pdns_recursor
+elif [ "$RELEASE" = "rec-43" -o "$RELEASE" = "rec-44" -o "$RELEASE" = "rec-master" ]; then
     write_centos 6 pdns-recursor pdns_recursor
     write_centos 7 pdns-recursor pdns_recursor
     write_centos 8 pdns-recursor pdns_recursor
@@ -158,6 +207,16 @@ elif [ "$RELEASE" = "rec-42" -o "$RELEASE" = "rec-43" ]; then
     write_debian buster pdns-recursor pdns_recursor
     write_ubuntu xenial pdns-recursor pdns_recursor
     write_ubuntu bionic pdns-recursor pdns_recursor
+    write_ubuntu focal pdns-recursor pdns_recursor
+elif [ "$RELEASE" = "dnsdist-15" -o "$RELEASE" = "dnsdist-master" ]; then
+    write_centos 6 dnsdist dnsdist
+    write_centos 7 dnsdist dnsdist
+    write_centos 8 dnsdist dnsdist
+    write_debian stretch dnsdist dnsdist
+    write_debian buster dnsdist dnsdist
+    write_ubuntu xenial dnsdist dnsdist
+    write_ubuntu bionic dnsdist dnsdist
+    write_ubuntu focal dnsdist dnsdist
 else
     echo "Invalid release: $RELEASE"
     exit 1
index 9f7b35a672a15f25f18f7e9586bd8f7716fd7ea4..e0bda5d9d2ec777cfaa3fc6edee06bdf55aaf4d8 100755 (executable)
@@ -29,7 +29,6 @@ done
 [ -z $MODULES ] && echo "No module directory found" >&2 && exit 1
 
 # Symlink the modules on the system
-cd regression-tests/modules
 for backend in *.so; do
   ln -sf $MODULES/$backend $backend
 done
index 5448eb819da4a7a0b7104cad44025ae28a4a341f..da30c02dfb2b6a5d1d44cc55497d5cd818d279ce 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
 export PDNSRECURSOR=${PDNSRECURSOR:-/usr/sbin/pdns_recursor}
+export PDNSRECCONTROL=${PDNSRECCONTROL:-/usr/bin/rec_control}
 export DNSBULKTEST=${DNSBULKTEST:-/usr/bin/dnsbulktest}
 
 if [ "$0" != "./build-scripts/test-recursor-bulk" ]; then
@@ -16,7 +17,7 @@ wget -c -N http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip
 
 unzip top-1m.csv.zip
 
-numdomains="1000 5000 10000 50000 100000 500000 100000"
+numdomains="1000 5000 10000 50000 100000 500000"
 if [ ! -z "$1" ]; then
   numdomains="$1"
 fi
@@ -38,7 +39,7 @@ for IPv6 in 0 1; do
       export context="${version}_v6:${IPv6}_csv:${CSV%%.*}"
       export IPv6
       export CSV
-      RECURSOR=$PDNSRECURSOR THRESHOLD=0 TRACE=no time ./recursor-test 5401 $domains || EXIT=1
+      RECURSOR=$PDNSRECURSOR RECCONTROL=$PDNSRECCONTROL THRESHOLD=0 TRACE=no time ./recursor-test 5401 $domains || EXIT=1
       mv -f recursor.log recursor-${context}.log
       sleep 10
     done
index 80445cb89485a3e822a29be6e96222ad3b0d564c..f1f10266c7773504c4a1451a1f109c5b0429e172 100755 (executable)
@@ -344,10 +344,9 @@ install_recursor() {
   run "cd .."
   run "wget http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip"
   run "unzip top-1m.csv.zip -d ${TRAVIS_BUILD_DIR}/regression-tests"
-  run 'echo -e "deb [arch=amd64] http://repo.powerdns.com/ubuntu trusty-auth-master main" | sudo tee /etc/apt/sources.list.d/pdns.list'
-  run 'echo -e "Package: pdns-*\nPin: origin repo.powerdns.com\nPin-Priority: 9001" | sudo tee /etc/apt/preferences.d/pdns'
-  run 'curl https://repo.powerdns.com/CBC8B383-pub.asc | sudo apt-key add - '
-  run 'sudo apt-get update'
+  run 'wget https://downloads.powerdns.com/tmp/pdns-4.2.0-rc2.255.master.g2bee14438-ubuntu-trusty.tar.bz2'
+  run 'tar xf pdns-4.2.0-rc2.255.master.g2bee14438-ubuntu-trusty.tar.bz2'
+  run 'sudo dpkg -i pdns-4.2.0-rc2.255.master.g2bee14438-ubuntu-trusty/pdns-server_4.2.0~rc2+master.255.g2bee14438-1pdns.trusty_amd64.deb pdns-4.2.0-rc2.255.master.g2bee14438-ubuntu-trusty/pdns-tools_4.2.0~rc2+master.255.g2bee14438-1pdns.trusty_amd64.deb pdns-4.2.0-rc2.255.master.g2bee14438-ubuntu-trusty/pdns-backend-bind_4.2.0~rc2+master.255.g2bee14438-1pdns.trusty_amd64.deb ; sudo apt-get -y install -f'
   run 'sudo apt-get -y install pdns-server pdns-tools'
   run "sudo service pdns stop"
   run 'for suffix in {1..40}; do sudo /sbin/ip addr add 10.0.3.$suffix/32 dev lo; done'
index 48677ee6425b629cebf543ea33748bd94205b999..b42bdd7337c718d3e25ce62b5fad1f059e89d27c 100644 (file)
@@ -1,8 +1,7 @@
 # geoip Configuration
 #
-# See for more information https://doc.powerdns.com/md/authoritative/backend-geoip/
+# See for more information https://doc.powerdns.com/authoritative/backends/geoip.html
 #
-# geoip-database-file=
-# geoip-database-file6=
+# geoip-database-files=
 # geoip-zones-file=
 # geoip-dnssec-keydir=
index cc1d83f9de747df172cb303b637a05e9a45e1228..8813ea89d99a359821a54338f3bd859a74465940 100644 (file)
@@ -1,6 +1,7 @@
 modules/gsqlite3backend/3.4.0_to_4.0.0_schema.sqlite3.sql
 modules/gsqlite3backend/4.0.0_to_4.2.0_schema.sqlite3.sql
 modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
+modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
 modules/gsqlite3backend/dnssec-3.x_to_3.4.0_schema.sqlite3.sql
 modules/gsqlite3backend/nodnssec-3.x_to_3.4.0_schema.sqlite3.sql
 modules/gsqlite3backend/schema.sqlite3.sql
index fd44e419422ee8936dc50f26177175164f0406f7..304916cf0263a82d3e3c73ffa05fa20fe6c5b023 100755 (executable)
@@ -55,7 +55,7 @@ override_dh_installinit:
 
 override_dh_install:
        dh_install
-       ./pdns/pdns_server --no-config --config | sed \
+       ./pdns/pdns_server --config=default | sed \
          -e 's!# module-dir=.*!!' \
          -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/pdns.d!' \
          -e 's!# launch=.*!&\nlaunch=!' \
index 48677ee6425b629cebf543ea33748bd94205b999..b42bdd7337c718d3e25ce62b5fad1f059e89d27c 100644 (file)
@@ -1,8 +1,7 @@
 # geoip Configuration
 #
-# See for more information https://doc.powerdns.com/md/authoritative/backend-geoip/
+# See for more information https://doc.powerdns.com/authoritative/backends/geoip.html
 #
-# geoip-database-file=
-# geoip-database-file6=
+# geoip-database-files=
 # geoip-zones-file=
 # geoip-dnssec-keydir=
index cc1d83f9de747df172cb303b637a05e9a45e1228..8813ea89d99a359821a54338f3bd859a74465940 100644 (file)
@@ -1,6 +1,7 @@
 modules/gsqlite3backend/3.4.0_to_4.0.0_schema.sqlite3.sql
 modules/gsqlite3backend/4.0.0_to_4.2.0_schema.sqlite3.sql
 modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
+modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
 modules/gsqlite3backend/dnssec-3.x_to_3.4.0_schema.sqlite3.sql
 modules/gsqlite3backend/nodnssec-3.x_to_3.4.0_schema.sqlite3.sql
 modules/gsqlite3backend/schema.sqlite3.sql
index 95a38b4d1653d57c1492b7074470582b5387ea51..5cc5cbb7c92d5194476907e021c1e06bdf101d17 100755 (executable)
@@ -52,7 +52,7 @@ override_dh_installinit:
 
 override_dh_install:
        dh_install
-       ./pdns/pdns_server --no-config --config | sed \
+       ./pdns/pdns_server --config=default | sed \
          -e 's!# module-dir=.*!!' \
          -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/pdns.d!' \
          -e 's!# launch=.*!&\nlaunch=!' \
index 48677ee6425b629cebf543ea33748bd94205b999..b42bdd7337c718d3e25ce62b5fad1f059e89d27c 100644 (file)
@@ -1,8 +1,7 @@
 # geoip Configuration
 #
-# See for more information https://doc.powerdns.com/md/authoritative/backend-geoip/
+# See for more information https://doc.powerdns.com/authoritative/backends/geoip.html
 #
-# geoip-database-file=
-# geoip-database-file6=
+# geoip-database-files=
 # geoip-zones-file=
 # geoip-dnssec-keydir=
index cc1d83f9de747df172cb303b637a05e9a45e1228..8813ea89d99a359821a54338f3bd859a74465940 100644 (file)
@@ -1,6 +1,7 @@
 modules/gsqlite3backend/3.4.0_to_4.0.0_schema.sqlite3.sql
 modules/gsqlite3backend/4.0.0_to_4.2.0_schema.sqlite3.sql
 modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
+modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
 modules/gsqlite3backend/dnssec-3.x_to_3.4.0_schema.sqlite3.sql
 modules/gsqlite3backend/nodnssec-3.x_to_3.4.0_schema.sqlite3.sql
 modules/gsqlite3backend/schema.sqlite3.sql
index 20c8df6e6a617b2ef7f94918cf376c342c77b203..28e42be93b5e535fba3384df67adde3181c33e48 100755 (executable)
@@ -51,7 +51,7 @@ override_dh_installinit:
 
 override_dh_install:
        dh_install
-       ./pdns/pdns_server --no-config --config | sed \
+       ./pdns/pdns_server --config=default | sed \
          -e 's!# module-dir=.*!!' \
          -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/pdns.d!' \
          -e 's!# launch=.*!&\nlaunch=!' \
index 61fc03220d4e5294a130d3bf59d7df0891c78ff7..8b0f44ed3e916892206fdaf3dacb55c5b0140e5e 100644 (file)
@@ -61,7 +61,7 @@ stop() {
 #  0 if daemon has been stopped
 #  1 if daemon was already stopped
 #  2 if daemon could not be stopped
-#  other if a failure occured
+#  other if a failure occurred
   start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME
   RETVAL="$?"
   [ "$RETVAL" = 2 ] && return 2
index 2f0ffd17a12d7c73c95a5ea288e19c8367f89197..1680d6c86e77ea2f86a81cacf056b65273dbce95 100755 (executable)
@@ -2,7 +2,7 @@
 DEB_HOST_ARCH_OS ?= $(shell dpkg-architecture -qDEB_HOST_ARCH_OS)
 
 # Enable hardening features for daemons
-# Note: blhc (build log hardening check) will find these false positivies: CPPFLAGS 2 missing, LDFLAGS 1 missing
+# Note: blhc (build log hardening check) will find these false positives: CPPFLAGS 2 missing, LDFLAGS 1 missing
 export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie
 DPKG_EXPORT_BUILDFLAGS = 1
 # Include buildflags.mk so we can append to the vars it sets.
@@ -44,7 +44,7 @@ override_dh_auto_install:
        install -m 644 -t debian/pdns-recursor/usr/share/pdns-recursor/lua-config debian/lua-config/rootkeys.lua
        install -m 644 -t debian/pdns-recursor/etc/powerdns debian/recursor.lua
        rm -f debian/pdns-recursor/etc/powerdns/recursor.conf-dist
-       ./pdns_recursor --no-config --config | sed \
+       ./pdns_recursor --config=default | sed \
                -e 's!# config-dir=.*!config-dir=/etc/powerdns!' \
                -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/recursor.d!' \
                -e 's!# local-address=.*!local-address=127.0.0.1!' \
@@ -53,6 +53,7 @@ override_dh_auto_install:
                -e 's!# setgid=.*!setgid=pdns!' \
                -e 's!# setuid=.*!setuid=pdns!' \
                -e 's!# hint-file=.*!&\nhint-file=/usr/share/dns/root.hints!' \
+               -e '/^# version-string=.*/d' \
                > debian/pdns-recursor/etc/powerdns/recursor.conf
 
 override_dh_strip:
index 61fc03220d4e5294a130d3bf59d7df0891c78ff7..8b0f44ed3e916892206fdaf3dacb55c5b0140e5e 100644 (file)
@@ -61,7 +61,7 @@ stop() {
 #  0 if daemon has been stopped
 #  1 if daemon was already stopped
 #  2 if daemon could not be stopped
-#  other if a failure occured
+#  other if a failure occurred
   start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME
   RETVAL="$?"
   [ "$RETVAL" = 2 ] && return 2
index e6951a3be3eaeef8e29a39671f5cf754f1cb6d4b..9f8872b027a6d7e839f10af7ba5143af837fc765 100755 (executable)
@@ -2,7 +2,7 @@
 DEB_HOST_ARCH_OS ?= $(shell dpkg-architecture -qDEB_HOST_ARCH_OS)
 
 # Enable hardening features for daemons
-# Note: blhc (build log hardening check) will find these false positivies: CPPFLAGS 2 missing, LDFLAGS 1 missing
+# Note: blhc (build log hardening check) will find these false positives: CPPFLAGS 2 missing, LDFLAGS 1 missing
 export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie
 DPKG_EXPORT_BUILDFLAGS = 1
 # Include buildflags.mk so we can append to the vars it sets.
@@ -43,7 +43,7 @@ override_dh_auto_install:
        install -m 644 -t debian/pdns-recursor/usr/share/pdns-recursor/lua-config debian/lua-config/rootkeys.lua
        install -m 644 -t debian/pdns-recursor/etc/powerdns debian/recursor.lua
        rm -f debian/tmp/etc/powerdns/recursor.conf-dist
-       ./pdns_recursor --no-config --config | sed \
+       ./pdns_recursor --config=default | sed \
                -e 's!# config-dir=.*!config-dir=/etc/powerdns!' \
                -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/recursor.d!' \
                -e 's!# local-address=.*!local-address=127.0.0.1!' \
@@ -52,6 +52,7 @@ override_dh_auto_install:
                -e 's!# setgid=.*!setgid=pdns!' \
                -e 's!# setuid=.*!setuid=pdns!' \
                -e 's!# hint-file=.*!&\nhint-file=/usr/share/dns/root.hints!' \
+               -e '/^# version-string=.*/d' \
                > debian/tmp/etc/powerdns/recursor.conf
 
 override_dh_strip:
index 61fc03220d4e5294a130d3bf59d7df0891c78ff7..8b0f44ed3e916892206fdaf3dacb55c5b0140e5e 100644 (file)
@@ -61,7 +61,7 @@ stop() {
 #  0 if daemon has been stopped
 #  1 if daemon was already stopped
 #  2 if daemon could not be stopped
-#  other if a failure occured
+#  other if a failure occurred
   start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME
   RETVAL="$?"
   [ "$RETVAL" = 2 ] && return 2
index 2f0ffd17a12d7c73c95a5ea288e19c8367f89197..1680d6c86e77ea2f86a81cacf056b65273dbce95 100755 (executable)
@@ -2,7 +2,7 @@
 DEB_HOST_ARCH_OS ?= $(shell dpkg-architecture -qDEB_HOST_ARCH_OS)
 
 # Enable hardening features for daemons
-# Note: blhc (build log hardening check) will find these false positivies: CPPFLAGS 2 missing, LDFLAGS 1 missing
+# Note: blhc (build log hardening check) will find these false positives: CPPFLAGS 2 missing, LDFLAGS 1 missing
 export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie
 DPKG_EXPORT_BUILDFLAGS = 1
 # Include buildflags.mk so we can append to the vars it sets.
@@ -44,7 +44,7 @@ override_dh_auto_install:
        install -m 644 -t debian/pdns-recursor/usr/share/pdns-recursor/lua-config debian/lua-config/rootkeys.lua
        install -m 644 -t debian/pdns-recursor/etc/powerdns debian/recursor.lua
        rm -f debian/pdns-recursor/etc/powerdns/recursor.conf-dist
-       ./pdns_recursor --no-config --config | sed \
+       ./pdns_recursor --config=default | sed \
                -e 's!# config-dir=.*!config-dir=/etc/powerdns!' \
                -e 's!# include-dir=.*!&\ninclude-dir=/etc/powerdns/recursor.d!' \
                -e 's!# local-address=.*!local-address=127.0.0.1!' \
@@ -53,6 +53,7 @@ override_dh_auto_install:
                -e 's!# setgid=.*!setgid=pdns!' \
                -e 's!# setuid=.*!setuid=pdns!' \
                -e 's!# hint-file=.*!&\nhint-file=/usr/share/dns/root.hints!' \
+               -e '/^# version-string=.*/d' \
                > debian/pdns-recursor/etc/powerdns/recursor.conf
 
 override_dh_strip:
index a043b26bbc72ac98ad87362edaef83f9676b2cba..7bbbc37ed84b53c2a3db751861511ae08cb0a5a4 100644 (file)
@@ -1,6 +1,6 @@
 FROM dist-base as package-builder
 ARG APT_URL
-RUN apt-get -y install devscripts dpkg-dev build-essential python3 equivs
+RUN DEBIAN_FRONTEND=noninteractive apt-get -y install devscripts dpkg-dev build-essential python3 equivs
 
 RUN mkdir /dist /pdns
 WORKDIR /pdns
index 85bb0940cf40a7c2adac8fa7465451c7468a0699..f3fd7a0f291d3cfb30bd9da181d804965fc41316 100644 (file)
@@ -57,7 +57,7 @@ RUN touch /var/lib/rpm/* && if $(grep -q 'release 6' /etc/redhat-release); then
     fi
 @ENDIF
 
-# mv accross layers with overlay2 is buggy in some kernel versions (results in empty dirs)
+# mv across layers with overlay2 is buggy in some kernel versions (results in empty dirs)
 # See: https://github.com/moby/moby/issues/33733
 #RUN mv /root/rpmbuild/RPMS/* /dist/
 RUN cp -R /root/rpmbuild/RPMS/* /dist/
index 32b7953b9a423087da30a62df4fd3f528d4f3b7b..bda6eb1fff6d313869a075fbb353f044756a5410 100644 (file)
@@ -1,7 +1,7 @@
 # First do the source builds
 @INCLUDE Dockerfile.target.sdist
 
-# This defines the dstribution base layer
+# This defines the distribution base layer
 # Put only the bare minimum of common commands here, without dev tools
 FROM amazonlinux:2 as dist-base
 ARG BUILDER_CACHE_BUSTER=
index 28e73cf1604b64f06534491ecc198e2925e4d4c7..fd89ccd9f20ae48316bc44cdb24a01be96b3f46c 100644 (file)
@@ -1,7 +1,7 @@
 # First do the source builds
 @INCLUDE Dockerfile.target.sdist
 
-# This defines the dstribution base layer
+# This defines the distribution base layer
 # Put only the bare minimum of common commands here, without dev tools
 FROM centos:6 as dist-base
 ARG BUILDER_CACHE_BUSTER=
index 68acb73001dfe974d355766b4991759891e3015a..4e62d693c61ae3c6500855849cbcdb0d15621f87 100644 (file)
@@ -1,7 +1,7 @@
 # First do the source builds
 @INCLUDE Dockerfile.target.sdist
 
-# This defines the dstribution base layer
+# This defines the distribution base layer
 # Put only the bare minimum of common commands here, without dev tools
 FROM centos:7 as dist-base
 ARG BUILDER_CACHE_BUSTER=
index 02316043cbee6bd1647d91a3181f6514cb64ce5c..4134de4b62ef69fcb36b3a69a8cf04fe252c7389 100644 (file)
@@ -1,7 +1,7 @@
 # First do the source builds
 @INCLUDE Dockerfile.target.sdist
 
-# This defines the dstribution base layer
+# This defines the distribution base layer
 # Put only the bare minimum of common commands here, without dev tools
 FROM centos:8 as dist-base
 ARG BUILDER_CACHE_BUSTER=
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-focal b/builder-support/dockerfiles/Dockerfile.target.ubuntu-focal
new file mode 100644 (file)
index 0000000..77e9afd
--- /dev/null
@@ -0,0 +1,27 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+FROM ubuntu:focal as dist-base
+ARG BUILDER_CACHE_BUSTER=
+ARG APT_URL
+RUN apt-get update && apt-get -y dist-upgrade
+
+@INCLUDE Dockerfile.debbuild-prepare
+
+@IF [ ! -z "$M_authoritative" ]
+ADD builder-support/debian/authoritative/debian-buster/ pdns-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@IF [ ! -z "$M_recursor" ]
+ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@IF [ ! -z "$M_dnsdist" ]
+ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@INCLUDE Dockerfile.debbuild
+
+# Do a test install and verify
+# Can be skipped with skiptests=1 in the environment
+# @EXEC [ "$skiptests" = "" ] && include Dockerfile.debtest
index 5614e978cb7981e202f93dd1ecf6b29f16af7603..9ca9884d357af0e6f715e3ff8fcbef9f5a6bdbb3 100644 (file)
@@ -152,6 +152,7 @@ fi
 %service_add_post %{name}.service
 %endif
 %if 0%{?rhel} >= 7
+systemctl daemon-reload ||:
 %systemd_post %{name}.service
 %endif
 
index 39df2502128c1a5695c5203a2b90c7f312a93660..4e9608e239db3962db487104360496f373e6dd1d 100644 (file)
@@ -116,6 +116,7 @@ exit 0
 %if 0%{?rhel} == 6
 chkconfig --add %{name}
 %else
+systemctl daemon-reload ||:
 %systemd_post %{name}.service
 %endif
 
index 8a2a2cb19af193fe357d57176ff8a14c365a84e1..33d9bf551a0a4b81c41ba9ca171d552d6640cd23 100644 (file)
@@ -69,7 +69,11 @@ This package contains the extra tools for %{name}
 Summary: MySQL backend for %{name}
 Group: System Environment/Daemons
 Requires: %{name}%{?_isa} = %{version}-%{release}
+%if 0%{?rhel} < 8
 BuildRequires: mysql-devel
+%else
+BuildRequires: mariadb-connector-c-devel
+%endif
 %global backends %{backends} gmysql
 
 %description backend-mysql
@@ -234,7 +238,7 @@ make install DESTDIR=%{buildroot}
 %{__install} -D -p %{SOURCE1} %{buildroot}%{_initrddir}/pdns
 %endif
 
-%{buildroot}/usr/sbin/pdns_server --no-config --config | sed \
+%{buildroot}/usr/sbin/pdns_server --config=default | sed \
   -e 's!# daemon=.*!daemon=no!' \
   -e 's!# guardian=.*!guardian=no!' \
   -e 's!# launch=.*!&\\nlaunch=!' \
@@ -273,6 +277,7 @@ chown -R pdns:pdns /var/lib/powerdns || :
 
 %post
 %if 0%{?rhel} >= 7
+systemctl daemon-reload ||:
 %systemd_post pdns.service
 %else
 /sbin/chkconfig --add pdns
@@ -407,6 +412,7 @@ fi
 %doc modules/gsqlite3backend/3.4.0_to_4.0.0_schema.sqlite3.sql
 %doc modules/gsqlite3backend/4.0.0_to_4.2.0_schema.sqlite3.sql
 %doc modules/gsqlite3backend/4.2.0_to_4.3.0_schema.sqlite3.sql
+%doc modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
 %{_libdir}/%{name}/libgsqlite3backend.so
 
 %if 0%{?rhel} >= 7
index 4e76baa4b7484293a61a7630d8d1a9e60b49fdcd..0ac8687750a730dddedcdce8b50f4629ab530329 100644 (file)
@@ -472,13 +472,13 @@ EXCLUDE                =
 # directories that are symbolic links (a Unix filesystem feature) are excluded 
 # from the input.
 
-EXCLUDE_SYMLINKS       = NO
+EXCLUDE_SYMLINKS       = YES
 
 # If the value of the INPUT tag contains directories, you can use the 
 # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
 # certain files from those directories.
 
-EXCLUDE_PATTERNS       = 
+EXCLUDE_PATTERNS       = test-*
 
 # The EXAMPLE_PATH tag can be used to specify one or more files or 
 # directories that contain example code fragments that are included (see 
index 91daeeab07791966784ff4bd6036ee011b11ecdf..88293d1ad07214eabeb5e85777acffe03eeb78b6 100644 (file)
@@ -18,8 +18,8 @@ AM_SILENT_RULES([yes])
 
 AC_CANONICAL_HOST
 # Add some default CFLAGS and CXXFLAGS, can be appended to using the environment variables
-CFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wno-unused-parameter $CFLAGS"
-CXXFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wno-unused-parameter $CXXFLAGS"
+CFLAGS="-g -O2 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls $CFLAGS"
+CXXFLAGS="-std=c++11 -g -O2 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls $CXXFLAGS"
 
 AC_PROG_CC
 AM_PROG_CC_C_O
@@ -48,7 +48,7 @@ PTHREAD_SET_NAME
 PDNS_WITH_LUA([mandatory])
 PDNS_CHECK_LUA_HPP
 
-AX_CXX_COMPILE_STDCXX_11
+AX_CXX_COMPILE_STDCXX_11([noext], [mandatory])
 
 AC_MSG_CHECKING([whether we will enable compiler security checks])
 AC_ARG_ENABLE([hardening],
@@ -144,7 +144,6 @@ AC_SUBST([LIBDL], [$lt_cv_dlopen_libs])
 
 PDNS_ENABLE_VERBOSE_LOGGING
 PDNS_ENABLE_PKCS11
-PDNS_ENABLE_GSS_TSIG
 
 AC_SUBST([socketdir])
 socketdir="/var/run"
@@ -398,9 +397,6 @@ AS_IF([test "x$LUAPC" != "x"],
 AS_IF([test "x$enable_experimental_pkcs11" = "xyes"],
   [AC_MSG_NOTICE([PKCS-11: yes])]
 )
-AS_IF([test "x$enable_experimental_gss_tsig" = "xyes"],
-  [AC_MSG_NOTICE([GSS-TSIG: yes])]
-)
 AS_IF([test "x$enable_lua_records" = "xyes"],
   [AC_MSG_NOTICE([LUA records: yes])]
 )
index d3405aeda042addf7665fad2c78c9073d6cea52c..7ecf09429214dc86674f699739df5d6fa1cc6b11 100644 (file)
@@ -15,12 +15,12 @@ have pdnsutil && {
   _pdnsutil_helper_local_() {
     local cur prev cmd
 
-    local _PDNSUTIL_ALL_CMDS="activate-tsig-key activate-zone-key add-record add-zone-key backend-cmd b2b-migrate bench-db change-slave-zone-master check-zone check-all-zones clear-zone
-                              create-bind-db create-slave-zone create-zone deactivate-tsig-key deactivate-zone-key delete-rrset delete-tsig-key delete-zone disable-dnssec
-                              edit-zone export-zone-dnskey export-zone-key generate-tsig-key generate-zone-key get-meta hash-zone-record increase-serial import-tsig-key
-                              import-zone-key load-zone list-algorithms list-keys list-zone list-all-zones list-tsig-keys rectify-zone rectify-all-zones remove-zone-key
-                              replace-rrset secure-all-zones secure-zone set-kind set-nsec3 set-presigned set-publish-cdnskey set-publish-cds set-meta show-zone
-                              unset-nsec3 unset-presigned unset-publish-cdnskey unset-publish-cds test-schema"
+    local _PDNSUTIL_ALL_CMDS="activate-tsig-key activate-zone-key add-record add-supermaster add-zone-key backend-cmd b2b-migrate bench-db change-slave-zone-master 
+                              check-zone check-all-zones clear-zone create-bind-db create-slave-zone create-zone deactivate-tsig-key deactivate-zone-key delete-rrset 
+                              delete-tsig-key delete-zone disable-dnssec edit-zone export-zone-dnskey export-zone-key generate-tsig-key generate-zone-key get-meta 
+                              hash-zone-record increase-serial import-tsig-key import-zone-key load-zone list-algorithms list-keys list-zone list-all-zones 
+                              list-tsig-keys rectify-zone rectify-all-zones remove-zone-key replace-rrset secure-all-zones secure-zone set-kind set-nsec3 set-presigned 
+                              set-publish-cdnskey set-publish-cds set-meta show-zone unset-nsec3 unset-presigned unset-publish-cdnskey unset-publish-cds test-schema"
     COMPREPLY=()
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
index 75ee4297c2b96b7ae4bab7f89a7fb6e90e882938..c3b04dafb81e49698253b4970c6300b3647b2492 100644 (file)
@@ -1,20 +1,50 @@
 End of life statements
 ======================
 
-The currently supported release train of PowerDNS Authoritative Server is 4.2.
+We aim to have a release every six months.
+The latest and previous release receive correctness, stability and security updates.
+The release before that gets critical security updates only.
+Older releases are marked end of life and receive no updates at all.
+Pre-releases do not receive immediate security updates.
 
-PowerDNS Authoritative Server 4.1 will only receive correctness, stability and security updates and will be receiving security updates only after PowerDNS Authoritative Server 4.3 is released.
-It wil be end of life after PowerDNS Authoritative Server 4.4 is released.
+The currently supported release train of PowerDNS Authoritative Server is 4.3.
 
-PowerDNS Authoritative Server 4.0 will only receive security updates and will be end of life after PowerDNS Authoritative Server 4.3 is released.
+PowerDNS Authoritative Server 4.2 will only receive correctness, stability and security updates and will be receiving security updates only after PowerDNS Authoritative Server 4.4 is released.
+It wil be end of life after PowerDNS Authoritative Server 4.5 is released.
 
-PowerDNS Authoritative Server 3.x and 2.x are end of life, and will not
+PowerDNS Authoritative Server 4.1 will only receive critical security updates and will be end of life after PowerDNS Authoritative Server 4.4 is released.
+
+PowerDNS Authoritative Server 4.0, 3.x and 2.x are end of life, and will not
 receive any updates, not even security fixes.
 
 Note: Users with a commercial agreement with PowerDNS.COM BV or Open-Xchange
 can receive extended support for releases which are End Of Life. If you are
 such a user, these EOL statements do not apply to you.
 
+.. list-table:: PowerDNS Authoritative Server Release Life Cycle
+   :header-rows: 1
+
+   * - Version
+     - Release date
+     - Security-Only updates
+     - End of Life
+   * - 4.3
+     - April 7 2020
+     - ~ April 2021
+     - ~ October 2021
+   * - 4.2
+     - August 30 2019
+     - ~ October 2020
+     - ~ April 2021
+   * - 4.1
+     - November 30 2017
+     - April 7 2020
+     - ~ October 2020
+   * - 4.0 and older
+     - EOL
+     - EOL
+     - EOL
+
 PowerDNS Authoritative Server 3.x
 ---------------------------------
 1st of December 2017
@@ -22,7 +52,7 @@ PowerDNS Authoritative Server 3.x
 The PowerDNS Authoritative Server 3.x releases are no longer supported, and
 will not receive any further updates, not even for security purposes.
 
-All users are urged to upgrade to version 4.1.  To upgrade from 3.x to 4.x,
+All users are urged to upgrade to the latest version.  To upgrade from 3.x to 4.x,
 :doc:`follow these instructions <../upgrading>`
 
 If you need help with upgrading, we provide `migration
@@ -64,6 +94,5 @@ to our supported users. If you are currently running 2.9.22 and need
 help to tide you over, we can also provide that as part of a `support
 agreement <https://www.powerdns.com/support-services-consulting.html>`__.
 
-But we urge everyone to move on to PowerDNS Authoritative Server 4.1 or
-later - it is a faster, more standards conforming and more powerful
+But we urge everyone to move on to PowerDNS Authoritative Server 4.x - it is a faster, more standards conforming and more powerful
 nameserver!
index e464cd0c888c47305ca90a88bce5472895a6da55..056ad3a0690320ce5c5b269636bf1ac6b95ae918 100644 (file)
@@ -59,7 +59,7 @@ Also, check that the configured backend is master or slave capable and you enter
 My masters won't allow PowerDNS to access zones as it is using the wrong local IP address
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 By default, PowerDNS lets the kernel pick the source address.
-To set an explicit source address, use the :ref:`setting-query-local-address` and :ref:`setting-query-local-address6` settings.
+To set an explicit source address, use the :ref:`setting-query-local-address` setting.
 
 PowerDNS does not answer queries on all my IP addresses (and I've ignored the warning I got about that at startup)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
index 18a8015e2e3f456c53948d381d1cfdf188e3030e..1943b9d8582b2b29d9e990d822c0e20cfd39daa0 100644 (file)
@@ -2,8 +2,9 @@ Backend writers' guide
 ======================
 
 PowerDNS backends are implemented via a simple yet powerful C++
-interface. If your needs are not met by the PipeBackend, you may want to
-write your own. Before doing any PowerDNS development, please read `this blog
+interface. If your needs are not met by the regular backends, including
+the PipeBackend and the RemoteBackend, you may want to write your own.
+Before doing any PowerDNS development, please read `this blog
 post <https://blog.powerdns.com/2015/06/23/what-is-a-powerdns-backend-and-how-do-i-make-it-send-an-nxdomain/>`__
 which has a FAQ and several pictures that help explain what a backend
 is.
@@ -117,12 +118,26 @@ default ``getSOA()`` method performs a regular lookup on your backend to
 figure out the SOA, so if you have no special treatment for SOA records,
 where is no need to implement your own ``getSOA()``.
 
+Figuring out the Start of Authority can require an important number of
+call to ``getSOA()`` if the name has a lot of labels. For example,
+figuring out that the SOA for ``2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.``
+is ``d.0.1.0.0.2.ip6.arpa.`` might involve 26 calls, chopping off one label
+at a time. If your backend has an efficient way to figure out the
+best SOA it has for a given name, it is possible to override the
+default ``getSOA()`` implementation to immediately return the
+``d.0.1.0.0.2.ip6.arpa.`` SOA record to the first
+``2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.``
+``getSOA()`` call.
+
 Besides direct queries, PowerDNS also needs to be able to list a zone,
 to do zone transfers for example. Each zone has an id which should be
-unique within the backend. To list all records belonging to a zone id,
+unique within the backends. To list all records belonging to a zone id,
 the ``list()`` method is used. Conveniently, the domain_id is also
 available in the ``SOAData`` structure.
 
+.. warning::
+  Each zone should have a unique id, even across backends.
+
 The following lists the contents of a zone called "powerdns.com".
 
 .. code-block:: cpp
@@ -210,9 +225,31 @@ furthermore, only about its A record:
     static RandomLoader randomloader;
 
 This simple backend can be used as an 'overlay'. In other words, it only
-knows about a single record, another loaded backend would have to know
-about the SOA and NS records and such. But nothing prevents us from
-loading it without another backend.
+knows about a single name, ``random.powerdns.com``, another loaded backend
+would have to know about the SOA and NS records for the ``powerdns.com`` zone
+and such.
+
+.. warning::
+  Spreading the content of a zone across multiple backends, described above
+  as 'overlay', makes the zone incompatible with some operations that
+  assume that a single zone is always entirely stored in the same backend.
+  Such operations include zone transfers, listing and editing zone content via
+  the API or ``pdnsutil``.
+
+.. warning::
+  When the content of a zone is spread across multiple backends, all the types
+  for a given name should be delegated to the same backend.
+  For example a backend can know about all the types for ``random.powerdns.com``
+  while another backend knows about all the types for ``random2.powerdns.com``,
+  but it is not possible to let one backend handle only ``AAAA`` queries for
+  all names while another one handles only ``A`` queries, for example.
+  This limitation comes from the fact that PowerDNS uses ``ANY`` queries to fetch
+  all types from the backend in one go and that it assumes that once one backend
+  has returned records the other ones do not need to be called.
+  It is also possible to have two backends providing records for the same name
+  and types, for example if the first one does not support DNSSEC and the second
+  does, but that requires some mechanism outside of PowerDNS to keep records in
+  sync between the two backends.
 
 The first part of the code contains the actual logic and should be
 pretty straightforward. The second part is a boilerplate 'factory' class
@@ -316,7 +353,7 @@ Classes
 Methods
 ~~~~~~~
 
-.. cpp:function:: void DNSBackend::lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt=0, int zoneId=-1)
+.. cpp:function:: void DNSBackend::lookup(const QType &qtype, const string &qdomain, DNSPacket *pkt=nullptr, int zoneId=-1)
 
   This function is used to initiate a straight lookup for a record of name
   'qdomain' and type 'qtype'. A QType can be converted into an integer by
@@ -327,6 +364,11 @@ Methods
   is, you can retrieve information about who asked the question with the
   ``pkt->getRemote()`` method.
 
+  .. note::
+    Since 4.1.0, 'SOA' lookups are not passed this pointer anymore because
+    PowerDNS doesn't support tailoring whether a whole zone exists or not based
+    on who is asking.
+
   Note that **qdomain** can be of any case and that your backend should
   make sure it is in effect case insensitive. Furthermore, the case of the
   original question should be retained in answers returned by ``get()``!
@@ -397,7 +439,7 @@ message won't be visible otherwise.
 To indicate the importance of an error, the standard syslog errorlevels
 are available. They can be set by outputting ``Logger::Critical``,
 ``Logger::Error``, ``Logger::Warning``, ``Logger::Notice``,
-``Logger::Info`` or ``Logger::Debug`` to ``L``, in descending order of
+``Logger::Info`` or ``Logger::Debug`` to ``g_log``, in descending order of
 graveness.
 
 Declaring and reading configuration details
@@ -630,7 +672,7 @@ The actual code in PowerDNS is currently:
 
         while(resolver.axfrChunk(recs)) {
           for(Resolver::res_t::const_iterator i=recs.begin();i!=recs.end();++i) {
-        db->feedRecord(*i);
+            db->feedRecord(*i);
           }
         }
         db->commitTransaction();
@@ -771,6 +813,147 @@ other update/remove functionality at a later stage.
   must be added to the zone. ``rrset`` can be empty in which case the
   method is used to remove a RRset.
 
+Domain metadata support
+-----------------------
+
+As described in :doc:`../domainmetadata`, each served zone can have “metadata”. Such metadata determines how this zone behaves in certain circumstances.
+In order for a backend to support domain metadata, the following operations have to be implemented:
+
+.. code-block:: cpp
+
+    class DNSBackend {
+    public:
+      /* ... */
+      virtual bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta);
+      virtual bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta);
+      virtual bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta);
+      /* ... */
+    }
+
+.. cpp:function:: virtual bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta)
+
+  Fills 'meta' with the value(s) of all kinds for zone 'name'. Returns true if the domain metadata operation are supported, regardless
+  of whether there is any data for this zone.
+
+.. cpp:function:: virtual bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
+
+  Fills 'meta' with the value(s) of the specified kind for zone 'name'. Returns true if the domain metadata operation are supported, regardless
+  of whether there is any data of this kind for this zone.
+
+.. cpp:function:: virtual bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
+
+  Store the values from 'meta' for the specified kind for zone 'name', discarding existing values if any. An empty meta is equivalent to a deletion request.
+  Returns true if the values have been correctly stored, and false otherwise.
+
+TSIG keys
+---------
+
+In order for a backend to support the storage of TSIG keys, the following operations have to be implemented:
+
+.. code-block:: cpp
+
+    class DNSBackend {
+    public:
+      /* ... */
+      virtual bool getTSIGKey(const DNSName& name, DNSName* algorithm, string* content);
+      virtual bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content);
+      virtual bool deleteTSIGKey(const DNSName& name);
+      virtual bool getTSIGKeys(std::vector< struct TSIGKey > &keys);
+      /* ... */
+    }
+
+DNSSEC support
+--------------
+
+In order for a backend to support DNSSEC, quite a few number of additional operations have to be implemented:
+
+.. code-block:: cpp
+
+    struct KeyData {
+      std::string content;
+      unsigned int id;
+      unsigned int flags;
+      bool active;
+      bool published;
+    };
+
+    class DNSBackend {
+    public:
+      /* ... */
+      virtual bool doesDNSSEC();
+      virtual bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after);
+
+      /* update operations */
+      virtual bool updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype=QType::ANY);
+      virtual bool updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove);
+      virtual bool feedEnts(int domain_id, map<DNSName,bool> &nonterm);
+      virtual bool feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bool> &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow);
+
+      /* keys management */
+      virtual bool getDomainKeys(const DNSName& name, std::vector<KeyData>& keys);
+      virtual bool removeDomainKey(const DNSName& name, unsigned int id);
+      virtual bool addDomainKey(const DNSName& name, const KeyData& key, int64_t& id);
+      virtual bool activateDomainKey(const DNSName& name, unsigned int id);
+      virtual bool deactivateDomainKey(const DNSName& name, unsigned int id);
+      virtual bool publishDomainKey(const DNSName& name, unsigned int id);
+      virtual bool unpublishDomainKey(const DNSName& name, unsigned int id);
+
+      /* ... */
+    }
+
+.. cpp:function:: virtual bool doesDNSSEC()
+
+  Returns true if that backend supports DNSSEC.
+
+.. cpp:function:: virtual bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
+
+  Asks the names before and after qname for NSEC and NSEC3. The qname will be hashed when using NSEC3. Care must be taken to handle wrap-around when qname is the first or last in the ordered list of zone names.
+
+.. cpp:function:: virtual bool updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype=QType::ANY)
+
+  Updates the ordername and auth fields.
+
+.. cpp:function:: virtual bool updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
+
+  Updates ENT after a zone has been rectified. If 'remove' is false, 'erase' contains a list of ENTs to remove from the zone before adding any. Otherwise all ENTs should be removed from the zone before adding any. 'insert' contains the list of ENTs to add to the zone after the removals have been done.
+
+.. cpp:function:: virtual bool feedEnts(int domain_id, map<DNSName,bool> &nonterm)
+
+  This method is used by ``pdnsutil rectify-zone`` to populate missing non-terminals. This is used when you have, say, record like _sip._upd.example.com, but no _udp.example.com. PowerDNS requires that there exists a non-terminal in between, and this instructs you to add one.
+
+.. cpp:function:: virtual bool feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bool> &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
+
+  Same as feedEnts, but provides NSEC3 hashing parameters.
+
+.. cpp:function:: virtual bool getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
+
+  Retrieves all DNSSEC keys. Content must be valid key record in format that PowerDNS understands.
+
+.. cpp:function:: virtual bool removeDomainKey(const DNSName& name, unsigned int id)
+
+  Removes this key.
+
+.. cpp:function:: virtual bool addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
+
+  Adds a new DNSSEC key for this domain.
+
+.. cpp:function:: virtual bool activateDomainKey(const DNSName& name, unsigned int id)
+
+  Activates an inactive DNSSEC key for this domain.
+
+.. cpp:function:: virtual bool deactivateDomainKey(const DNSName& name, unsigned int id)
+
+  Deactivates an active DNSSEC key for this domain.
+
+.. cpp:function:: virtual bool publishDomainKey(const DNSName& name, unsigned int id)
+
+  Publishes a previously hidden DNSSEC key for this domain.
+
+.. cpp:function:: virtual bool unpublishDomainKey(const DNSName& name, unsigned int id)
+
+  Hides a DNSSEC key for this domain. Hidden DNSSEC keys are used for signing but do not appear in the actual zone,
+  and are useful for rollover operations.
+
 Miscellaneous
 -------------
 
index f22552b7495a48c8447d8582ce29e599fd76ab22..985074b66f4db607e74b0719cb827b5029d49b62 100644 (file)
@@ -55,6 +55,15 @@ CNAME-like mechanisms on a zone's apex. See the :doc:`howto <../guides/alias>` f
 on how to configure PowerDNS to serve records synthesized from ALIAS
 records.
 
+.. _types-apl:
+
+APL
+-----
+
+.. versionadded:: 4.4.0
+
+The APL record, specified in :rfc:`3123`, is used to specify a DNS RR type "APL" for address prefix lists.
+
 .. _types-caa:
 
 CAA
index 8ea0d0a260e054135f41bf5d15b39cd3b71e6767..4890668999214b0d137aa2195f93172aa4b642ca 100644 (file)
@@ -75,6 +75,8 @@ slave DNSSEC-enabled domains (where the RRSIGS are in the AXFR), a
 :ref:`metadata-presigned` domain metadata is set
 during the zonetransfer.
 
+You can use ``pdnsutil create-bind-db`` to make this database file for you.
+
 .. warning::
    If this is left empty on slaves and a presigned zone is transferred,
    it will (silently) serve it without DNSSEC. This in turn results in
index 5d8b5916373f4d3c59d3044b7e451cf4cd1dd398..af424212e79c94b1e25d58e72c1a22d34cd8d5f5 100644 (file)
@@ -36,7 +36,7 @@ assumes this layout is in place. For full migration notes, please see
 for master, slave and superslave operation.
 
 When using the InnoDB storage engine, we suggest adding foreign key
-contraints to the tables in order to automate deletion of records, key
+constraints to the tables in order to automate deletion of records, key
 material, and other information upon deletion of a domain from the
 domains table. The following SQL does the job:
 
@@ -130,7 +130,7 @@ Use the InnoDB READ-COMMITTED transaction isolation level. Default: yes.
 ^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.2.1
 
-Send the CLIENT_SSL capabily flag to the server. SSL suppport is announced by the server via CLIENT_SSL and is enabled if the client returns the same capability. Default: no.
+Send the CLIENT_SSL capability flag to the server. SSL support is announced by the server via CLIENT_SSL and is enabled if the client returns the same capability. Default: no.
 
 .. _setting-gmysql-timeout:
 
index f65b4bf970316465e1f73423e7b3803d9233b992..96cb1723adb5a99bb74ed4ac8a9bb35dd9fb49a2 100644 (file)
@@ -91,6 +91,17 @@ parameters are documented `in the PostgreSQL
 documentation <https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS>`__.
 Default: "".
 
+.. _setting-gpgsql-prepared-statements:
+
+``gpgsql-prepared-statements``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Prepare statements for better performance, instead of sending parameterized queries.
+Might not work with connection poolers.
+Default: yes.
+
+.. versionadded:: 4.4.0
+
 Default schema
 --------------
 
@@ -98,3 +109,70 @@ This is the 4.3 schema. Please find `the 4.2 schema <https://github.com/PowerDNS
 
 .. literalinclude:: ../../modules/gpgsqlbackend/schema.pgsql.sql
    :language: SQL
+
+CockroachDB
+-----------
+
+`CockroachDB <https://www.cockroachlabs.com/docs/stable/architecture/overview.html>`__ is a highly available, resilient database that focuses on scaling and consistency. Specifically: it offers a PostgreSQL like database interface,
+which means that most tools that talk the PostgreSQL protocol can use it.
+
+A few changes are needed on top of the generic PostgreSQL settings. CockroachDB does not natively support the range operators that some PowerDNS database queries use,
+and care must be taken that table index columns do not exceed the internal maximum integer size that PowerDNS uses.
+
+Schema differences
+^^^^^^^^^^^^^^^^^^
+
+Given the normal pgsql schema, change the following:
+
+1. Add explicit SEQUENCEs for all SERIAL columns:
+
+.. code-block:: SQL
+
+  CREATE SEQUENCE domain_id MAXVALUE 2147483648;
+  CREATE SEQUENCE record_id MAXVALUE 2147483648;
+  CREATE SEQUENCE comment_id MAXVALUE 2147483648;
+  CREATE SEQUENCE meta_id MAXVALUE 2147483648;
+  CREATE SEQUENCE key_id MAXVALUE 2147483648;
+  CREATE SEQUENCE tsig_id MAXVALUE 2147483648;
+
+2. Change all SERIAL / BIGSERIAL columns to use the SEQUENCEs. For instance:
+
+.. code-block:: SQL
+
+  -- Before
+  CREATE TABLE domains (
+    id SERIAL PRIMARY KEY,
+    --
+  }
+
+  -- After
+  CREATE TABLE domains (
+    id INT DEFAULT nextval('domain_id') PRIMARY KEY,
+    --
+  );
+
+
+3. Do **not** add the following index to the records table, the text_pattern_ops operator class is not supported:
+
+.. code-block:: SQL
+
+  CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops);
+
+
+Configuration changes
+^^^^^^^^^^^^^^^^^^^^^
+
+Four queries must be overridden in the PowerDNS config, because by default they use a range operator that is not supported. These modified queries are actually
+taken from the generic MySQL backend, and modified for syntax:
+
+.. code-block:: ini
+
+  gpgsql-get-order-first-query=select ordername from records where domain_id = $1 and disabled = false and ordername is not null order by 1 asc limit 1
+  gpgsql-get-order-before-query=select ordername, name from records where ordername <= $1 and domain_id = $2 and disabled = false and ordername is not null order by 1 desc limit 1
+  gpgsql-get-order-after-query=select ordername from records where ordername > $1 and domain_id = $2 and disabled = false and ordername is not null order by 1 asc limit 1
+  gpgsql-get-order-last-query=select ordername, name from records where ordername != '' and domain_id = $1 and disabled = false and ordername is not null order by 1 desc limit 1
+
+References
+^^^^^^^^^^
+
+See `this Github issue <https://github.com/PowerDNS/pdns/issues/5375#issuecomment-644771800>`__ for the original tests and a full working schema.
index 66d17725114d01339656b527f4d1925c0891e67f..b056d607ba92f301669f3f5be5e496b0ddbd1310 100644 (file)
@@ -190,7 +190,7 @@ the C function 'snprintf' which implies that substitutions are performed
 on the basis of %-placeholders.
 
 To see the default queries for a backend, run
-``pdns_server --no-config --launch=BACKEND --config``.
+``pdns_server --launch=BACKEND --config=default``.
 
 Regular Queries
 ^^^^^^^^^^^^^^^
index dfc43d7a5d563e97d2aaa3dcdab3329a03d0a115..ba2e2a938da8233fcd88615131305479a90ba9a2 100644 (file)
@@ -381,7 +381,7 @@ Reverse lookups
 Currently there are two options: Set ``ldap-method`` to ``strict`` to
 have the code automatically derive PTR records from A and AAAA records
 in the tree. Or, in ``simple`` and ``tree`` modes, create additional
-objects explictly mapping each address to a PTR record.
+objects explicitly mapping each address to a PTR record.
 
 For ``strict`` or ``simple`` modes, first create an object with an SOA
 record for the reverse-lookup zone(s) corresponding to the A and AAAA
@@ -429,7 +429,7 @@ Tree mode requires each component to be a dc element of its own:
     associateddomain:1.0.1.10.in-addr.arpa
 
 To use this kind of record, add the dnsdomain2 schema to the
-configuration of ther LDAP server.
+configuration of the LDAP server.
 
 **CAUTION:** ``ldap-method=strict`` can not be used if zone transfers
 (AXFR) are needed to other name servers. Distributing zones can only be
index 36aa329b575b69928e99f30639ea0f53012b56cb..cc86d8733b8e4a6ff4ad4dd1c62146eee87775c2 100644 (file)
@@ -53,7 +53,7 @@ Synchronisation mode: sync, nosync, nometasync, mapasync
 Default: mapasync
 
 * ``sync``: LMDB synchronous mode. Safest option, but also slightly slower. Can  also be enabled with ``lmdb-sync-mode=`` 
-* ``nosync``: don't flush systems buffers to disk when committing a transation.
+* ``nosync``: don't flush systems buffers to disk when committing a transaction.
   This means a system crash can corrupt the database or lose the last transactions if buffers are not yet flushed to disk.
 * ``nometasync``: flush system buffers to disk only once per transaction, omit the metadata flush. This maintains database integrity, but can potentially lose the last committed transaction if the operating system crashes.
 * ``mapasync``: (default). Use asynchronous flushes to disk. As with nosync, a system crash can then corrupt the database or lose the last transactions.
index 6915b5d8f913fe483ad252db757783e7379ce5fe..330f3fb139a0a310d876e3a4423deb82c85d290d 100644 (file)
@@ -122,7 +122,7 @@ Bug fixes
    integers 16 bits, fixes
    `#5443 <https://github.com/PowerDNS/pdns/issues/5443>`__
 -  `#5346 <https://github.com/PowerDNS/pdns/pull/5346>`__: configure.ac:
-   Corrects syntax error in test statement on existance of
+   Corrects syntax error in test statement on existence of
    libcrypto\_ecdsa (shinsterneck)
 -  `#5440 <https://github.com/PowerDNS/pdns/pull/5440>`__: configure.ac:
    Fix quoting issue fixes
index dc346d6483e1e594512e05e124b18dcd7c0e2624..fa23a403d9442a1111e873edcf45d030f0f3c488 100644 (file)
@@ -1,6 +1,18 @@
 Changelogs for 4.1.x
 ====================
 
+.. changelog::
+  :version: 4.1.14
+  :released: September 2nd 2020
+
+  This release contains the fix for :doc:`PowerDNS Security Advisory 2020-05 <../security-advisories/powerdns-advisory-2020-05>` (CVE-2020-17482)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9500
+
+    Raise an exception on invalid hex content in unknown records.
+
 .. changelog::
   :version: 4.1.13
   :released: August 9th 2019
@@ -883,7 +895,7 @@ Changelogs for 4.1.x
 
     Existing zone files may now be interpreted differently.
     Specifically, where we previously used the SOA minimum field for the default
-    TTL if none was set explictly, or no $TTL was set, we now use the TTL from
+    TTL if none was set explicitly, or no $TTL was set, we now use the TTL from
     the previous line.
 
   .. change::
index b02102f4eec39999e7888fb193b041924a52b805..731c8adf2c977767c5f10d9ce50bb3ff69e2c55f 100644 (file)
@@ -1,6 +1,65 @@
 Changelogs for 4.2.x
 ====================
 
+.. changelog::
+  :version: 4.2.3
+  :released: September 2nd 2020
+
+  This release contains the fix for :doc:`PowerDNS Security Advisory 2020-05 <../security-advisories/powerdns-advisory-2020-05>` (CVE-2020-17482)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9499
+
+    Raise an exception on invalid hex content in unknown records.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9191
+    :tickets: 9181
+
+    mydns: add SOA to list() output
+
+.. changelog::
+  :version: 4.2.2
+  :released: 9th of April 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9010
+
+    fix records ending up in wrong packet section (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9003, 8736
+
+    cache: strictly enforce maximum size, and improve cleanup routine
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9001
+
+    avoid IXFR-in corruption when deltas come in close together (please see the :ref:`ixfr-in-corruption-4.2.2` upgrade notes)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8786
+
+    api: add includerings option to statistics endpoint
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8612
+
+    fix out-of-bound access for zero length "serialized" string when using lmdbbackend. (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8602
+
+    bind backend: pthread_mutex_t should be inited and destroyed and not be copied
+
 .. changelog::
   :version: 4.2.1
   :released: 2nd of December 2019
@@ -40,6 +99,14 @@ Changelogs for 4.2.x
 
     API: reduce number of database connections (Kees Monshouwer)
 
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8497
+
+    Clear the caches for the entire zone after a patch operation (was apex only).
+    The default default-api-rectify setting was ignored in patchZone(), rectify only took place when the API-RECTIFY metadata was set to "1".
+    (Kees Monshouwer)
+
   .. change::
     :tags: Improvements
     :pullreq: 8546
@@ -1432,7 +1499,7 @@ Changelogs for 4.2.x
     :pullreq: 5361
     :tickets: 3602
 
-    Make requests always return to sender, for usage in multimaster slave zones. Also - made sure that the master that is questioned for updates will be selected randomly, to prevent repeatidally asking a dead master for updates.
+    Make requests always return to sender, for usage in multimaster slave zones. Also - made sure that the master that is questioned for updates will be selected randomly, to prevent repeatedly asking a dead master for updates.
 
   .. change::
     :tags: Improvements, API
index f1e598c91e2b74d2e9deea95d116be45c8375ca1..cac9394173a9d31d116ce77b33803c5b8069ea6d 100644 (file)
@@ -1,6 +1,126 @@
 Changelogs for 4.3.x
 ====================
 
+.. changelog::
+  :version: 4.3.1
+  :released: 22nd of September 2020
+
+  This is version 4.3.1 of the Authoritative Server.
+  This release contains the fix for :doc:`PowerDNS Security Advisory 2020-05 <../security-advisories/powerdns-advisory-2020-05>` (CVE-2020-17482).
+  It also contains several other fixes and improvements:
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9498
+
+    Raise an exception on invalid hex content in unknown records.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9444
+
+    Handle the extra single-row result set of MySQL stored procedures (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9036
+
+    EL8 pkgs: Build mysql backend against mariadb-connector-c-devel
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9219
+
+    gpgsql: Reintroduce prepared statements (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9233
+
+    gsqlite3backend: add missing indexes (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9224
+
+    use real remote for supermaster createSlaveDomain() (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9176
+
+    Optimize IXFR-to-AXFR fallback path (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9013
+
+    Install bind SQL schema files as part of bindbackend (Chris Hofstaedtler)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 9083
+
+    add ubuntu focal target
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9480
+
+    Do not send out of zone lookups to the backends (Kees Monshouwer)
+
+.. changelog::
+  :version: 4.3.0
+  :released: 7th of April 2020
+
+  This is version 4.3.0 of the Authoritative Server.
+  It contains all changes mentioned in the alpha, beta and RC versions below, plus two more bugfixes.
+
+  If you are upgrading from beta2 or rc2, AND ONLY THEN, please read `pull request #8975 <https://github.com/PowerDNS/pdns/pull/8975>`__ very carefully.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8977
+
+    avoid IXFR-in corruption when deltas come in close together (please see the :ref:`ixfr-in-corruption-4.3.0` upgrade notes)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8975
+
+    improve sql schema updates
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8951
+
+    reduce the number of temporary memory allocations
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8973
+
+    Fix NSECx for unpublished DNSKEYs properly
+
+.. changelog::
+  :version: 4.3.0-rc2
+  :released: 18th of March 2020
+
+  This is the first Release Candidate for version 4.3.0 of the Authoritative Server.
+  The version called 4.3.0-rc1 was never released because of the cache cleanup change mentioned below.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8924
+
+    Make sure we look at 10% of all cached items during cleanup (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8936
+
+    emit correct NSEC/NSEC3 bitmaps in hidden key situations (Robin Geuze)
+
 .. changelog::
   :version: 4.3.0-beta2
   :released: 21st of February 2020
index fc7f8a70f721e01fdf090bf301d0e60af0b30daa..7c204465687465b73dae1c7c88d1a414da6176ba 100644 (file)
@@ -722,7 +722,7 @@ Changes between RC2 and 3.4.0:
 -  `commit 016d810 <https://github.com/PowerDNS/pdns/commit/016d810>`__:
    improve postgresql detection during ./configure
 -  `commit dce1e90 <https://github.com/PowerDNS/pdns/commit/dce1e90>`__:
-   DNAME: don't sign the synthesised CNAME
+   DNAME: don't sign the synthesized CNAME
 -  `commit 25e7af3 <https://github.com/PowerDNS/pdns/commit/25e7af3>`__:
    send empty SERVFAIL after a backend throws a DBException, instead of
    including useless content
@@ -3990,7 +3990,7 @@ Miscellaneous
    `commit
    569 <http://wiki.powerdns.com/projects/trac/changeset/569>`__.
 -  PowerDNS now reports if it is running in 32 or 64 bit mode, useful
-   for bi-arch users that need to know if they are benefitting from
+   for bi-arch users that need to know if they are benefiting from
    `AMD's great processor <http://www.amd.com>`__. `commit
    571 <http://wiki.powerdns.com/projects/trac/changeset/571>`__.
 -  **dnsscope** compiles again, `commit
index 2325d0955377aafc142943a48b6a42de681edfc3..b7ad8da7bcb39f10b388255b8cf0e453965e3aa9 100644 (file)
@@ -4,57 +4,106 @@ PKCS#11 support
 .. note::
   This feature is experimental, use at your own risk!
 
-.. deprecated:: 4.0.0
-  slot IDs are deprecated, and you are expected to use slot label instead
+To enable it, compile PowerDNS Authoritative Server using ``--enable-experimental-pkcs11`` flag on configure.
+This requires you to have the p11-kit libraries and headers.
 
-To enable it, compile PowerDNS Authoritative Server using
-``--enable-experimental-pkcs11`` flag on configure. This requires you to
-have p11-kit libraries and headers.
+You can also log on to the tokens after starting the server, in this case you need to edit your PKCS#11 cryptokey record and remove PIN or set it empty.
+Do this after assigning/creating a key, as the PIN is required for assigning keys to zone.
 
-You can also log on to the tokens after starting server, in this case
-you need to edit your PKCS#11 cryptokey record and remove PIN or set it
-empty. PIN is required for assigning keys to zone.
-
-Using with SoftHSM
-------------------
+Using PKCS#11 with SoftHSM
+--------------------------
 
 .. warning::
-  Due to an interaction between `SoftHSM and Botan <https://github.com/PowerDNS/pdns/issues/2496>`__,
-  the PowerDNS Authoritative Server **will most likely** crash on exit when built with ``--enable-botan1.10 --enable-experimental-pkcs11``.
+  Due to an interaction between `SoftHSM and Botan <https://github.com/PowerDNS/pdns/issues/2496>`__, the PowerDNS Authoritative Server **will most likely** crash on exit when built with ``--enable-botan1.10 --enable-experimental-pkcs11``.
   In 4.2.0, Botan support has been removed and this is no longer an issue.
 
-To test this feature, a software HSM can be used. It is **not
-recommended** to use this in production.
+To test this feature, a software HSM can be used.
+It is **not recommended** to do this in production.
 
-Instructions on how to setup SoftHSM to work with the feature after
-compilation on Ubuntu/Debian (tested with Ubuntu 12.04 and 14.04).
+These instructions have been tested on Debian 10 (Buster).
 
-- ``apt-get install softhsm p11-kit opensc``
-- create directory ``/etc/pkcs11/modules``
-- create a file ``softhsm`` (``softhsm.module`` on newer versions),
-  with contents:::
+- ``apt-get install softhsm p11-kit``
+- Verify that it works: ``p11-kit -l``, you should see ``softhsm2: .....``
+- Create a token::
 
-    module: /home/cmouse/softhsm/lib/softhsm/libsofthsm.so     managed: yes
+    softhsm2-util --init-token --label my-pkcs11-dnskey --free --pin 1234 --so-pin 1234
 
-- Verify that it works: ``p11-kit -l``
-- Create at least two tokens (ksk and zsk) with (slot-number starts from 0)::
+- Assign the token to a zone (it says KSK, but because there is no ZSK, this will become a CSK)::
 
-    sudo softhsm --init-token --slot slot-number --label zone-ksk|zone-zsk --pin some-pin --so-pin another-pin
+    pdnsutil hsm assign example.com ecdsa256 ksk softhsm2 my-pkcs11-dnskey 1234 'my key' 'my pub key'
 
--  Using pkcs11-tool, initialize your new keys.::
+- Create the key (for 25, use the ID shown by the previous command)::
 
-    sudo pkcs11-tool --module=/home/cmouse/softhsm/lib/softhsm/libsofthsm.so -l -p some-pin -k --key-type RSA:2048 -a zone-ksk|zone-zsk --slot-index slot-number
+    pdnsutil hsm create-key example.com 25
 
--  Assign the keys using (note that token label is not necessarily same
-   as object label, see p11-kit -l)::
+-  Verify that everything worked, you should see valid data there::
 
-    pdnsutil hsm assign zone rsasha256 ksk|zsk softhsm token-label pin zone-ksk|zsk
+    pdnsutil show-zone example.com
 
--  Verify that everything worked, you should see valid data there::
+SoftHSM2 with forwarding
+------------------------
 
-    pdnsutil show-zone zone
+Based on https://p11-glue.github.io/p11-glue/p11-kit/manual/remoting.html.
+
+You need to install ``gnutls-bin`` to get token URLs.
+
+You cannot run ``p11-kit server`` as root, so you will need some user for running it. This user must be in the ``softhsm`` group.
+
+These commands need to be run as the non-root user (we shall call it ``tokenuser``).
+
+First, set up your token::
+
+   softhsm2-util --init-token --label "ecdsa#1" --pin 1234 --so-pin 1234 --free
+   pkcs11-tool --module /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so --keypairgen --key-type EC:prime256v1 --pin 1234 -a 'my key' --token-label "ecdsa#1"
+
+Ensure it's there::
+
+   pkcs11-tool --module /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so -l -O -p 1234
+
+Get the URL for ``p11-kit server``, which is needed for the server::
+
+   p11tool --provider /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-client.so --list-tokens
+
+Set up forwarding::
+
+  cat <<EOF > /etc/pkcs11/modules/p11-kit-client.module
+  module: /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-client.so
+  EOF
+
+  p11-kit server -u pdns --provider /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so "pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=29fdc44dc0d61539;token=ecdsa%231"
+  P11_KIT_SERVER_ADDRESS=unix:path=/run/user/1000/p11-kit/pkcs11-5198; export P11_KIT_SERVER_ADDRESS;
+  P11_KIT_SERVER_PID=5199; export P11_KIT_SERVER_PID;
+
+You will need those values in PowerDNS running environment. Now you can verify that the token is reachable as ``pdns`` user with::
+
+  pkcs11-tool --module /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-client.so -T
+  Available slots:
+  Slot 0 (0x10): SoftHSM slot ID 0x40d61539
+    token label        : ecdsa#1
+    token manufacturer : SoftHSM project
+    token model        : SoftHSM v2
+    token flags        : login required, rng, token initialized, PIN initialized, other flags=0x20
+    hardware version   : 2.5
+    firmware version   : 2.5
+    serial num         : 29fdc44dc0d61539
+    pin min/max        : 4/255
+
+Then assign the HSM token to your zone with::
+
+  pdnsutil hsm assign example.com ecdsa256 ksk p11-kit-client 'ecdsa#1' 1234 'my key'
+
+And then verify with ``show-zone`` that the zone now has a valid key.
+
+You can do this over SSH as well (note that the example connects from token server to DNS server)::
+
+    ssh -R /var/run/pdns/pkcs11:${P11_KIT_SERVER_ADDRESS#*=} pdns@server
+    export P11_KIT_SERVER_ADDRESS=/var/run/pdns/pkcs11
+
+Verify that the token is visible::
+
+   pkcs11-tool --module /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-client.so -T
 
--  SoftHSM signatures are fast enough to be used in live environment.
+Then use the ``pdnsutil hsm assign`` command like before to assign the key to your zone; now you have DNSSEC over SSH.
 
 Using CryptAS
 -------------
@@ -96,8 +145,8 @@ Smart Card token on Ubuntu 14.04.
   Manager no longer can show your token certificates and keys, at least
   on version v6.23.04. ::
 
-    pkcs11-tool --module=/home/cmouse/softhsm/lib/softhsm/libsofthsm.so -l -p some-pin -k --key-type RSA:2048 -a zone-ksk
-    pkcs11-tool --module=/home/cmouse/softhsm/lib/softhsm/libsofthsm.so -l -p some-pin -k --key-type RSA:2048 -a zone-zsk
+    pkcs11-tool --module=/lib64/libASEP11.so -l -p some-pin -k --key-type RSA:2048 -a zone-ksk
+    pkcs11-tool --module=/lib64/libASEP11.so -l -p some-pin -k --key-type RSA:2048 -a zone-zsk
 
 - Verify that keys are there::
 
index 4321d59c5f4aaacc64a644e0f5f3e0fc8fe96578..dbe94322ecb5b35e11a5251b2339ef8097254aea 100644 (file)
@@ -58,5 +58,5 @@ while not strictly conforming to :rfc:`6979`.
 
 .. note::
   Actual supported algorithms depend on the crypto-libraries
-  PowerDNS was compiled against. To check the supported DNSSEC algoritms
+  PowerDNS was compiled against. To check the supported DNSSEC algorithms
   in your build of PowerDNS, run ``pdnsutil list-algorithms``.
index a1392a28a36377590f82cfbe31f526f871ac5d98..55629cc56bb147137fd6f7769b78028b813ba67b 100644 (file)
@@ -518,3 +518,6 @@ Simple example script:
 
       return false
     end
+
+Additional updatepolicy example scripts can be found in our
+`Wiki <https://github.com/PowerDNS/pdns/wiki/Lua-Examples-(Authoritative)>`__.
index 9b3f59e0ba4639a97d421ecfe86474d168010868..5a152a1c5a25dd323ec177b4c589ba7ea76d6cc4 100644 (file)
@@ -8,6 +8,11 @@ zone behaves in certain circumstances.
   Domain metadata is only available for DNSSEC capable
   backends! Make sure to enable the proper '-dnssec' setting to benefit.
 
+.. warning::
+  When multiple backends are in use, domain metadata is only retrieved from
+  and written to the first DNSSEC-capable backend, no matter where the related
+  zones live.
+
 For the BIND backend, this information is either stored in the
 :ref:`setting-bind-dnssec-db` or the hybrid database,
 depending on your settings.
@@ -113,6 +118,8 @@ Use this named TSIG key to retrieve this zone from its master, see :ref:`tsig-pr
 
 GSS-ALLOW-AXFR-PRINCIPAL
 ------------------------
+  .. versionchanged:: 4.3.1
+    GSS support was removed
 
 Allow this GSS principal to perform AXFR retrieval. Most commonly it is
 ``host/something@REALM``, ``DNS/something@REALM`` or ``user@REALM``.
@@ -120,6 +127,8 @@ Allow this GSS principal to perform AXFR retrieval. Most commonly it is
 
 GSS-ACCEPTOR-PRINCIPAL
 ----------------------
+  .. versionchanged:: 4.4.0
+    GSS support was removed
 
 Use this principal for accepting GSS context.
 (See :ref:`tsig-gss-tsig`).
index ae62df5c748c9468114822ecdc3d0595f8585506..21b369c94ab42d456b9a8664e9229da9c6645a53 100644 (file)
@@ -21,6 +21,280 @@ The following webserver related configuration items are available:
 * :ref:`setting-webserver-allow-from`: Netmasks that are allowed to connect to the webserver
 * :ref:`setting-webserver-max-bodysize`: Maximum request/response body size in megabytes
 
+
+Metrics Endpoint
+----------------
+
+.. versionadded:: 4.4.0
+
+The webserver exposes a metrics-endpoint that follows the `prometheus exposition-format <https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md>`_ on path ``/metrics``.
+
+The metrics listed are equivalent to the variables section on the index-page of the webserver (prefixed with ``pdns_auth_`` and replacing dashes with underscores).
+
+A simple ``GET`` request will return a response similar to the following:
+
+.. http:get:: /metrics
+
+::
+
+  HTTP/1.1 200 OK
+  Connection: close
+  Content-Length: 12044
+  Content-Type: text/plain
+  Server: PowerDNS/0.0.19015.0.master.ge719aae4e8
+
+  # HELP pdns_auth_corrupt_packets Number of corrupt packets received
+  # TYPE pdns_auth_corrupt_packets counter
+  pdns_auth_corrupt_packets 0
+  # HELP pdns_auth_deferred_cache_inserts Amount of cache inserts that were deferred because of maintenance
+  # TYPE pdns_auth_deferred_cache_inserts counter
+  pdns_auth_deferred_cache_inserts 0
+  # HELP pdns_auth_deferred_cache_lookup Amount of cache lookups that were deferred because of maintenance
+  # TYPE pdns_auth_deferred_cache_lookup counter
+  pdns_auth_deferred_cache_lookup 0
+  # HELP pdns_auth_deferred_packetcache_inserts Amount of packet cache inserts that were deferred because of maintenance
+  # TYPE pdns_auth_deferred_packetcache_inserts counter
+  pdns_auth_deferred_packetcache_inserts 0
+  # HELP pdns_auth_deferred_packetcache_lookup Amount of packet cache lookups that were deferred because of maintenance
+  # TYPE pdns_auth_deferred_packetcache_lookup counter
+  pdns_auth_deferred_packetcache_lookup 0
+  # HELP pdns_auth_dnsupdate_answers DNS update packets successfully answered.
+  # TYPE pdns_auth_dnsupdate_answers counter
+  pdns_auth_dnsupdate_answers 0
+  # HELP pdns_auth_dnsupdate_changes DNS update changes to records in total.
+  # TYPE pdns_auth_dnsupdate_changes counter
+  pdns_auth_dnsupdate_changes 0
+  # HELP pdns_auth_dnsupdate_queries DNS update packets received.
+  # TYPE pdns_auth_dnsupdate_queries counter
+  pdns_auth_dnsupdate_queries 0
+  # HELP pdns_auth_dnsupdate_refused DNS update packets that are refused.
+  # TYPE pdns_auth_dnsupdate_refused counter
+  pdns_auth_dnsupdate_refused 0
+  # HELP pdns_auth_incoming_notifications NOTIFY packets received.
+  # TYPE pdns_auth_incoming_notifications counter
+  pdns_auth_incoming_notifications 0
+  # HELP pdns_auth_overload_drops Queries dropped because backends overloaded
+  # TYPE pdns_auth_overload_drops counter
+  pdns_auth_overload_drops 0
+  # HELP pdns_auth_packetcache_hit Number of hits on the packet cache
+  # TYPE pdns_auth_packetcache_hit counter
+  pdns_auth_packetcache_hit 0
+  # HELP pdns_auth_packetcache_miss Number of misses on the packet cache
+  # TYPE pdns_auth_packetcache_miss counter
+  pdns_auth_packetcache_miss 0
+  # HELP pdns_auth_packetcache_size Number of entries in the packet cache
+  # TYPE pdns_auth_packetcache_size gauge
+  pdns_auth_packetcache_size 0
+  # HELP pdns_auth_query_cache_hit Number of hits on the query cache
+  # TYPE pdns_auth_query_cache_hit counter
+  pdns_auth_query_cache_hit 0
+  # HELP pdns_auth_query_cache_miss Number of misses on the query cache
+  # TYPE pdns_auth_query_cache_miss counter
+  pdns_auth_query_cache_miss 0
+  # HELP pdns_auth_query_cache_size Number of entries in the query cache
+  # TYPE pdns_auth_query_cache_size gauge
+  pdns_auth_query_cache_size 0
+  # HELP pdns_auth_rd_queries Number of recursion desired questions
+  # TYPE pdns_auth_rd_queries counter
+  pdns_auth_rd_queries 0
+  # HELP pdns_auth_recursing_answers Number of recursive answers sent out
+  # TYPE pdns_auth_recursing_answers counter
+  pdns_auth_recursing_answers 0
+  # HELP pdns_auth_recursing_questions Number of questions sent to recursor
+  # TYPE pdns_auth_recursing_questions counter
+  pdns_auth_recursing_questions 0
+  # HELP pdns_auth_recursion_unanswered Number of packets unanswered by configured recursor
+  # TYPE pdns_auth_recursion_unanswered counter
+  pdns_auth_recursion_unanswered 0
+  # HELP pdns_auth_security_status Security status based on regular polling
+  # TYPE pdns_auth_security_status gauge
+  pdns_auth_security_status 0
+  # HELP pdns_auth_servfail_packets Number of times a server-failed packet was sent out
+  # TYPE pdns_auth_servfail_packets counter
+  pdns_auth_servfail_packets 0
+  # HELP pdns_auth_signatures Number of DNSSEC signatures made
+  # TYPE pdns_auth_signatures counter
+  pdns_auth_signatures 0
+  # HELP pdns_auth_tcp_answers Number of answers sent out over TCP
+  # TYPE pdns_auth_tcp_answers counter
+  pdns_auth_tcp_answers 0
+  # HELP pdns_auth_tcp_answers_bytes Total size of answers sent out over TCP
+  # TYPE pdns_auth_tcp_answers_bytes counter
+  pdns_auth_tcp_answers_bytes 0
+  # HELP pdns_auth_tcp_queries Number of TCP queries received
+  # TYPE pdns_auth_tcp_queries counter
+  pdns_auth_tcp_queries 0
+  # HELP pdns_auth_tcp4_answers Number of IPv4 answers sent out over TCP
+  # TYPE pdns_auth_tcp4_answers counter
+  pdns_auth_tcp4_answers 0
+  # HELP pdns_auth_tcp4_answers_bytes Total size of answers sent out over TCPv4
+  # TYPE pdns_auth_tcp4_answers_bytes counter
+  pdns_auth_tcp4_answers_bytes 0
+  # HELP pdns_auth_tcp4_queries Number of IPv4 TCP queries received
+  # TYPE pdns_auth_tcp4_queries counter
+  pdns_auth_tcp4_queries 0
+  # HELP pdns_auth_tcp6_answers Number of IPv6 answers sent out over TCP
+  # TYPE pdns_auth_tcp6_answers counter
+  pdns_auth_tcp6_answers 0
+  # HELP pdns_auth_tcp6_answers_bytes Total size of answers sent out over TCPv6
+  # TYPE pdns_auth_tcp6_answers_bytes counter
+  pdns_auth_tcp6_answers_bytes 0
+  # HELP pdns_auth_tcp6_queries Number of IPv6 TCP queries received
+  # TYPE pdns_auth_tcp6_queries counter
+  pdns_auth_tcp6_queries 0
+  # HELP pdns_auth_timedout_packets Number of packets which weren't answered within timeout set
+  # TYPE pdns_auth_timedout_packets counter
+  pdns_auth_timedout_packets 0
+  # HELP pdns_auth_udp_answers Number of answers sent out over UDP
+  # TYPE pdns_auth_udp_answers counter
+  pdns_auth_udp_answers 0
+  # HELP pdns_auth_udp_answers_bytes Total size of answers sent out over UDP
+  # TYPE pdns_auth_udp_answers_bytes counter
+  pdns_auth_udp_answers_bytes 0
+  # HELP pdns_auth_udp_do_queries Number of UDP queries received with DO bit
+  # TYPE pdns_auth_udp_do_queries counter
+  pdns_auth_udp_do_queries 0
+  # HELP pdns_auth_udp_queries Number of UDP queries received
+  # TYPE pdns_auth_udp_queries counter
+  pdns_auth_udp_queries 0
+  # HELP pdns_auth_udp4_answers Number of IPv4 answers sent out over UDP
+  # TYPE pdns_auth_udp4_answers counter
+  pdns_auth_udp4_answers 0
+  # HELP pdns_auth_udp4_answers_bytes Total size of answers sent out over UDPv4
+  # TYPE pdns_auth_udp4_answers_bytes counter
+  pdns_auth_udp4_answers_bytes 0
+  # HELP pdns_auth_udp4_queries Number of IPv4 UDP queries received
+  # TYPE pdns_auth_udp4_queries counter
+  pdns_auth_udp4_queries 0
+  # HELP pdns_auth_udp6_answers Number of IPv6 answers sent out over UDP
+  # TYPE pdns_auth_udp6_answers counter
+  pdns_auth_udp6_answers 0
+  # HELP pdns_auth_udp6_answers_bytes Total size of answers sent out over UDPv6
+  # TYPE pdns_auth_udp6_answers_bytes counter
+  pdns_auth_udp6_answers_bytes 0
+  # HELP pdns_auth_udp6_queries Number of IPv6 UDP queries received
+  # TYPE pdns_auth_udp6_queries counter
+  pdns_auth_udp6_queries 0
+  # HELP pdns_auth_cpu_iowait Time spent waiting for I/O to complete by the whole system, in units of USER_HZ
+  # TYPE pdns_auth_cpu_iowait counter
+  pdns_auth_cpu_iowait 2739
+  # HELP pdns_auth_cpu_steal Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ
+  # TYPE pdns_auth_cpu_steal counter
+  pdns_auth_cpu_steal 0
+  # HELP pdns_auth_fd_usage Number of open filedescriptors
+  # TYPE pdns_auth_fd_usage gauge
+  pdns_auth_fd_usage 26
+  # HELP pdns_auth_key_cache_size Number of entries in the key cache
+  # TYPE pdns_auth_key_cache_size gauge
+  pdns_auth_key_cache_size 0
+  # HELP pdns_auth_latency Average number of microseconds needed to answer a question
+  # TYPE pdns_auth_latency gauge
+  pdns_auth_latency 0
+  # HELP pdns_auth_meta_cache_size Number of entries in the metadata cache
+  # TYPE pdns_auth_meta_cache_size gauge
+  pdns_auth_meta_cache_size 0
+  # HELP pdns_auth_open_tcp_connections Number of currently open TCP connections
+  # TYPE pdns_auth_open_tcp_connections gauge
+  pdns_auth_open_tcp_connections 0
+  # HELP pdns_auth_qsize_q Number of questions waiting for database attention
+  # TYPE pdns_auth_qsize_q gauge
+  pdns_auth_qsize_q 0
+  # HELP pdns_auth_real_memory_usage Actual unique use of memory in bytes (approx)
+  # TYPE pdns_auth_real_memory_usage gauge
+  pdns_auth_real_memory_usage 133189632
+  # HELP pdns_auth_ring_logmessages_capacity Maximum number of entries in the logmessages ring
+  # TYPE pdns_auth_ring_logmessages_capacity gauge
+  pdns_auth_ring_logmessages_capacity 10000
+  # HELP pdns_auth_ring_logmessages_size Number of entries in the logmessages ring
+  # TYPE pdns_auth_ring_logmessages_size gauge
+  pdns_auth_ring_logmessages_size 7
+  # HELP pdns_auth_ring_noerror_queries_capacity Maximum number of entries in the noerror-queries ring
+  # TYPE pdns_auth_ring_noerror_queries_capacity gauge
+  pdns_auth_ring_noerror_queries_capacity 10000
+  # HELP pdns_auth_ring_noerror_queries_size Number of entries in the noerror-queries ring
+  # TYPE pdns_auth_ring_noerror_queries_size gauge
+  pdns_auth_ring_noerror_queries_size 0
+  # HELP pdns_auth_ring_nxdomain_queries_capacity Maximum number of entries in the nxdomain-queries ring
+  # TYPE pdns_auth_ring_nxdomain_queries_capacity gauge
+  pdns_auth_ring_nxdomain_queries_capacity 10000
+  # HELP pdns_auth_ring_nxdomain_queries_size Number of entries in the nxdomain-queries ring
+  # TYPE pdns_auth_ring_nxdomain_queries_size gauge
+  pdns_auth_ring_nxdomain_queries_size 0
+  # HELP pdns_auth_ring_queries_capacity Maximum number of entries in the queries ring
+  # TYPE pdns_auth_ring_queries_capacity gauge
+  pdns_auth_ring_queries_capacity 10000
+  # HELP pdns_auth_ring_queries_size Number of entries in the queries ring
+  # TYPE pdns_auth_ring_queries_size gauge
+  pdns_auth_ring_queries_size 0
+  # HELP pdns_auth_ring_remotes_capacity Maximum number of entries in the remotes ring
+  # TYPE pdns_auth_ring_remotes_capacity gauge
+  pdns_auth_ring_remotes_capacity 10000
+  # HELP pdns_auth_ring_remotes_corrupt_capacity Maximum number of entries in the remotes-corrupt ring
+  # TYPE pdns_auth_ring_remotes_corrupt_capacity gauge
+  pdns_auth_ring_remotes_corrupt_capacity 10000
+  # HELP pdns_auth_ring_remotes_corrupt_size Number of entries in the remotes-corrupt ring
+  # TYPE pdns_auth_ring_remotes_corrupt_size gauge
+  pdns_auth_ring_remotes_corrupt_size 0
+  # HELP pdns_auth_ring_remotes_size Number of entries in the remotes ring
+  # TYPE pdns_auth_ring_remotes_size gauge
+  pdns_auth_ring_remotes_size 0
+  # HELP pdns_auth_ring_remotes_unauth_capacity Maximum number of entries in the remotes-unauth ring
+  # TYPE pdns_auth_ring_remotes_unauth_capacity gauge
+  pdns_auth_ring_remotes_unauth_capacity 10000
+  # HELP pdns_auth_ring_remotes_unauth_size Number of entries in the remotes-unauth ring
+  # TYPE pdns_auth_ring_remotes_unauth_size gauge
+  pdns_auth_ring_remotes_unauth_size 0
+  # HELP pdns_auth_ring_servfail_queries_capacity Maximum number of entries in the servfail-queries ring
+  # TYPE pdns_auth_ring_servfail_queries_capacity gauge
+  pdns_auth_ring_servfail_queries_capacity 10000
+  # HELP pdns_auth_ring_servfail_queries_size Number of entries in the servfail-queries ring
+  # TYPE pdns_auth_ring_servfail_queries_size gauge
+  pdns_auth_ring_servfail_queries_size 0
+  # HELP pdns_auth_ring_unauth_queries_capacity Maximum number of entries in the unauth-queries ring
+  # TYPE pdns_auth_ring_unauth_queries_capacity gauge
+  pdns_auth_ring_unauth_queries_capacity 10000
+  # HELP pdns_auth_ring_unauth_queries_size Number of entries in the unauth-queries ring
+  # TYPE pdns_auth_ring_unauth_queries_size gauge
+  pdns_auth_ring_unauth_queries_size 0
+  # HELP pdns_auth_signature_cache_size Number of entries in the signature cache
+  # TYPE pdns_auth_signature_cache_size gauge
+  pdns_auth_signature_cache_size 0
+  # HELP pdns_auth_sys_msec Number of msec spent in system time
+  # TYPE pdns_auth_sys_msec counter
+  pdns_auth_sys_msec 56
+  # HELP pdns_auth_udp_in_errors UDP 'in' errors
+  # TYPE pdns_auth_udp_in_errors counter
+  pdns_auth_udp_in_errors 151
+  # HELP pdns_auth_udp_noport_errors UDP 'noport' errors
+  # TYPE pdns_auth_udp_noport_errors counter
+  pdns_auth_udp_noport_errors 9
+  # HELP pdns_auth_udp_recvbuf_errors UDP 'recvbuf' errors
+  # TYPE pdns_auth_udp_recvbuf_errors counter
+  pdns_auth_udp_recvbuf_errors 0
+  # HELP pdns_auth_udp_sndbuf_errors UDP 'sndbuf' errors
+  # TYPE pdns_auth_udp_sndbuf_errors counter
+  pdns_auth_udp_sndbuf_errors 9
+  # HELP pdns_auth_uptime Uptime of process in seconds
+  # TYPE pdns_auth_uptime counter
+  pdns_auth_uptime 672
+  # HELP pdns_auth_user_msec Number of msec spent in user time
+  # TYPE pdns_auth_user_msec counter
+  pdns_auth_user_msec 48
+
+
+Prometheus can then be configured to scrape metrics from this endpoint using a simple job description like the following:
+
+.. prometheus scrape-job::
+
+  scrape_configs:
+    - job_name: 'pdns_auth'
+      scrape_interval: 1m
+      static_configs:
+        - targets: ['pdns_auth_host:pdns_auth_ws_port'] 
+
+Further details can be gathered from the `prometheus docs <https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config>`_.
+
+
 Enabling the API
 ----------------
 
index 82579902ef22220f7fb2823fce5c1acbebda0dad..76917d8468bea3de0ed064358328e4c1ad1bff5d 100644 (file)
@@ -159,6 +159,11 @@ paths:
           in: path
           required: true
           description: The id of the zone to retrieve
+        - name: rrsets
+          in: query
+          description: '“true” (default) or “false”, whether to include the “rrsets” in the response Zone object.'
+          type: boolean
+          default: true
       responses:
         '200':
           description: A Zone
@@ -443,7 +448,7 @@ paths:
 
   '/servers/{server_id}/zones/{zone_id}/metadata':
     get:
-      summary: Get all the MetaData associated with the zone.
+      summary: 'Get all the Metadata associated with the zone.'
       operationId: listMetadata
       tags:
         - zonemetadata
@@ -482,20 +487,18 @@ paths:
           in: path
           required: true
         - name: metadata
-          description: List of metadata to add/create
+          description: Metadata object with list of values to create
           required: true
           in: body
           schema:
-            type: array
-            items:
-              $ref: '#/definitions/Metadata'
+            $ref: '#/definitions/Metadata'
       responses:
         '204':
           description: OK
 
   '/servers/{server_id}/zones/{zone_id}/metadata/{metadata_kind}':
     get:
-      summary: Get the content of a single kind of domain metadata as a list of MetaData objects.
+      summary: 'Get the content of a single kind of domain metadata as a Metadata object.'
       operationId: getMetadata
       tags:
         - zonemetadata
@@ -514,14 +517,15 @@ paths:
           type: string
           in: path
           required: true
-          description: '???'
+          description: The kind of metadata
       responses:
         '200':
-          description: List of Metadata objects
+          description: Metadata object with list of values
           schema:
             $ref: '#/definitions/Metadata'
     put:
-      summary: 'Modify the content of a single kind of domain metadata.'
+      summary: 'Replace the content of a single kind of domain metadata.'
+      description: 'Creates a set of metadata entries of given kind for the zone. Existing metadata entries for the zone with the same kind are removed.'
       operationId: modifyMetadata
       tags:
         - zonemetadata
@@ -547,8 +551,10 @@ paths:
           schema:
             $ref: '#/definitions/Metadata'
       responses:
-        '204':
-          description: OK
+        '200':
+          description: Metadata object with list of values
+          schema:
+            $ref: '#/definitions/Metadata'
     delete:
       summary: 'Delete all items of a single kind of domain metadata.'
       operationId: deleteMetadata
@@ -569,9 +575,9 @@ paths:
           type: string
           in: path
           required: true
-          description: '???'
+          description: The kind of metadata
       responses:
-        '204':
+        '200':
           description: OK
 
   '/servers/{server_id}/zones/{zone_id}/cryptokeys':
@@ -801,6 +807,7 @@ paths:
          * Changing the Name, this will remove the key with tsigkey_id after adding.
          * Changing the Algorithm
          * Changing the Key
+
         Only the relevant fields have to be provided in the request body.
       operationId: putTSIGKey
       tags:
@@ -1234,7 +1241,7 @@ definitions:
         description: 'Whether or not the key is in active use'
       published:
         type: boolean
-        descriptioon: 'Whether or not the DNSKEY record is published in the zone'
+        description: 'Whether or not the DNSKEY record is published in the zone'
       dnskey:
         type: string
         description: 'The DNSKEY record for this key'
index 5509e21d569ea16d8a5abe698db03589a6df5a98..dce4448684c70bb51a3ebfd3d259ae93dcf5b72e 100644 (file)
@@ -18,7 +18,7 @@ Generating a new TSIG key
 
   {"name": "mytsigkey", "algorithm": "hmac-sha256"}
 
-Will yield a response similar to this (several headers ommitted):
+Will yield a response similar to this (several headers omitted):
 
 .. code-block:: http
 
index b8b8b595a8468cceb0ed1e215a87c5bc441dd217..ddda71559fb61fcdf4310ac0c3281d705203f742 100644 (file)
@@ -37,7 +37,7 @@ Changes made through the Zones API will always yield valid zone data, as the API
 
 DNSSEC-enabled zones should be :ref:`rectified <rules-for-filling-out-dnssec-fields>` after changing the zone data.
 This can be done by the API automatically after a change when the :ref:`metadata-api-rectify` metadata is set.
-When creating or updating a zone, the "api_rectify" field of the :json:object:`ZOne` can be set to `true` to enable this behaviour.
+When creating or updating a zone, the "api_rectify" field of the :json:object:`Zone` can be set to `true` to enable this behaviour.
 
 Backends might implement additional features (by coincidence or not).
 These things are not supported through the API.
index 086287c9c03c2a359a65a651199a953a46f5c321..a7710d412ca7478a9c415ceaf35a36ab1425526d 100644 (file)
@@ -61,7 +61,7 @@ If possible, supply the actual name of your domain and the IP address of your se
 I found a bug!
 ^^^^^^^^^^^^^^
 As much as we'd like to think we are perfect, bugs happen.
-If you have found a bug, please file a bug report on `GitHub <https://github.com/PowerDNS/pdns/issues/new>`_.
+If you have found a bug, please file a bug report on `GitHub <https://github.com/PowerDNS/pdns/issues/new?template=bug_report.md>`_.
 Please fill in the template and we'll try our best to help you.
 
 I found a security issue!
@@ -71,5 +71,5 @@ Please report this in private, see the :ref:`securitypolicy`.
 I have a good idea for a feature!
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 We like to work on new things!
-You can file a feature request on `GitHub <https://github.com/PowerDNS/pdns/issues/new>`_.
+You can file a feature request on `GitHub <https://github.com/PowerDNS/pdns/issues/new?template=feature_request.md>`__.
 
index 2b25b06f492a374fd2829d004d382e6a84eeb03e..0aaa2c36855f22d214d2c725819f0ffbc767a500 100644 (file)
@@ -32,7 +32,7 @@ required backend as follows:
 Redhat-based Systems
 ~~~~~~~~~~~~~~~~~~~~
 
-On RedHat based system there are 2 options to install PowerDNS, from
+On RedHat based systems there are 3 options to install PowerDNS, from
 `EPEL <https://fedoraproject.org/wiki/EPEL>`__, the `repository from
 Kees Monshouwer <https://www.monshouwer.eu/download/3rd_party/pdns/>`__
 or from `the PowerDNS repositories <https://repo.powerdns.com>`__:
@@ -49,6 +49,8 @@ The different backends can be installed using
 
     $ sudo yum install pdns-backend-$backend
 
+Note that for some of those package sources, the bind backend is shipped as part of the base ``pdns`` package, and there is no separate ``pdns-backend-bind`` package.
+
 FreeBSD
 ~~~~~~~
 
index 2707269e27c6c5cbb0d2a1743bca9b1efeb80b28..95e06e381d56c0d7e94e86a4467fd04ccd0cfb45 100644 (file)
@@ -3,7 +3,7 @@
 DNSName objects
 ^^^^^^^^^^^^^^^
 
-A :class:`DNSName` object represents a name in the DNS. It has serveral functions that can manipulate it without conversions to strings.
+A :class:`DNSName` object represents a name in the DNS. It has several functions that can manipulate it without conversions to strings.
 Creating a ``DNSName`` is done with the :func:`newDN`::
 
   myname = newDN("www.example.com")
@@ -37,7 +37,7 @@ Functions and methods of a ``DNSName``
 
   .. method:: DNSName:canonCompare(name) -> bool
 
-    Performs a comparaison of DNS names in canonical order.
+    Performs a comparison of DNS names in canonical order.
     Returns true if the DNSName comes before ``name``.
     See https://tools.ietf.org/html/rfc4034#section-6
 
@@ -92,7 +92,7 @@ Functions and methods of a ``DNSName``
 
   .. method:: DNSName::equal(name) -> bool
 
-    Perform a comparaison of the DNSName to the given ``name``.
+    Perform a comparison of the DNSName to the given ``name``.
     You can also compare directly two DNSName objects using
     the ``==`` operator
 
index 57f311c26b5b2e7f7fd59dd26dcf3af215fa143b..f50ad8f101dc3134d28dc196b75480de98c8d605 100644 (file)
@@ -118,4 +118,4 @@ Prefixing a mask with ``!`` excludes that mask from matching.
 
       Returns true if ``address`` matches any of the masks in the group.
 
-      :param ComboAddress address: The IP addres to match the netmasks against.
+      :param ComboAddress address: The IP address to match the netmasks against.
index 12c7bb810af7ae0c96b228643ced36f75cd24d2a..9bf04214ef163658929caea8f0a718ae313fdc9a 100644 (file)
@@ -17,4 +17,4 @@ bit in the query.
 Example
 -------
 
-``nsec3dig 8.8.8.8 53 doesntexist.isoc.nl TXT recurse``
+``nsec3dig 8.8.8.8 53 doesnotexist.isoc.nl TXT recurse``
index 8c1b8d1ff07272eefa34aeb42e8c2573c772128e..79e7d8b3fb8b494aa008cd4a01aca34c3ff31918 100644 (file)
@@ -147,10 +147,12 @@ respsizes
 
 Get a histogram of the response sizes.
 
-retrieve *DOMAIN*
-^^^^^^^^^^^^^^^^^
+retrieve *DOMAIN* [IP]
+^^^^^^^^^^^^^^^^^^^^^^
 
 Retrieve slave *DOMAIN* from its master. Done nearly immediately.
+If IP is specified, then retrieval is forced from the specified IP.
+Port may be specified in AFI specific manner.
 
 set *VARIABLE* *VALUE*
 ^^^^^^^^^^^^^^^^^^^^^^
@@ -190,4 +192,4 @@ Print the version of the running pdns daemon.
 See also
 --------
 
-pdns\_server(1)
+pdns\_server (1), pdnsutil (1)
index ceb86f12a6874f48052e43ebdfe56b7d1aa7948f..9ee765ae15414ece50f64c9ead8ed1da7f04c3fd 100644 (file)
@@ -29,7 +29,7 @@ See the online documentation for all options
 --loglevel=<LEVEL>      Set the logging level.
 --config                Show the currently configuration. There are three optional values:
                         --config=default show the default configuration.
-                        --config=diff    show modified options in the curent configuration.
+                        --config=diff    show modified options in the current configuration.
                         --config=check   parse the current configuration, with error checking.
 --help                  To view more options that are available use this program.
 
index b8a3baa596134429841dd8e86f28bdd453f309b1..dce9405ef3723d2d77c497b3bf982576435f6a6b 100644 (file)
@@ -160,6 +160,8 @@ ZONE MANIPULATION COMMANDS
 add-record *ZONE* *NAME* *TYPE* [*TTL*] *CONTENT*
     Add one or more records of *NAME* and *TYPE* to *ZONE* with *CONTENT* 
     and optional *TTL*. If *TTL* is not set, default will be used. 
+add-supermaster *IP* *NAMESERVER* [*ACCOUNT*]
+    Add a supermaster entry into the backend. This enables receiving zone updates from other servers.
 create-zone *ZONE*
     Create an empty zone named *ZONE*.
 create-slave-zone *ZONE* *MASTER* [*MASTER*]..
@@ -253,10 +255,10 @@ bench-db [*FILE*]
 
 OTHER TOOLS
 -----------
-ipencrypt *IP-ADDRESS* passsword
+ipencrypt *IP-ADDRESS* password
     Encrypt an IP address according to the 'ipcipher' standard
 
-ipdecrypt *IP-ADDRESS* passsword
+ipdecrypt *IP-ADDRESS* password
     Encrypt an IP address according to the 'ipcipher' standard
 
 See also
index 8ce4e1c6821882d335af9d1a32049425d7fe1db2..3fc868b96c88b987d6d01dd6075f398a1b3fa353 100644 (file)
@@ -31,13 +31,15 @@ hidesoadetails
     Don't show the SOA serial in the response.
 hidettl
     Replace TTLs with `[ttl]` in the response.
+proxy *TCP?* *SRC* *DST*
+    Wrap query in PROXYv2 protocol with these parameters. The first parameter accepts 0 for UDP and 1 for TCP. The second and third take IP addresses and port.
 recurse
     Set the RD bit in the question.
 showflags
     Show the NSEC3 flags in the response (they are hidden by default).
 tcp
     Use TCP instead of UDP to send the query.
-xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFSRC*
+xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFDST*
        Send an *XPF* additional with these parameters.
 
 Examples
@@ -48,6 +50,5 @@ Simple queries to local resolvers
     sdig ::1 53 example.com A recurse
 
 Query to a DNS-over-HTTPS server requesting dnssec and recursion
-    sdig https://dns.somesample.net/dns-query 443 example.com A dnssec recurse
-
+    sdig https://dns.example.net/dns-query 443 example.com A dnssec recurse
 
index 8191e416c8f923afea8e05d1892e5d2303f58e29..760089497f73382e016130efcc1967b38764a0f1 100644 (file)
@@ -66,7 +66,7 @@ Create a named.conf with all the domains as slave domains, e.g.:
 
 Make sure the directory is writable for the ``pdns_server`` process and
 that :ref:`setting-bind-config` parameter
-references this file. Now start PowerDNS and wait untill all zones are
+references this file. Now start PowerDNS and wait until all zones are
 transferred. Now you can change the zone type to master:
 
 ::
index d2f6aabe07059f7787eed13ad478da15983b5394..ff891db5b92796920bc0f43b16269e201f7b7b88 100644 (file)
@@ -222,7 +222,9 @@ So, to benefit from this feature, a backend needs to know about the IP
 address of the supermaster, and how PowerDNS will be listed in the set
 of NS records remotely, and the 'account' name of your supermaster.
 There is no need to fill the account name out but it does help keep
-track of where a domain comes from.
+track of where a domain comes from. 
+Adding a supermaster can be done either directly in the database,
+or by using the 'pdnsutil add-supermaster' command. 
 
 .. note::
   Removal of zones provisioned using the supermaster must be
index a709c7782085ca103df02f333af4d8b231e132a0..04b261ff6b6987cc4bf5fa35868666d7612154a0 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020030304 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020092201 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 
@@ -34,8 +34,8 @@ auth-4.0.4.security-status                              60 IN TXT "3 Upgrade now
 auth-4.0.5.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-03.html"
 auth-4.0.6.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
 auth-4.0.7.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-03.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-05.html"
-auth-4.0.8.security-status                              60 IN TXT "1 OK"
-auth-4.0.9.security-status                              60 IN TXT "1 OK"
+auth-4.0.8.security-status                              60 IN TXT "2 Unsupported release (EOL)"
+auth-4.0.9.security-status                              60 IN TXT "2 Unsupported release (EOL)"
 auth-4.1.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.1.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.1.0-rc3.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -49,20 +49,27 @@ auth-4.1.6.security-status                              60 IN TXT "3 Upgrade now
 auth-4.1.7.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
 auth-4.1.8.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
 auth-4.1.9.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
-auth-4.1.10.security-status                             60 IN TXT "1 OK"
-auth-4.1.11.security-status                             60 IN TXT "1 OK"
-auth-4.1.12.security-status                             60 IN TXT "1 OK"
-auth-4.1.13.security-status                             60 IN TXT "1 OK"
+auth-4.1.10.security-status                             60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.1.11.security-status                             60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.1.12.security-status                             60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.1.13.security-status                             60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.1.14.security-status                             60 IN TXT "1 OK"
 auth-4.2.0-alpha1.security-status                       60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
 auth-4.2.0-beta1.security-status                        60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
 auth-4.2.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 auth-4.2.0-rc2.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 auth-4.2.0-rc3.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.2.0.security-status                              60 IN TXT "1 OK"
-auth-4.2.1.security-status                              60 IN TXT "1 OK"
+auth-4.2.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.2.1.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.2.2.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.2.3.security-status                              60 IN TXT "1 OK"
 auth-4.3.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 auth-4.3.0-beta1.security-status                        60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.3.0-beta2.security-status                        60 IN TXT "1 OK"
+auth-4.3.0-beta2.security-status                        60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.3.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.3.0-rc2.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.3.0.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2020-05.html"
+auth-4.3.1.security-status                              60 IN TXT "1 OK"
 
 ; Auth Debian
 auth-3.4.1-2.debian.security-status                     60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2015-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
@@ -88,7 +95,7 @@ auth-3.4.1-4_deb8u4.debian.security-status              60 IN TXT "3 Upgrade now
 auth-3.4.1-4_deb8u5.debian.security-status              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2016-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
 auth-3.4.1-4_deb8u6.debian.security-status              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
 auth-3.4.1-4_deb8u7.debian.security-status              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2017-04.html"
-auth-3.4.1-4_deb8u8.debian.security-status              60 IN TXT "1 OK"
+auth-3.4.1-4_deb8u8.debian.security-status              60 IN TXT "2 Unsupported release (EOL)"
 
 auth-3.4.4-2_bpo8_1.debian.security-status              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2015-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2015-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
 auth-3.4.5-1_bpo8_1.debian.security-status              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2015-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
@@ -118,7 +125,7 @@ auth-4.0.1-6.debian.security-status                     60 IN TXT "3 Upgrade now
 auth-4.0.1-7.debian.security-status                     60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2016-02.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2016-03.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2016-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2016-05.html"
 auth-4.0.2-1.debian.security-status                     60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2017-04.html"
 auth-4.0.3-1.debian.security-status                     60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2017-04.html"
-auth-4.0.3-1_deb9u2.debian.security-status              60 IN TXT "1 OK"
+auth-4.0.3-1_deb9u2.debian.security-status              60 IN TXT "2 Unsupported release (EOL)"
 ; From here on security polling has been disabled for this distribution.
 
 ; Auth Ubuntu
@@ -191,27 +198,40 @@ recursor-4.1.5.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.1.6.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-09.html"
 recursor-4.1.7.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-09.html"
 recursor-4.1.8.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2019-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2019-02.html"
-recursor-4.1.9.security-status                          60 IN TXT "1 OK"
-recursor-4.1.10.security-status                         60 IN TXT "1 OK"
-recursor-4.1.11.security-status                         60 IN TXT "1 OK"
-recursor-4.1.12.security-status                         60 IN TXT "1 OK"
-recursor-4.1.13.security-status                         60 IN TXT "1 OK"
-recursor-4.1.14.security-status                         60 IN TXT "1 OK"
-recursor-4.1.15.security-status                         60 IN TXT "1 OK"
-recursor-4.2.0-alpha1.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.2.0-beta1.security-status                    60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.2.0-rc1.security-status                      60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.2.0-rc2.security-status                      60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.2.0.security-status                          60 IN TXT "1 OK"
-recursor-4.2.1.security-status                          60 IN TXT "1 OK"
-recursor-4.3.0-alpha1.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-alpha2.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-alpha3.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-beta1.security-status                    60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-beta2.security-status                    60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-rc1.security-status                      60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-rc2.security-status                      60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0.security-status                          60 IN TXT "1 OK"
+recursor-4.1.9.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.10.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.11.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.12.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.13.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.14.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.15.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.1.16.security-status                         60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-04.html"
+recursor-4.1.17.security-status                         60 IN TXT "1 OK"
+recursor-4.2.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.2.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.2.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.2.0-rc2.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.2.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.2.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.2.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-04.html"
+recursor-4.2.3.security-status                          60 IN TXT "1 OK"
+recursor-4.2.4.security-status                          60 IN TXT "1 OK"
+recursor-4.3.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0-alpha3.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0-beta2.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0-rc2.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.3.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-01.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-02.html https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-03.html"
+recursor-4.3.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-04.html"
+recursor-4.3.2.security-status                          60 IN TXT "1 OK"
+recursor-4.3.3.security-status                          60 IN TXT "1 OK"
+recursor-4.3.4.security-status                          60 IN TXT "1 OK"
+recursor-4.4.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.4.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release"
+recursor-4.4.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release"
+recursor-4.4.0-rc1.security-status                      60 IN TXT "1 OK"
 
 ; Recursor Debian
 recursor-3.6.2-2.debian.security-status                 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/"
@@ -339,3 +359,9 @@ dnsdist-1.4.0-rc3.security-status                          60 IN TXT "2 Unsuppor
 dnsdist-1.4.0-rc4.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 dnsdist-1.4.0-rc5.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 dnsdist-1.4.0.security-status                              60 IN TXT "1 OK"
+dnsdist-1.5.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.5.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.5.0-rc2.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.5.0-rc3.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.5.0-rc4.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.5.0.security-status                              60 IN TXT "1 OK"
diff --git a/docs/security-advisories/powerdns-advisory-2020-05.rst b/docs/security-advisories/powerdns-advisory-2020-05.rst
new file mode 100644 (file)
index 0000000..e9a9124
--- /dev/null
@@ -0,0 +1,25 @@
+PowerDNS Security Advisory 2020-05: Leaking uninitialised memory through crafted zone records
+=============================================================================================
+
+-  CVE: CVE-2020-17482
+-  Date: September 22nd, 2020
+-  Affects: PowerDNS Authoritative 4.3.0 and earlier
+-  Not affected: 4.3.1 and up, 4.2.3 and up, 4.1.14 and up
+-  Severity: Low
+-  Impact: Information leak
+-  Exploit: This problem can be triggered via crafted records by an authorized user
+-  Risk of system compromise: Low
+-  Solution: Upgrade to a fixed version
+-  Workaround: Do not take zone data from untrusted users
+
+An issue has been found in PowerDNS Authoritative Server before 4.3.1 where an authorized user with the ability to insert crafted records into a zone might be able to leak the content of uninitialized memory.
+Such a user could be a customer inserting data via a control panel, or somebody with access to the REST API.
+Crafted records cannot be inserted via AXFR.
+
+This issue has been assigned CVE-2020-17482.
+
+PowerDNS Authoritative up to and including version 4.3.0 are affected.
+Please note that at the time of writing, PowerDNS Authoritative 4.0 and below are no longer supported, as described in
+https://doc.powerdns.com/authoritative/appendices/EOL.html.
+
+We would like to thank Nathaniel Ferguson for finding and subsequently reporting this issue!
diff --git a/docs/security-advisories/powerdns-advisory-2020-06.rst b/docs/security-advisories/powerdns-advisory-2020-06.rst
new file mode 100644 (file)
index 0000000..74e4eb8
--- /dev/null
@@ -0,0 +1,28 @@
+PowerDNS Security Advisory 2020-06: Various issues in our GSS-TSIG support
+==========================================================================
+
+-  CVE: CVE-2020-24696, CVE-2020-24697, CVE-2020-24698
+-  Date: September 22nd, 2020
+-  Affects: PowerDNS Authoritative versions before 4.4.0, when compiled with --enable-experimental-gss-tsig
+-  Not affected: 4.4.0 and up, and any version compiled without GSS-TSIG support
+-  Severity: Low
+-  Impact: Crashes, Information Leaks, Possible code execution
+-  Exploit: This problem can be triggered via crafted packets
+-  Risk of system compromise: Low
+-  Solution: Do not use software built with GSS-TSIG support
+
+Various issues have been found in our GSS-TSIG support, where an unauthorized attacker could cause crashes, possibly leak uninitialised memory, and possibly execute arbitrary code.
+
+These issues have been assigned:
+
+* CVE-2020-24696: A remote, unauthenticated attacker can trigger a race condition leading to a crash, or possibly arbitrary code execution, by sending crafted queries with a GSS-TSIG signature.
+* CVE-2020-24697: A remote, unauthenticated attacker can cause a denial of service by sending crafted queries with a GSS-TSIG signature.
+* CVE-2020-24698: A remote, unauthenticated attacker might be able to cause a double-free, leading to a crash or possibly arbitrary code execution by sending crafted queries with a GSS-TSIG signature.
+
+All PowerDNS Authoritative versions are affected, but *only* if they have been compiled with ``--enable-experimental-gss-tsig``.
+We have never published packages with the feature enabled.
+
+Because of the various issues with the feature (including a complete lack of testing code around it), and no reports of production usage of GSS-TSIG, we have decided to remove the relevant code completely in PowerDNS Authoritative 4.4.0.
+Users of earlier versions that rely on the feature can keep doing so until they upgrade to 4.4.0, but need to be aware of these issues.
+
+We would like to thank Nathaniel Ferguson for finding and subsequently reporting these issues!
index 5fbdbcbabcb51153cb6fb5c9a7aa2fc99e26725e..8b5121125810aa761771755d504cfc18de1f815e 100644 (file)
@@ -35,7 +35,12 @@ Allow 8 bit DNS queries.
 -  Default: 127.0.0.0/8,::1
 
 If set, only these IP addresses or netmasks will be able to perform
-AXFR.
+AXFR without TSIG.
+
+.. warning::
+   This setting only applies to AXFR without TSIG keys. If you allow a TSIG key to perform an AXFR,
+   this setting will not be checked for that transfer, and the client will be able to perform the AXFR
+   from everywhere.
 
 .. _setting-allow-dnsupdate-from:
 
@@ -359,7 +364,7 @@ to enable DNSSEC. Must be one of:
 
 .. note::
   Actual supported algorithms depend on the crypto-libraries
-  PowerDNS was compiled against. To check the supported DNSSEC algoritms
+  PowerDNS was compiled against. To check the supported DNSSEC algorithms
   in your build of PowerDNS, run ``pdnsutil list-algorithms``.
 
 .. _setting-default-ksk-size:
@@ -482,7 +487,7 @@ to enable DNSSEC. Must be one of:
 
 .. note::
   Actual supported algorithms depend on the crypto-libraries
-  PowerDNS was compiled against. To check the supported DNSSEC algoritms
+  PowerDNS was compiled against. To check the supported DNSSEC algorithms
   in your build of PowerDNS, run ``pdnsutil list-algorithms``.
 
 .. _setting-default-zsk-size:
@@ -607,6 +612,9 @@ Enable/Disable DNS update (RFC2136) support. See :doc:`dnsupdate` for more.
 -  Boolean
 -  Default: yes
 
+.. versionchanged:: 4.4.0
+  This setting has been removed
+
 Perform AAAA additional processing. This sends AAAA records in the
 ADDITIONAL section when sending a referral.
 
@@ -665,7 +673,7 @@ Entropy source file to use.
 
 .. versionadded:: 4.1.0
 
-If this is enabled, ALIAS records are expanded (synthesised to their
+If this is enabled, ALIAS records are expanded (synthesized to their
 A/AAAA).
 
 If this is disabled (the default), ALIAS records will not be expanded and
@@ -770,19 +778,27 @@ available in non-static distributions.
 ``local-address``
 -----------------
 .. versionchanged:: 4.3.0
-  now also takes your IPv6 addresses
+  now also accepts IPv6 addresses
 
 .. versionchanged:: 4.3.0
-  Before 4.3.0, this setting only supported IPv4.
+  Before 4.3.0, this setting only supported IPv4 addresses.
 
--  IPv4 Addresses, separated by commas or whitespace
+-  IPv4/IPv6 Addresses, with optional port numbers, separated by commas or whitespace
 -  Default: ``0.0.0.0, ::``
 
-Local IP addresses to which we bind. It is highly advised to bind to
-specific interfaces and not use the default 'bind to any'. This causes
-big problems if you have multiple IP addresses. Unix does not provide a
-way of figuring out what IP address a packet was sent to when binding to
-any.
+Local IP addresses to which we bind. Each address specified can
+include a port number; if no port is included then the
+:ref:`setting-local-port` port will be used for that address. If a
+port number is specified, it must be separated from the address with a
+':'; for an IPv6 address the address must be enclosed in square
+brackets.
+
+Examples::
+
+  local-address=127.0.0.1 ::1
+  local-address=0.0.0.0:5353
+  local-address=[::]:8053
+  local-address=127.0.0.1:53, [::1]:5353
 
 .. _setting-local-address-nonexist-fail:
 
@@ -834,7 +850,8 @@ addresses do not exist on this server.
 -  Integer
 -  Default: 53
 
-The port on which we listen. Only one port possible.
+Local port to bind to.
+If an address in :ref:`setting-local-address` does not have an explicit port, this port is used.
 
 .. _setting-log-dns-details:
 
@@ -1224,7 +1241,7 @@ default has been "yes" since 2005.
 -  Boolean
 -  Default: no
 
-If this is enabled, ALIAS records are expanded (synthesised to their
+If this is enabled, ALIAS records are expanded (synthesized to their
 A/AAAA) during outgoing AXFR. This means slaves will not automatically
 follow changes in those A/AAAA records unless you AXFR regularly!
 
@@ -1270,21 +1287,33 @@ Seconds to store queries with an answer in the Query Cache. See :ref:`query-cach
 
 ``query-local-address``
 -----------------------
+.. versionchanged:: 4.4.0
+  Accepts both IPv4 and IPv6 addresses. Also accept more than one address per
+  address family.
 
--  IPv4 Address
--  Default: 0.0.0.0
+-  IP addresses, separated by spaces or commas
+-  Default: `0.0.0.0 ::`
 
-The IP address to use as a source address for sending queries. Useful if
+The IP addresses to use as a source address for sending queries. Useful if
 you have multiple IPs and PowerDNS is not bound to the IP address your
 operating system uses by default for outgoing packets.
 
+PowerDNS will pick the correct address family based on the remote's address (v4
+for outgoing v4, v6 for outgoing v6). However, addresses are selected at random
+without taking into account ip subnet reachability. It is highly recommended to
+use the defaults in that case (the kernel will pick the right source address for
+the network).
+
 .. _setting-query-local-address6:
 
 ``query-local-address6``
 ------------------------
+.. deprecated:: 4.4.0
+  Use :ref:`setting-query-local-address`. The default has been changed
+  from '::' to unset.
 
 -  IPv6 Address
--  Default: '::'
+-  Default: unset
 
 Source IP address for sending IPv6 queries.
 
@@ -1393,7 +1422,7 @@ it is disabled by default.
 - String
 - Default: auto
 
-Specify which random number generator to use. Permissible choises are:
+Specify which random number generator to use. Permissible choices are:
 
 - auto - choose automatically
 - sodium - Use libsodium ``randombytes_uniform``
@@ -1404,7 +1433,7 @@ Specify which random number generator to use. Permissible choises are:
 - kiss - Use simple settable deterministic RNG. **FOR TESTING PURPOSES ONLY!**
 
 .. note::
-  Not all choises are available on all systems.
+  Not all choices are available on all systems.
 
 .. _setting-security-poll-suffix:
 
index 0716c9487e0ffee0bd5db71b97e87d28d4b7a881..bd0a12fb6c2fdd37b353be6607c5151d19663b30 100644 (file)
@@ -33,6 +33,10 @@ with the key name in the content field. For example::
 
     $ dig -t axfr powerdnssec.org @127.0.0.1 -y 'test:kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys='
 
+.. warning::
+  Any host with the correct TSIG key will be able to perform the AXFR, even
+  if the host is not within the defined ``allow-axfr-ips`` ranges.
+
 Another way of importing and activating TSIG keys into the database is using
 :doc:`pdnsutil <manpages/pdnsutil.1>`:
 
@@ -115,6 +119,8 @@ the master, not just those about AXFR requests.
 
 GSS-TSIG support
 ----------------
+  .. versionchanged:: 4.4.0
+    GSS support was removed
 
 GSS-TSIG allows authentication and authorization of DNS updates or AXFR
 using Kerberos with TSIG signatures.
index 5de844bc50be8182564889d6d9ca96f460dec12a..a6c5a53de998864c8fa6bc7d8e7e701914ddd6f2 100644 (file)
@@ -8,6 +8,24 @@ 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.3.x to 4.4.0
+--------------
+
+``IPSECKEY`` change on secondaries
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The in-database format of the ``IPSECKEY`` has changed from 'generic' format to its specialized format.
+It is recommended to re-transfer, using ``pdns_control retrieve ZONE``, all zones that have ``IPSECKEY`` or ``TYPE45`` records.
+
+4.3.0 to 4.3.1
+--------------
+
+On RHEL/CentOS 8, the gmysql backend now uses ``mariadb-connector-c`` instead of ``mysql-libs``.
+This change was made because the default MySQL implementation for RHEL8 is MariaDB, and MariaDB and MySQL cannot be installed in parallel due to conflicting RPM packages.
+The mariadb client lib will connect to your existing MySQL servers without trouble.
+
+Unknown record encoding (`RFC 3597 <https://tools.ietf.org/html/rfc3597>`__) has become more strict as a result of the fixes for :doc:`PowerDNS Security Advisory 2020-05 <../security-advisories/powerdns-advisory-2020-05>`. Please use ``pdnsutil check-all-zones`` to review your zone contents.
+
 4.2.x to 4.3.0
 --------------
 
@@ -61,12 +79,40 @@ Removed settings
 Schema changes
 ^^^^^^^^^^^^^^
 - The new 'unpublished DNSSEC keys' feature comes with a mandatory schema change for all database backends (including BIND with a DNSSEC database). Please find files named "4.2.0_to_4.3.0_schema.X.sql" for your database backend in our Git repo, tarball, or distro-specific documentation path. For the LMDB backend, please review :ref:`setting-lmdb-schema-version`.
+- If you are upgrading from beta2 or rc2, AND ONLY THEN, please read `pull request #8975 <https://github.com/PowerDNS/pdns/pull/8975>`__ very carefully.
 
 Implicit 5->7 algorithm upgrades
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Since version 3.0 (the first version of the PowerDNS Authoritative Server that supported DNSSEC signing), we have automatically, silently, upgraded algorithm 5 (RSASHA1) keys to algorithm 7 (RSASHA1-NSEC3-SHA1) when the user enabled NSEC3. This has been a source of confusion, and because of that, we introduced warnings for users of this feature in 4.0 and 4.1. To see if you are affected, run ``pdnsutil check-all-zones`` from version 4.0 or up. In this release, the automatic upgrade is gone, and affected zones will break if no action is taken.
 
+.. _ixfr-in-corruption-4.3.0:
+
+IXFR-in corruption
+^^^^^^^^^^^^^^^^^^
+
+A bug in PowerDNS versions before 4.2.2/4.3.0 would cause wrong deletion or addition of records if IXFR deltas came in very quickly (within the query cache timeout, which defaults to 20/60 seconds).
+If you have zones which use inbound IXFR (in other words, the ``IXFR`` metadata item for that zone is set to ``1``), we strongly suggest triggering a completely fresh transfer.
+You could accomplish that by deleting all records in the zone with an SQL query and waiting for a fresh transfer, or (1) disabling IXFR (2) forcing a fresh transfer using ``pdns_control retrieve example.com`` (3) enabling IXFR again.
+
+4.2.X to 4.2.3
+--------------
+
+Unknown record encoding (`RFC 3597 <https://tools.ietf.org/html/rfc3597>`__) has become more strict as a result of the fixes for :doc:`PowerDNS Security Advisory 2020-05 <../security-advisories/powerdns-advisory-2020-05>`. Please use ``pdnsutil check-all-zones`` to review your zone contents.
+
+4.X.X to 4.2.2
+--------------
+
+.. _ixfr-in-corruption-4.2.2:
+
+IXFR-in corruption
+^^^^^^^^^^^^^^^^^^
+
+A bug in PowerDNS versions before 4.2.2/4.3.0 would cause wrong deletion or addition of records if IXFR deltas came in very quickly (within the query cache timeout, which defaults to 20/60 seconds).
+If you have zones which use inbound IXFR (in other words, the ``IXFR`` metadata item for that zone is set to ``1``), we strongly suggest triggering a completely fresh transfer.
+You could accomplish that by deleting all records in the zone with an SQL query and waiting for a fresh transfer, or (1) disabling IXFR (2) forcing a fresh transfer using ``pdns_control retrieve example.com`` (3) enabling IXFR again.
+
+
 4.1.X to 4.2.0
 --------------
 
@@ -75,6 +121,11 @@ Since version 3.0 (the first version of the PowerDNS Authoritative Server that s
 - Autoserial support has been removed. The ``change_date`` column has been removed from the ``records`` table in all gsql backends, but leaving it in is harmless.
 - The :doc:`Generic PostgreSQL backend <backends/generic-postgresql>` schema has changed: the ``notified_serial`` column type in the ``domains`` table has been changed from ``INT DEFAULT NULL`` to ``BIGINT DEFAULT NULL``: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
 
+4.1.X to 4.1.14
+---------------
+
+Unknown record encoding (`RFC 3597 <https://tools.ietf.org/html/rfc3597>`__) has become more strict as a result of the fixes for :doc:`PowerDNS Security Advisory 2020-05 <../security-advisories/powerdns-advisory-2020-05>`. Please use ``pdnsutil check-all-zones`` to review your zone contents.
+
 4.1.0 to 4.1.1
 --------------
 
index f4874261f62884eb5f60d6a09e9eb70a6d0206be..ccb090127aee304a3c2d0a3ac136cb35b4bd7769 100644 (file)
@@ -6,7 +6,10 @@
 #include <string.h>
 #include <map>
 
-using namespace std;
+using std::string;
+using std::runtime_error;
+using std::tuple;
+using std::weak_ptr;
 
 static string MDBError(int rc)
 {
@@ -335,7 +338,7 @@ MDBRWTransaction MDBRWTransactionImpl::getRWTransaction()
 
 MDBROTransaction MDBRWTransactionImpl::getROTransaction()
 {
-  return std::move(getRWTransaction());
+  return getRWTransaction();
 }
 
 MDBROTransaction MDBEnv::getROTransaction()
index e5fe14a1dea63098ee9cfd35cbaf68d5e4099d4f..2a32c67a4e9edf8ddfe08593b0456635fe918e79 100644 (file)
 #include <vector>
 #include <algorithm>
 
-// apple compiler somehow has string_view even in c++11!
-#if __cplusplus < 201703L && !defined(__APPLE__)
+#ifdef __cpp_lib_string_view
+using std::string_view;
+#else
 #include <boost/version.hpp>
 #if BOOST_VERSION >= 106100
 #include <boost/utility/string_view.hpp>
 using boost::string_view;
-#else
+#elif BOOST_VERSION >= 105300
 #include <boost/utility/string_ref.hpp>
 using string_view = boost::string_ref;
+#else
+using string_view = std::string;
 #endif
-#else // C++17
-using std::string_view;
 #endif
 
 
index 5982757baf70d007276a9744ba3868eea56feee8..5017cd7b847b556cef0bdfab62a59f877b5be52d 100644 (file)
@@ -1459,7 +1459,7 @@ private:
             const auto traceBack = readTopAndPop<std::string>(state, std::move(traceBackRef)); // stack top: error
             PushedObject errorCode{state, 1};
 
-            // an error occured during execution, either an error message or a std::exception_ptr was pushed on the stack
+            // an error occurred during execution, either an error message or a std::exception_ptr was pushed on the stack
             if (pcallReturnValue == LUA_ERRMEM) {
                 throw std::bad_alloc{};
 
index ca2154f6a4ab6e7fef28ac5a6fae68d4e555bbc3..fd317b932e5107350865952e1c15a08c44710991 100644 (file)
@@ -3,6 +3,7 @@
 namespace YaHTTP {
 
   template class AsyncLoader<Request>;
+  template class AsyncLoader<Response>;
 
   bool isspace(char c) {
     return std::isspace(c) != 0;
index c42c5c12f1539e898867ca35b3b244ea8ae72e2b..055c44883008d0b5e78b7a573a31c4c5417430db 100644 (file)
@@ -12,6 +12,7 @@ The current targets cover:
 - the auth, dnsdist and rec packet caches (fuzz_target_packetcache and
   fuzz_target_dnsdistcache) ;
 - MOADNSParser (fuzz_target_moadnsparser) ;
+- the Proxy Protocol parser (fuzz_target_proxyprotocol) ;
 - ZoneParserTNG (fuzz_target_zoneparsertng).
 
 By default the targets are linked against a standalone target,
@@ -38,7 +39,9 @@ Corpus
 This directory contains a few files used for continuous fuzzing
 of the PowerDNS products.
 
-The 'corpus' directory contains two sub-directories:
+The 'corpus' directory contains three sub-directories:
+- proxy-protocol-raw-packets/ contains DNS queries prefixed with a Proxy
+  Protocol v2 header, used by fuzz_target_proxyprotocol ;
 - raw-dns-packets/ contains DNS queries and responses as captured on
   the wire. These are used by the fuzz_target_dnsdistcache,
   fuzz_target_moadnsparser and fuzz_target_packetcache targets ;
diff --git a/fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-local-header b/fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-local-header
new file mode 100644 (file)
index 0000000..f2f8264
Binary files /dev/null and b/fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-local-header differ
diff --git a/fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-v4-with-tlvs b/fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-v4-with-tlvs
new file mode 100644 (file)
index 0000000..250bed2
Binary files /dev/null and b/fuzzing/corpus/proxy-protocol-raw-packets/proxy-protocol-v4-with-tlvs differ
index 1be83c676f1e7c6f89e0e54ed1193e51fc644bde..cd6dc49f469568aa48d871920a2c2d56b6cba3b0 100644 (file)
@@ -1,4 +1,5 @@
 AC_DEFUN([PDNS_CHECK_DNSTAP], [
+  AC_REQUIRE([PDNS_WITH_PROTOBUF])
   AC_MSG_CHECKING([whether we will have dnstap])
   AC_ARG_ENABLE([dnstap],
     AS_HELP_STRING([--enable-dnstap],[enable dnstap support @<:@default=$1@:>@]),
@@ -27,5 +28,8 @@ AC_DEFUN([PDNS_CHECK_DNSTAP], [
     AS_IF([test x"$FSTRM_LIBS" = "x"], [
       AC_MSG_ERROR([dnstap requested but libfstrm was not found])
     ])
+    AS_IF([test "x$PROTOBUF_LIBS" = "x" -o x"$PROTOC" = "x"], [
+      AC_MSG_ERROR([dnstap requested but protobuf was not found])
+    ])
   ])
 ])
diff --git a/m4/pdns_enable_gss_tsig.m4 b/m4/pdns_enable_gss_tsig.m4
deleted file mode 100644 (file)
index 9acd756..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-AC_DEFUN([PDNS_ENABLE_GSS_TSIG],[
-  AC_MSG_CHECKING([whether to enable experimental GSS-TSIG support])
-  AC_ARG_ENABLE([experimental_gss_tsig],
-    AS_HELP_STRING([--enable-experimental-gss-tsig],
-      [enable experimental GSS-TSIG support @<:@default=no@:>@]
-    ),
-    [enable_experimental_gss_tsig=$enableval],
-    [enable_experimental_gss_tsig=no]
-  )
-
-  AC_MSG_RESULT([$enable_experimental_gss_tsig])
-
-  AM_CONDITIONAL([GSS_TSIG],[test "x$enable_experimental_gss_tsig" != "xno"])
-  AC_SUBST(GSS_TSIG)
-  AS_IF([test "x$enable_experimental_gss_tsig" != "xno"],
-   [PKG_CHECK_MODULES([GSS], [krb5 krb5-gssapi gss],
-      [
-        AC_DEFINE([ENABLE_GSS_TSIG], [1], [Define to 1 if you want to enable GSS-TSIG support])
-        GSS_TSIG=yes
-      ],
-      [AC_MSG_ERROR([Required libraries for GSS-TSIG not found])]
-   )],
-    [GSS_TSIG=no])
-])
index 77919fcd234a13b6c65fa826e1c66fe266d8156f..f0b2a06df425ed116dd66fee05a8ed2ee0da6736 100644 (file)
@@ -174,16 +174,33 @@ AC_DEFUN([AX_CHECK_SYSTEMD_FEATURES], [
                  # @aio, @sync, @chown, @setuid, @memlock, @signal and @timer in 235
                  systemd_system_call_filter=y
               fi
+              if test $_systemd_version -ge 239; then
+                 systemd_private_mounts=y
+              fi
+              if test $_systemd_version -ge 242; then
+                 systemd_protect_hostname=y
+                 systemd_restrict_suidsgid=y
+              fi
+              if test $_systemd_version -ge 244; then
+                 systemd_protect_kernel_logs=y
+              fi
+              if test $_systemd_version -ge 245; then
+                 systemd_protect_clock=y
+              fi
           ])
         ])
         AM_CONDITIONAL([HAVE_SYSTEMD_DYNAMIC_USER], [ test x"$systemd_dynamic_user" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_LOCK_PERSONALITY], [ test x"$systemd_lock_personality" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_MEMORY_DENY_WRITE_EXECUTE], [ test x"$systemd_memory_deny_write_execute" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_DEVICES], [ test x"$systemd_private_devices" = "xy" ])
+        AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_MOUNTS], [ test x"$systemd_private_mounts" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_TMP], [ test x"$systemd_private_tmp" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PRIVATE_USERS], [ test x"$systemd_private_users" = "xy" ])
+        AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_CLOCK], [ test x"$systemd_protect_clock" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_CONTROL_GROUPS], [ test x"$systemd_protect_control_groups" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_HOME], [ test x"$systemd_protect_home" = "xy" ])
+        AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_HOSTNAME], [ test x"$systemd_protect_hostname" = "xy" ])
+        AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_KERNEL_LOGS], [ test x"$systemd_protect_kernel_logs" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_KERNEL_MODULES], [ test x"$systemd_protect_kernel_modules" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_KERNEL_TUNABLES], [ test x"$systemd_protect_kernel_tunables" = "xy" ])
         AM_CONDITIONAL([HAVE_SYSTEMD_PROTECT_SYSTEM], [ test x"$systemd_protect_system" = "xy" ])
index aaf88be6840fb9be25d9212b62f55ab682e22481..812a8537a3c4d606910887a62345db399e8bc1a2 100644 (file)
@@ -9,6 +9,10 @@ BUILT_SOURCES = \
        ../../pdns/bindlexer.l \
        ../../pdns/bindparser.yy
 
+dist_doc_DATA = \
+       ../../pdns/bind-dnssec.4.2.0_to_4.3.0_schema.sqlite3.sql \
+       ../../pdns/bind-dnssec.schema.sqlite3.sql
+
 EXTRA_DIST = OBJECTFILES OBJECTLIBS
 
 libbindbackend_la_SOURCES = \
index 9e6c33c38dc867f9a9d9883fd91719f864c4de0a..e7841a570d04c3de7ff4041ba8cf263af7e3a8fa 100644 (file)
@@ -77,9 +77,9 @@ Bind2Backend::state_t Bind2Backend::s_state;
 int Bind2Backend::s_first=1;
 bool Bind2Backend::s_ignore_broken_records=false;
 
-pthread_rwlock_t Bind2Backend::s_state_lock=PTHREAD_RWLOCK_INITIALIZER;
-pthread_mutex_t Bind2Backend::s_supermaster_config_lock=PTHREAD_MUTEX_INITIALIZER; // protects writes to config file
-pthread_mutex_t Bind2Backend::s_startup_lock=PTHREAD_MUTEX_INITIALIZER;
+ReadWriteLock Bind2Backend::s_state_lock;
+std::mutex Bind2Backend::s_supermaster_config_lock; // protects writes to config file
+std::mutex Bind2Backend::s_startup_lock;
 string Bind2Backend::s_binddirectory;  
 
 template <typename T>
@@ -290,7 +290,7 @@ bool Bind2Backend::feedRecord(const DNSResourceRecord &rr, const DNSName &ordern
     throw DBException("out-of-zone data '"+rr.qname.toLogString()+"' during AXFR of zone '"+bbd.d_name.toLogString()+"'");
   }
 
-  shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
+  shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
   string content = drc->getZoneRepresentation();
 
   // SOA needs stripping too! XXX FIXME - also, this should not be here I think
@@ -463,7 +463,7 @@ void Bind2Backend::alsoNotifies(const DNSName& domain, set<string> *ips)
       (*ips).insert(str);
     }
   }
-  ReadLock rl(&s_state_lock);  
+  ReadLock rl(&s_state_lock);
   for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
     if(i->d_name == domain) {
       for(set<string>::iterator it = i->d_also_notify.begin(); it != i->d_also_notify.end(); it++) {
@@ -636,13 +636,13 @@ string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>&parts, Util
   ostringstream ret;
 
   if (parts.size() > 1) {
-    for (const auto& part : parts) {
+    for (vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
       BB2DomainInfo bbd;
-      if (safeGetBBDomainInfo(DNSName(part), &bbd)) {
+      if (safeGetBBDomainInfo(DNSName(*i), &bbd)) {
         printDomainExtendedStatus(ret, bbd);
       }
       else {
-        ret << part << " no such domain" << std::endl;
+        ret << *i << " no such domain" << std::endl;
       }
     }
   }
@@ -730,7 +730,7 @@ Bind2Backend::Bind2Backend(const string &suffix, bool loadZones)
   if (!loadZones && d_hybrid)
     return;
 
-  Lock l(&s_startup_lock);
+  std::lock_guard<std::mutex> l(s_startup_lock);
   
   setupDNSSEC();
   if(!s_first) {
@@ -1150,7 +1150,7 @@ void Bind2Backend::lookup(const QType &qtype, const DNSName &qname, int zoneId,
 
   if(!found) {
     if(mustlog)
-      g_log<<Logger::Warning<<"Found no authoritative zone for '"<<qname<<"' and/or id "<<bbd.d_id<<endl;
+      g_log<<Logger::Warning<<"Found no authoritative zone for '"<<qname<<"' and/or id "<<zoneId<<endl;
     d_handle.d_list=false;
     return;
   }
@@ -1375,7 +1375,7 @@ bool Bind2Backend::createSlaveDomain(const string &ip, const DNSName& domain, co
     << "' from supermaster " << ip << endl;
 
   {
-    Lock l2(&s_supermaster_config_lock);
+    std::lock_guard<std::mutex> l2(s_supermaster_config_lock);
         
     ofstream c_of(getArg("supermaster-config").c_str(),  std::ios::app);
     if (!c_of) {
@@ -1443,7 +1443,7 @@ class Bind2Factory : public BackendFactory
    public:
       Bind2Factory() : BackendFactory("bind") {}
 
-      void declareArguments(const string &suffix="")
+      void declareArguments(const string &suffix="") override
       {
          declare(suffix,"ignore-broken-records","Ignore records that are out-of-bound for the zone.","no");
          declare(suffix,"config","Location of named.conf","");
@@ -1456,13 +1456,13 @@ class Bind2Factory : public BackendFactory
          declare(suffix,"hybrid","Store DNSSEC metadata in other backend","no");
       }
 
-      DNSBackend *make(const string &suffix="")
+      DNSBackend *make(const string &suffix="") override
       {
          assertEmptySuffix(suffix);
          return new Bind2Backend(suffix);
       }
       
-      DNSBackend *makeMetadataOnly(const string &suffix="")
+      DNSBackend *makeMetadataOnly(const string &suffix="") override
       {
         assertEmptySuffix(suffix);
         return new Bind2Backend(suffix, false);
index 1a33194326ef68b936f758e9f6822148ae31c138..aa38223bd0976ea739d047d813bb1ba40c7c1c5d 100644 (file)
@@ -187,7 +187,7 @@ public:
   void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override;
 
   static DNSBackend *maker();
-  static pthread_mutex_t s_startup_lock;
+  static std::mutex s_startup_lock;
 
   void setFresh(uint32_t domain_id) override;
   void setNotified(uint32_t id, uint32_t serial) override;
@@ -221,7 +221,7 @@ public:
                                               ordered_unique<tag<NameTag>, member<BB2DomainInfo, DNSName, &BB2DomainInfo::d_name> >
                                               > > state_t;
   static state_t s_state;
-  static pthread_rwlock_t s_state_lock;
+  static ReadWriteLock s_state_lock;
 
   void parseZoneFile(BB2DomainInfo *bbd);
   void rediscover(string *status=nullptr) override;
@@ -229,7 +229,7 @@ public:
 
   // for supermaster support
   bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db) override;
-  static pthread_mutex_t s_supermaster_config_lock;
+  static std::mutex s_supermaster_config_lock;
   bool createSlaveDomain(const string &ip, const DNSName &domain, const string &nameserver, const string &account) override;
 
 private:
index 2238ababdfd3a3ef776171e926acfbdf4e3b5ae1..83a86bc06b369036afb261798af8d4b3c643d339 100644 (file)
@@ -32,7 +32,7 @@
 #include <fstream>
 #include <yaml-cpp/yaml.h>
 
-pthread_rwlock_t GeoIPBackend::s_state_lock=PTHREAD_RWLOCK_INITIALIZER;
+ReadWriteLock GeoIPBackend::s_state_lock;
 
 struct GeoIPDNSResourceRecord: DNSResourceRecord {
   int weight;
@@ -263,7 +263,7 @@ void GeoIPBackend::initialize() {
     for(auto &item: dom.records) {
       map<uint16_t, float> weights;
       map<uint16_t, float> sums;
-      map<uint16_t, GeoIPDNSResourceRecord> lasts;
+      map<uint16_t, GeoIPDNSResourceRecord*> lasts;
       bool has_weight=false;
       // first we look for used weight
       for(const auto &rr: item.second) {
@@ -277,13 +277,13 @@ void GeoIPBackend::initialize() {
           rr.weight=static_cast<int>((static_cast<float>(rr.weight) / weights[rr_type])*1000.0);
           sums[rr_type] += rr.weight;
           rr.has_weight = has_weight;
-          lasts[rr_type] = rr;
+          lasts[rr_type] = &rr;
         }
         // remove rounding gap
         for(auto &x: lasts) {
           float sum = sums[x.first];
           if (sum < 1000)
-            x.second.weight += (1000-sum);
+            x.second->weight += (1000-sum);
         }
       }
     }
@@ -314,11 +314,13 @@ GeoIPBackend::~GeoIPBackend() {
 bool GeoIPBackend::lookup_static(const GeoIPDomain &dom, const DNSName &search, const QType &qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask &gl) {
   const auto& i = dom.records.find(search);
   map<uint16_t,int> cumul_probabilities;
+  map<uint16_t,bool> weighted_match;
   int probability_rnd = 1+(dns_random(1000)); // setting probability=0 means it never is used
 
   if (i != dom.records.end()) { // return static value
     for(const auto& rr : i->second) {
-      if (qtype != QType::ANY && rr.qtype != qtype) continue;
+      if ((qtype != QType::ANY && rr.qtype != qtype) || weighted_match[rr.qtype.getCode()])
+        continue;
 
       if (rr.has_weight) {
         gl.netmask = (addr.isIPv6()?128:32);
@@ -332,6 +334,10 @@ bool GeoIPBackend::lookup_static(const GeoIPDomain &dom, const DNSName &search,
       d_result.push_back(rr);
       d_result.back().content = content;
       d_result.back().qname = qdomain;
+      // If we are weighted we only return one resource and we found a matching resource,
+      // so no need to check the other ones.
+      if (rr.has_weight)
+        weighted_match[rr.qtype.getCode()] = true;
     }
     // ensure we get most strict netmask
     for(DNSResourceRecord& rr: d_result) {
@@ -519,7 +525,7 @@ string getGeoForLua(const std::string& ip, int qaint)
   return "";
 }
 
-bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
+static bool queryGeoLocation(const Netmask& addr, GeoIPNetmask& gl, double& lat, double& lon,
                       boost::optional<int>& alt, boost::optional<int>& prec)
 {
   for(auto const& gi: s_geoip_files) {
@@ -929,13 +935,13 @@ class GeoIPFactory : public BackendFactory{
 public:
   GeoIPFactory() : BackendFactory("geoip") {}
 
-  void declareArguments(const string &suffix = "") {
+  void declareArguments(const string &suffix = "") override {
     declare(suffix, "zones-file", "YAML file to load zone(s) configuration", "");
     declare(suffix, "database-files", "File(s) to load geoip data from ([driver:]path[;opt=value]", "");
     declare(suffix, "dnssec-keydir", "Directory to hold dnssec keys (also turns DNSSEC on)", "");
   }
 
-  DNSBackend *make(const string &suffix) {
+  DNSBackend *make(const string &suffix) override {
     return new GeoIPBackend(suffix);
   }
 };
index 1b885117bcd9a4cfae29b588cc3079229772beaa..a45bbda4e54ae0972b105cbbcbbca7fad200940a 100644 (file)
@@ -67,7 +67,7 @@ public:
   bool unpublishDomainKey(const DNSName& name, unsigned int id) override;
 
 private:
-  static pthread_rwlock_t s_state_lock;
+  static ReadWriteLock s_state_lock;
 
   void initialize();
   string format2str(string format, const Netmask &addr, GeoIPNetmask& gl);
index c1f6cadae02b4c02a0d47577914a6f5bb4464ddd..477760f4cde623c737c414b281c1cafa3a431396 100644 (file)
@@ -1,2 +1 @@
-ALTER TABLE cryptokeys ADD COLUMN published BOOL DEFAULT 1;
-
+ALTER TABLE cryptokeys ADD published BOOL NULL DEFAULT 1 AFTER active;
index ef80c9d88a55ec7c40da17db4ee301fcc61f64ba..7036788bdc7ddcc5dd49ccf02ba10225494f8b3c 100644 (file)
@@ -62,6 +62,7 @@ void gMySQLBackend::reconnect()
                    getArgAsNum("timeout"),
                    mustDo("thread-cleanup"),
                    mustDo("ssl")));
+  allocateStatements();
 }
 
 class gMySQLFactory : public BackendFactory
@@ -69,7 +70,7 @@ class gMySQLFactory : public BackendFactory
 public:
   gMySQLFactory(const string &mode) : BackendFactory(mode),d_mode(mode) {}
 
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"dbname","Database name to connect to","powerdns");
     declare(suffix,"user","Database backend user to connect as","powerdns");
@@ -103,6 +104,7 @@ public:
     declare(suffix,"info-all-slaves-query","","select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix,"supermaster-query","", "select account from supermasters where ip=? and nameserver=?");
     declare(suffix,"supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+    declare(suffix,"supermaster-add","", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
 
     declare(suffix,"insert-zone-query","", "insert into domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,NULL,NULL)");
 
@@ -159,7 +161,7 @@ public:
     declare(suffix, "search-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE name LIKE ? OR comment LIKE ? LIMIT ?");
   }
 
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new gMySQLBackend(d_mode,suffix);
   }
index 74087425e262d9462638628033df0ff56c18e238..d5e81995293fdf397820198ba317488377c84b00 100644 (file)
@@ -65,7 +65,7 @@ private:
 static thread_local MySQLThreadCloser threadcloser;
 
 bool SMySQL::s_dolog;
-pthread_mutex_t SMySQL::s_myinitlock = PTHREAD_MUTEX_INITIALIZER;
+std::mutex SMySQL::s_myinitlock;
 
 class SMySQLStatement: public SSqlStatement
 {
@@ -181,8 +181,6 @@ public:
   }
 
   SSqlStatement* execute() {
-    int err;
-
     prepareStatement();
 
     if (!d_stmt) return this;
@@ -192,20 +190,20 @@ public:
       d_dtime.set();
     }
 
-    if ((err = mysql_stmt_bind_param(d_stmt, d_req_bind))) {
+    if (mysql_stmt_bind_param(d_stmt, d_req_bind) != 0) {
       string error(mysql_stmt_error(d_stmt));
       releaseStatement();
       throw SSqlException("Could not bind mysql statement: " + d_query + string(": ") + error);
     }
 
-    if ((err = mysql_stmt_execute(d_stmt))) {
+    if (mysql_stmt_execute(d_stmt) != 0) {
       string error(mysql_stmt_error(d_stmt));
       releaseStatement();
       throw SSqlException("Could not execute mysql statement: " + d_query + string(": ") + error);
     }
 
     // MySQL documentation says you can call this safely for all queries
-    if ((err = mysql_stmt_store_result(d_stmt))) {
+    if (mysql_stmt_store_result(d_stmt) != 0) {
       string error(mysql_stmt_error(d_stmt));
       releaseStatement();
       throw SSqlException("Could not store mysql statement: " + d_query + string(": ") + error);
@@ -241,7 +239,7 @@ public:
          stmt->bind_result_done to false, causing the second to reset the existing binding),
          and we can't bind it right after the call to mysql_stmt_store_result() if it returned
          no rows, because then the statement 'contains no metadata' */
-      if (d_res_bind != nullptr && (err = mysql_stmt_bind_result(d_stmt, d_res_bind))) {
+      if (d_res_bind != nullptr && mysql_stmt_bind_result(d_stmt, d_res_bind) != 0) {
         string error(mysql_stmt_error(d_stmt));
         releaseStatement();
         throw SSqlException("Could not bind parameters to mysql statement: " + d_query + string(": ") + error);
@@ -295,7 +293,7 @@ public:
     if (d_residx >= d_resnum) {
       mysql_stmt_free_result(d_stmt);
       while(!mysql_stmt_next_result(d_stmt)) {
-        if ((err = mysql_stmt_store_result(d_stmt))) {
+        if (mysql_stmt_store_result(d_stmt) != 0) {
           string error(mysql_stmt_error(d_stmt));
           releaseStatement();
           throw SSqlException("Could not store mysql statement while processing additional sets: " + d_query + string(": ") + error);
@@ -303,8 +301,11 @@ public:
         d_resnum = mysql_stmt_num_rows(d_stmt);
         // XXX: For some reason mysql_stmt_result_metadata returns NULL here, so we cannot
         // ensure row field count matches first result set.
-        if (d_resnum > 0) { // ignore empty result set
-          if (d_res_bind != nullptr && (err = mysql_stmt_bind_result(d_stmt, d_res_bind))) {
+        // We need to check the field count as stored procedure return the final values of OUT and INOUT parameters
+        // as an extra single-row result set following any result sets produced by the procedure itself.
+        // mysql_stmt_field_count() will return 0 for those.
+        if (mysql_stmt_field_count(d_stmt) > 0 && d_resnum > 0) { // ignore empty result set
+          if (d_res_bind != nullptr && mysql_stmt_bind_result(d_stmt, d_res_bind) != 0) {
             string error(mysql_stmt_error(d_stmt));
             releaseStatement();
             throw SSqlException("Could not bind parameters to mysql statement: " + d_query + string(": ") + error);
@@ -367,8 +368,6 @@ public:
 private:
 
   void prepareStatement() {
-    int err;
-
     if (d_prepared) return;
     if (d_query.empty()) {
       d_prepared = true;
@@ -378,7 +377,7 @@ private:
     if ((d_stmt = mysql_stmt_init(d_db))==NULL)
       throw SSqlException("Could not initialize mysql statement, out of memory: " + d_query);
 
-    if ((err = mysql_stmt_prepare(d_stmt, d_query.c_str(), d_query.size()))) {
+    if (mysql_stmt_prepare(d_stmt, d_query.c_str(), d_query.size()) != 0) {
       string error(mysql_stmt_error(d_stmt));
       releaseStatement();
       throw SSqlException("Could not prepare statement: " + d_query + string(": ") + error);
@@ -444,7 +443,7 @@ void SMySQL::connect()
 {
   int retry=1;
 
-  Lock l(&s_myinitlock);
+  std::lock_guard<std::mutex> l(s_myinitlock);
   if (d_threadCleanup) {
     threadcloser.enable();
   }
@@ -515,7 +514,7 @@ SMySQL::~SMySQL()
 
 SSqlException SMySQL::sPerrorException(const string &reason)
 {
-  return SSqlException(reason+string(": ")+mysql_error(&d_db));
+  return SSqlException(reason+string(": ERROR ")+std::to_string(mysql_errno(&d_db))+" ("+string(mysql_sqlstate(&d_db))+"): "+mysql_error(&d_db));
 }
 
 std::unique_ptr<SSqlStatement> SMySQL::prepare(const string& query, int nparams)
index 74c5a4bc302f2c0317a2f15f1ec807dd7f830d13..50780e9c51a1957a68fe00acfda103040f5f44d5 100644 (file)
@@ -20,6 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#include <mutex>
+
 #include <mysql.h>
 #include "pdns/backends/gsql/ssql.hh"
 #include "pdns/utility.hh"
@@ -48,7 +50,7 @@ private:
   void connect();
 
   static bool s_dolog;
-  static pthread_mutex_t s_myinitlock;
+  static std::mutex s_myinitlock;
 
   MYSQL d_db;
   std::string d_database;
index 02139c24c4b0cb7fc959b2b828d18cb37c777b39..6ef452702eb9356ad335f3b93da2f180538335a9 100644 (file)
@@ -47,6 +47,8 @@ gODBCBackend::gODBCBackend (const std::string & mode, const std::string & suffix
     throw PDNSException( "Unable to launch " + mode + " connection: " + e.txtReason());
   }
 
+  allocateStatements();
+
   g_log << Logger::Warning << mode << " Connection successful" << std::endl;
 }
 
@@ -61,7 +63,7 @@ public:
   }
 
   //! Declares all needed arguments.
-  void declareArguments( const std::string & suffix = "" )
+  void declareArguments( const std::string & suffix = "" ) override
   {
     declare( suffix, "datasource", "Datasource (DSN) to use","PowerDNS");
     declare( suffix, "username", "User to connect as","powerdns");
@@ -86,6 +88,7 @@ public:
     declare(suffix,"info-all-slaves-query","","select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix,"supermaster-query","", "select account from supermasters where ip=? and nameserver=?");
     declare(suffix,"supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+    declare(suffix,"supermaster-add","", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
 
     declare(suffix,"insert-zone-query","", "insert into domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,null,null)");
 
@@ -146,7 +149,7 @@ public:
   }
 
   //! Constructs a new gODBCBackend object.
-  DNSBackend *make(const string & suffix = "" )
+  DNSBackend *make(const string & suffix = "" ) override
   {
     return new gODBCBackend( d_mode, suffix );
   }
index f9fa3a84f16b178f9874293559fefa0d884705a6..02a27cdf378616ce8d45bea190cc95ddeed07167 100644 (file)
@@ -1 +1,8 @@
-ALTER TABLE cryptokeys ADD COLUMN published BOOL DEFAULT true;
+BEGIN;
+  ALTER TABLE cryptokeys ADD COLUMN published BOOL DEFAULT TRUE;
+
+  ALTER TABLE cryptokeys ADD COLUMN content_new TEXT;
+  UPDATE cryptokeys SET content_new = content;
+  ALTER TABLE cryptokeys DROP COLUMN content;
+  ALTER TABLE cryptokeys RENAME COLUMN content_new TO content;
+COMMIT;
index c8e38a86073717a4d7a7d8c6a4dd5cfa241727c4..4ea3f69277113f142a2513146f2db6805410abe5 100644 (file)
@@ -40,17 +40,19 @@ gPgSQLBackend::gPgSQLBackend(const string &mode, const string &suffix)  : GSQLBa
 {
   try {
     setDB(new SPgSQL(getArg("dbname"),
-                 getArg("host"),
-                 getArg("port"),
-                 getArg("user"),
-                 getArg("password"),
-                 getArg("extra-connection-parameters")));
+                     getArg("host"),
+                     getArg("port"),
+                     getArg("user"),
+                     getArg("password"),
+                     getArg("extra-connection-parameters"),
+                     mustDo("prepared-statements")));
   }
 
   catch(SSqlException &e) {
     g_log<<Logger::Error<<mode<<" Connection failed: "<<e.txtReason()<<endl;
     throw PDNSException("Unable to launch "+mode+" connection: "+e.txtReason());
   }
+  allocateStatements();
   g_log<<Logger::Info<<mode<<" Connection successful. Connected to database '"<<getArg("dbname")<<"' on '"<<getArg("host")<<"'."<<endl;
 }
 
@@ -79,7 +81,7 @@ class gPgSQLFactory : public BackendFactory
 public:
   gPgSQLFactory(const string &mode) : BackendFactory(mode),d_mode(mode) {}
 
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"dbname","Backend database name to connect to","");
     declare(suffix,"user","Database backend user to connect as","");
@@ -87,6 +89,7 @@ public:
     declare(suffix,"port","Database backend port to connect to","");
     declare(suffix,"password","Database backend password to connect with","");
     declare(suffix,"extra-connection-parameters", "Extra parameters to add to connection string","");
+    declare(suffix,"prepared-statements", "Use prepared statements instead of parameterized queries", "yes");
 
     declare(suffix,"dnssec","Enable DNSSEC processing","no");
 
@@ -108,6 +111,7 @@ public:
     declare(suffix,"info-all-slaves-query","","select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix,"supermaster-query","", "select account from supermasters where ip=$1 and nameserver=$2");
     declare(suffix,"supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=$1 and account=$2");
+    declare(suffix,"supermaster-add","", "insert into supermasters (ip, nameserver, account) values ($1,$2,$3)");
 
     declare(suffix,"insert-zone-query","", "insert into domains (type,name,master,account,last_check, notified_serial) values($1,$2,$3,$4,null,null)");
 
@@ -165,7 +169,7 @@ public:
 
   }
 
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new gPgSQLBackend(d_mode,suffix);
   }
index 3a7534bb8166923ac4c6a35c5e7fd540ca0673b9..80735c2e6b41c396c11908da6686a30a89568bf7 100644 (file)
@@ -77,7 +77,7 @@ CREATE TABLE cryptokeys (
   domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
   flags                 INT NOT NULL,
   active                BOOL,
-  published             BOOL DEFAULT true,
+  published             BOOL DEFAULT TRUE,
   content               TEXT
 );
 
index 68e5be885fc7cf88d576a52ed39185d3481c27c0..fe6e06517aeb54b0dcb372b8e35f941eafc47146 100644 (file)
 class SPgSQLStatement: public SSqlStatement
 {
 public:
-  SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db) {
+  SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db, unsigned int nstatement) {
     d_query = query;
     d_dolog = dolog;
     d_parent = db;
-    d_prepared = false;
     d_nparams = nparams;
-    d_res = NULL;
-    d_res_set = NULL;
-    paramValues = NULL;
-    paramLengths = NULL;
-    d_paridx = 0;
-    d_residx = 0;
-    d_resnum = 0;
-    d_fnum = 0;
-    d_cur_set = 0;
+    d_nstatement = nstatement;
   }
 
   SSqlStatement* bind(const string& name, bool value) { return bind(name, string(value ? "t" : "f")); }
@@ -77,10 +68,26 @@ public:
   SSqlStatement* execute() {
     prepareStatement();
     if (d_dolog) {
-      g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": " << d_query << endl;
+      g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": Statement: " << d_query << endl;
+      if (d_paridx) {
+        // Log message is similar, bot not exactly the same as the postgres server log.
+        std::stringstream log_message;
+        log_message<< "Query "<<((long)(void*)this)<<": Parameters: ";
+        for (int i = 0; i < d_paridx; i++) {
+          if (i != 0) {
+            log_message << ", ";
+          }
+          log_message << "$" << (i + 1) << " = '" << paramValues[i] << "'";
+        }
+        g_log<<Logger::Warning<< log_message.str() << endl;
+      }
       d_dtime.set();
     }
-    d_res_set = PQexecParams(d_db(), d_query.c_str(), d_nparams, NULL, paramValues, paramLengths, NULL, 0);
+    if (!d_stmt.empty()) {
+      d_res_set = PQexecPrepared(d_db(), d_stmt.c_str(), d_nparams, paramValues, paramLengths, nullptr, 0);
+    } else {
+      d_res_set = PQexecParams(d_db(), d_query.c_str(), d_nparams, nullptr, paramValues, paramLengths, nullptr, 0);
+    }
     ExecStatusType status = PQresultStatus(d_res_set);
     if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
       string errmsg(PQresultErrorMessage(d_res_set));
@@ -98,10 +105,10 @@ public:
   }
 
   void nextResult() {
-    if (d_res_set == NULL) return; // no refcursor
+    if (d_res_set == nullptr) return; // no refcursor
     if (d_cur_set >= PQntuples(d_res_set)) {
       PQclear(d_res_set);
-      d_res_set = NULL;
+      d_res_set = nullptr;
       return;
     }
     // this code handles refcursors if they are returned
@@ -111,7 +118,7 @@ public:
 #if PG_VERSION_NUM > 90000
       // PQescapeIdentifier was added to libpq in postgresql 9.0
       char *val = PQgetvalue(d_res_set, d_cur_set++, 0);
-      char *portal =  PQescapeIdentifier(d_db(), val, strlen(val));
+      char *portal = PQescapeIdentifier(d_db(), val, strlen(val));
       string cmd = string("FETCH ALL FROM \"") + string(portal) + string("\"");
       PQfreemem(portal);
 #else
@@ -119,17 +126,16 @@ public:
       string cmd = string("FETCH ALL FROM \"") + portal + string("\"");
 #endif
       // execute FETCH
-      if (d_dolog)
+      if (d_dolog) {
          g_log<<Logger::Warning<<"Query: "<<cmd<<endl;
+      }
       d_res = PQexec(d_db(),cmd.c_str());
       d_resnum = PQntuples(d_res);
-      d_fnum = PQnfields(d_res);
       d_residx = 0;
     } else {
       d_res = d_res_set;
-      d_res_set = NULL;
+      d_res_set = nullptr;
       d_resnum = PQntuples(d_res);
-      d_fnum = PQnfields(d_res);
     }
   }
 
@@ -160,7 +166,7 @@ public:
     d_residx++;
     if (d_residx >= d_resnum) {
       PQclear(d_res);
-      d_res = NULL;
+      d_res = nullptr;
       nextResult();
     }
     return this;
@@ -168,7 +174,7 @@ public:
 
   SSqlStatement* getResult(result_t& result) {
     result.clear();
-    if (d_res == NULL) return this;
+    if (d_res == nullptr) return this;
     result.reserve(d_resnum);
     row_t row;
     while(hasNextRow()) { nextRow(row); result.push_back(std::move(row)); }
@@ -177,20 +183,26 @@ public:
 
   SSqlStatement* reset() {
      int i;
-     if (d_res)
+     if (d_res) {
        PQclear(d_res);
-     if (d_res_set)
+     }
+     if (d_res_set) {
        PQclear(d_res_set);
-     d_res_set = NULL;
-     d_res = NULL;
+     }
+     d_res_set = nullptr;
+     d_res = nullptr;
      d_paridx = d_residx = d_resnum = 0;
-     if (paramValues)
-       for(i=0;i<d_nparams;i++)
-         if (paramValues[i]) delete [] paramValues[i];
+     if (paramValues) {
+       for(i=0;i<d_nparams;i++) {
+         if (paramValues[i]) {
+           delete [] paramValues[i];
+         }
+       }
+     }
      delete [] paramValues;
-     paramValues = NULL;
+     paramValues = nullptr;
      delete [] paramLengths;
-     paramLengths = NULL;
+     paramLengths = nullptr;
      return this;
   }
 
@@ -207,20 +219,38 @@ private:
   void releaseStatement() {
     d_prepared = false;
     reset();
+    if (!d_stmt.empty()) {
+      string cmd = string("DEALLOCATE " + d_stmt);
+      PGresult *res = PQexec(d_db(), cmd.c_str());
+      PQclear(res);
+      d_stmt.clear();
+    }
   }
 
   void prepareStatement() {
     if (d_prepared) return;
-    paramValues=NULL;
-    d_cur_set=d_paridx=d_residx=d_resnum=d_fnum=0;
-    paramLengths=NULL;
-    d_res=NULL;
-    d_res_set=NULL;
+    if (d_parent->usePrepared()) {
+      // prepare a statement; name must be unique per session (using d_nstatement to ensure this).
+      this->d_stmt = string("stmt") + std::to_string(d_nstatement);
+      PGresult* res = PQprepare(d_db(), d_stmt.c_str(), d_query.c_str(), d_nparams, nullptr);
+      ExecStatusType status = PQresultStatus(res);
+      string errmsg(PQresultErrorMessage(res));
+      PQclear(res);
+      if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
+        releaseStatement();
+        throw SSqlException("Fatal error during prePQpreparepare: " + d_query + string(": ") + errmsg);
+      }
+    }
+    paramValues = nullptr;
+    paramLengths = nullptr;
+    d_cur_set = d_paridx = d_residx = d_resnum = 0;
+    d_res = nullptr;
+    d_res_set = nullptr;
     d_prepared = true;
   }
 
   void allocate() {
-     if (paramValues != NULL) return;
+     if (paramValues != nullptr) return;
      paramValues = new char*[d_nparams];
      paramLengths = new int[d_nparams];
      memset(paramValues, 0, sizeof(char*)*d_nparams);
@@ -228,30 +258,32 @@ private:
   }
 
   string d_query;
+  string d_stmt;
   SPgSQL *d_parent;
-  PGresult *d_res_set;
-  PGresult *d_res;
+  PGresult *d_res_set{nullptr};
+  PGresult *d_res{nullptr};
   bool d_dolog;
   DTime d_dtime; // only used if d_dolog is set
-  bool d_prepared;
+  bool d_prepared{false};
   int d_nparams;
-  int d_paridx;
-  char **paramValues;
-  int *paramLengths;
-  int d_residx;
-  int d_resnum;
-  int d_fnum;
-  int d_cur_set;
+  int d_paridx{0};
+  char **paramValues{nullptr};
+  int *paramLengths{nullptr};
+  int d_residx{0};
+  int d_resnum{0};
+  int d_cur_set{0};
+  unsigned int d_nstatement;
 };
 
 bool SPgSQL::s_dolog;
 
 SPgSQL::SPgSQL(const string &database, const string &host, const string& port, const string &user,
-               const string &password, const string &extra_connection_parameters)
+               const string &password, const string &extra_connection_parameters, const bool use_prepared)
 {
-  d_db=0;
+  d_db = nullptr;
   d_in_trx = false;
-  d_connectstr="";
+  d_connectstr = "";
+  d_nstatements = 0;
 
   if (!database.empty())
     d_connectstr+="dbname="+database;
@@ -275,6 +307,8 @@ SPgSQL::SPgSQL(const string &database, const string &host, const string& port, c
     d_connectstr+=" password="+password;
   }
 
+  d_use_prepared = use_prepared;
+
   d_db=PQconnectdb(d_connectstr.c_str());
 
   if (!d_db || PQstatus(d_db)==CONNECTION_BAD) {
@@ -318,7 +352,8 @@ void SPgSQL::execute(const string& query)
 
 std::unique_ptr<SSqlStatement> SPgSQL::prepare(const string& query, int nparams)
 {
-  return std::unique_ptr<SSqlStatement>(new SPgSQLStatement(query, s_dolog, nparams, this));
+  d_nstatements++;
+  return std::unique_ptr<SSqlStatement>(new SPgSQLStatement(query, s_dolog, nparams, this, d_nstatements));
 }
 
 void SPgSQL::startTransaction() {
index c0aab35ca1625c5a59382b9bf4bc99018676c910..aea85c422197457210a664eb18e0b6508eebc02a 100644 (file)
@@ -29,7 +29,7 @@ class SPgSQL : public SSql
 public:
   SPgSQL(const string &database, const string &host="", const string& port="",
          const string &user="", const string &password="",
-         const string &extra_connection_parameters="");
+         const string &extra_connection_parameters="", const bool use_prepared = true);
 
   ~SPgSQL();
   
@@ -47,6 +47,7 @@ public:
 
   PGconn* db() { return d_db; }
   bool in_trx() const { return d_in_trx; }
+  bool usePrepared() { return d_use_prepared; }
 
 private:
   PGconn* d_db;
@@ -54,4 +55,6 @@ private:
   string d_connectlogstr;
   static bool s_dolog;
   bool d_in_trx;
+  bool d_use_prepared;
+  unsigned int d_nstatements;
 };
index d334fecdfccc953866dfefc967c25178e2b19e11..a6ca8fd3dcf12c3a106c021ffd5d6381e5e988a5 100644 (file)
@@ -1 +1,17 @@
-ALTER TABLE cryptokeys ADD published BOOL DEFAULT 1;
+BEGIN TRANSACTION;
+  CREATE TABLE cryptokeys_temp (
+    id                  INTEGER PRIMARY KEY,
+    domain_id           INT NOT NULL,
+    flags               INT NOT NULL,
+    active              BOOL,
+    published           BOOL DEFAULT 1,
+    content             TEXT,
+    FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
+  );
+
+  INSERT INTO cryptokeys_temp SELECT id,domain_id,flags,active,1,content FROM cryptokeys;
+  DROP TABLE cryptokeys;
+  ALTER TABLE cryptokeys_temp RENAME TO cryptokeys;
+
+  CREATE INDEX domainidindex ON cryptokeys(domain_id);
+COMMIT;
diff --git a/modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql b/modules/gsqlite3backend/4.3.0_to_4.3.1_schema.sqlite3.sql
new file mode 100644 (file)
index 0000000..26eb997
--- /dev/null
@@ -0,0 +1,15 @@
+CREATE INDEX records_lookup_idx ON records(name, type);
+CREATE INDEX records_lookup_id_idx ON records(domain_id, name, type);
+CREATE INDEX records_order_idx ON records(domain_id, ordername);
+
+DROP INDEX IF EXISTS rec_name_index;
+DROP INDEX IF EXISTS nametype_index;
+DROP INDEX IF EXISTS domain_id;
+DROP INDEX IF EXISTS orderindex;
+
+CREATE INDEX comments_idx ON comments(domain_id, name, type);
+
+DROP INDEX IF EXISTS comments_domain_id_index;
+DROP INDEX IF EXISTS comments_nametype_index;
+
+ANALYZE;
index 2a836773803579f1d4eb534d28275a4f353c9de6..4c5de6dcbec7b59fa29107651500822d04668c31 100644 (file)
@@ -10,6 +10,7 @@ dist_doc_DATA = \
        3.4.0_to_4.0.0_schema.sqlite3.sql \
        4.0.0_to_4.2.0_schema.sqlite3.sql \
        4.2.0_to_4.3.0_schema.sqlite3.sql \
+       4.3.0_to_4.3.1_schema.sqlite3.sql \
        schema.sqlite3.sql
 
 libgsqlite3backend_la_SOURCES = gsqlite3backend.cc gsqlite3backend.hh
index d697b83bd5cc5460bcad96c9945148a89b02494b..92a2ec21e296b27337d3e89ade741a7e91ec30f3 100644 (file)
@@ -45,6 +45,7 @@ gSQLite3Backend::gSQLite3Backend( const std::string & mode, const std::string &
   {
     SSQLite3 *ptr = new SSQLite3( getArg( "database" ), getArg( "pragma-journal-mode") );
     setDB(ptr);
+    allocateStatements();
     if(!getArg("pragma-synchronous").empty()) {
       ptr->execute("PRAGMA synchronous="+getArg("pragma-synchronous"));
     }
@@ -72,7 +73,7 @@ public:
   }
   
   //! Declares all needed arguments.
-  void declareArguments( const std::string & suffix = "" )
+  void declareArguments( const std::string & suffix = "" ) override
   {
     declare(suffix, "database", "Filename of the SQLite3 database", "powerdns.sqlite");
     declare(suffix, "pragma-synchronous", "Set this to 0 for blazing speed", "");
@@ -99,6 +100,7 @@ public:
     declare(suffix, "info-all-slaves-query", "","select id,name,master,last_check from domains where type='SLAVE'");
     declare(suffix, "supermaster-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
     declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=:nameserver and account=:account");
+    declare(suffix,"supermaster-add","", "insert into supermasters (ip, nameserver, account) values (:ip,:nameserver,:account)"); 
 
     declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(:type, :domain, :masters, :account, null, null)");
 
@@ -156,7 +158,7 @@ public:
   }
 
   //! Constructs a new gSQLite3Backend object.
-  DNSBackend *make( const string & suffix = "" )
+  DNSBackend *make( const string & suffix = "" ) override
   {
     return new gSQLite3Backend( d_mode, suffix );
   }
index 64a7da0d20f36fe2fd124fc591d023664cea6e12..08755211855fe5be610fd3463a36fa81574445f3 100644 (file)
@@ -27,10 +27,9 @@ CREATE TABLE records (
   FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
 );
 
-CREATE INDEX rec_name_index ON records(name);
-CREATE INDEX nametype_index ON records(name,type);
-CREATE INDEX domain_id ON records(domain_id);
-CREATE INDEX orderindex ON records(ordername);
+CREATE INDEX records_lookup_idx ON records(name, type);
+CREATE INDEX records_lookup_id_idx ON records(domain_id, name, type);
+CREATE INDEX records_order_idx ON records(domain_id, ordername);
 
 
 CREATE TABLE supermasters (
@@ -53,8 +52,7 @@ CREATE TABLE comments (
   FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
 );
 
-CREATE INDEX comments_domain_id_index ON comments (domain_id);
-CREATE INDEX comments_nametype_index ON comments (name, type);
+CREATE INDEX comments_idx ON comments(domain_id, name, type);
 CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
 
 
index e28f3e6bfc37e49a4aed3a61a29f9d7af6ce921f..1073e8031140606f27e9d8aa9580fc861fcbcaa7 100644 (file)
@@ -82,7 +82,7 @@ static int ldapGssapiAuthenticatorSaslInteractCallback( LDAP *conn, unsigned fla
 }
 
 LdapGssapiAuthenticator::LdapGssapiAuthenticator( const std::string& kt, const std::string &ccache, int tmout )
-  : d_logPrefix( "[LDAP GSSAPI] " ), d_keytabFile( kt ), d_cCacheFile( ccache ), d_timeout( tmout )
+  : d_logPrefix( "[LDAP GSSAPI] " ), d_keytabFile( kt ), d_cCacheFile( ccache )
 {
   krb5_error_code code;
 
@@ -118,7 +118,7 @@ bool LdapGssapiAuthenticator::authenticate( LDAP *conn )
     return false;
   }
   else if ( code == -2 ) {
-    // Here it may be possible to retry after obtainting a fresh ticket
+    // Here it may be possible to retry after obtaining a fresh ticket
     g_log<<Logger::Debug << d_logPrefix << "No TGT found, trying to acquire a new one" << std::endl;
     code = updateTgt();
 
index 5cf8d412008aa4ede5f91ae301cd55803dfb26d5..011549933a7f2dbcedb05cf95f75e77fd9e42b75 100644 (file)
@@ -45,7 +45,6 @@ class LdapGssapiAuthenticator : public LdapAuthenticator
     std::string d_logPrefix;
     std::string d_keytabFile;
     std::string d_cCacheFile;
-    int d_timeout;
     std::string d_lastError;
 
     krb5_context d_context;
index 7078bf5513e48fa03426eb0300da7024e8b816f2..f428a1adf0c311a24068dc3cf1dcf7ea2311361e 100644 (file)
@@ -277,7 +277,7 @@ class LdapFactory : public BackendFactory
 
     LdapFactory() : BackendFactory( "ldap" ) {}
 
-    void declareArguments( const string &suffix="" )
+    void declareArguments( const string &suffix="" ) override
     {
       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" );
@@ -297,7 +297,7 @@ class LdapFactory : public BackendFactory
     }
 
 
-    DNSBackend* make( const string &suffix="" )
+    DNSBackend* make( const string &suffix="" ) override
     {
       return new LdapBackend( suffix );
     }
index 80fc6b5ab592ff7af4384a10f47c373d7a674b15..fa3f09cc274895c6cbfa6ebb22d175e61dee40c9 100644 (file)
@@ -231,18 +231,18 @@ void serFromString(const string_view& str, DNSResourceRecord& rr)
 }
 
 
-std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
+static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
 {
-  auto drc = DNSRecordContent::mastermake(qtype, 1, content);
+  auto drc = DNSRecordContent::mastermake(qtype, QClass::IN, content);
   return drc->serialize(domain, false);
 }
 
-std::shared_ptr<DNSRecordContent> unserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
+static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
 {
   if(qtype == QType::A && content.size() == 4) {
     return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
   }
-  return DNSRecordContent::unserialize(qname, qtype, content);
+  return DNSRecordContent::deserialize(qname, qtype, content);
 }
 
 
@@ -595,6 +595,11 @@ void LMDBBackend::lookup(const QType &type, const DNSName &qdomain, int zoneId,
   }
     
   DNSName relqname = qdomain.makeRelative(hunt);
+
+  if(relqname.empty()) {
+    throw DBException("lookup for out of zone rrset");
+  }
+
   //  cout<<"get will look for "<<relqname<< " in zone "<<hunt<<" with id "<<zoneId<<endl;
   d_rotxn = getRecordsROTransaction(zoneId, d_rwtxn);
 
@@ -653,7 +658,7 @@ bool LMDBBackend::get(DNSZoneRecord& rr)
     rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
     rr.domain_id = compoundOrdername::getDomainID(key);
     rr.dr.d_ttl = drr.ttl;
-    rr.dr.d_content = unserializeContentZR(rr.dr.d_type, rr.dr.d_name, drr.content);
+    rr.dr.d_content = deserializeContentZR(rr.dr.d_type, rr.dr.d_name, drr.content);
     rr.auth = drr.auth;
 
     if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
@@ -758,25 +763,14 @@ void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
 }
 
 
-bool LMDBBackend::setMaster(const DNSName &domain, const std::string& ips)
+bool LMDBBackend::setMasters(const DNSName &domain, const vector<ComboAddress> &masters)
 {
-  vector<ComboAddress> masters;
-  vector<string> parts;
-  stringtok(parts, ips, " \t;,");
-  for(const auto& ip : parts) 
-    masters.push_back(ComboAddress(ip, 53));
-  
   return genChangeDomain(domain, [&masters](DomainInfo& di) {
       di.masters = masters;
     });
 }
 
-bool LMDBBackend::createDomain(const DNSName &domain)
-{
-  return createDomain(domain, "NATIVE", "", "");
-}
-          
-bool LMDBBackend::createDomain(const DNSName &domain, const string &type, const string &masters, const string &account)
+bool LMDBBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account)
 {
   DomainInfo di;
 
@@ -784,16 +778,10 @@ bool LMDBBackend::createDomain(const DNSName &domain, const string &type, const
   if(txn.get<0>(domain, di)) {
     throw DBException("Domain '"+domain.toLogString()+"' exists already");
   }
-  
+
   di.zone = domain;
-  if(pdns_iequals(type, "master"))
-    di.kind = DomainInfo::Master;
-  else if(pdns_iequals(type, "slave"))
-    di.kind = DomainInfo::Slave;
-  else if(pdns_iequals(type, "native"))
-    di.kind = DomainInfo::Native;
-  else
-    throw DBException("Unable to create domain of unknown type '"+type+"'");
+  di.kind = kind;
+  di.masters = masters;
   di.account = account;
 
   txn.put(di);
@@ -1575,7 +1563,7 @@ class LMDBFactory : public BackendFactory
 {
 public:
   LMDBFactory() : BackendFactory("lmdb") {}
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"filename","Filename for lmdb","./pdns.lmdb");
     declare(suffix,"sync-mode","Synchronisation mode: nosync, nometasync, mapasync, sync","mapasync");
@@ -1583,7 +1571,7 @@ public:
     declare(suffix,"shards","Records database will be split into this number of shards", (sizeof(long) == 4) ? "2" : "64");
     declare(suffix,"schema-version","Maximum allowed schema version to run on this DB. If a lower version is found, auto update is performed", SCHEMAVERSION_TEXT); 
   }
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new LMDBBackend(suffix);
   }
index 6199f6fdc26d8574018220677f06992262cb207f..d37b497879dfef1be24d841f9b638859b1f30857 100644 (file)
@@ -38,10 +38,8 @@ public:
   bool list(const DNSName &target, int id, bool include_disabled) override;
 
   bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true) override;
-  bool createDomain(const DNSName &domain, const string &type, const string &masters, const string &account);
+  bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account) override;
 
-  bool createDomain(const DNSName &domain) override;
-  
   bool startTransaction(const DNSName &domain, int domain_id=-1) override;
   bool commitTransaction() override;
   bool abortTransaction() override;
@@ -57,7 +55,7 @@ public:
 
   void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
   
-  bool setMaster(const DNSName &domain, const string &ip) override;
+  bool setMasters(const DNSName &domain, const vector<ComboAddress> &masters) override;
   bool setKind(const DNSName &domain, const DomainInfo::DomainKind kind) override;
   bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta) override;
   bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
index 378203eccdac2c8418b5f3267678e777463468da..b3ce01e0d5f8e5b2c5485d242aaf22814397f7bd 100644 (file)
@@ -31,14 +31,14 @@ class Lua2Factory : public BackendFactory
 public:
   Lua2Factory() : BackendFactory("lua2") {}
 
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"filename","Filename of the script for lua backend","powerdns-luabackend.lua");
     declare(suffix,"query-logging","Logging of the Lua2 Backend","no");
     declare(suffix,"api","Lua backend API version","2");
   }
 
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     const std::string apiSet = "lua2" + suffix + "-api";
     const int api = ::arg().asNum(apiSet);
index 8a5183977f93caddbeab60f4daf902e1119800d7..52ca5f95a33e9189e813369e965aa6d4a82e7a70 100644 (file)
@@ -232,12 +232,7 @@ UnixRemote::UnixRemote(const string& path, int timeout)
   if(connect(d_fd, (struct sockaddr*)&remote, sizeof(remote)) < 0)
     unixDie("Unable to connect to remote '"+path+"' using UNIX domain socket");
 
-  d_fp = fdopen(d_fd, "r");
-}
-
-UnixRemote::~UnixRemote()
-{
-  fclose(d_fp);
+  d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(d_fd, "r"), fclose);
 }
 
 void UnixRemote::send(const string& line)
@@ -250,7 +245,7 @@ void UnixRemote::send(const string& line)
 void UnixRemote::receive(string& line)
 {
   line.clear();
-  stringfgets(d_fp, line);
+  stringfgets(d_fp.get(), line);
   trim_right(line);
 }
 
index 84c516e3d0eaa64994ae4393e2a6bbd2bd11a560..87099db5667963a9661dfb168cbbb0e6c23210db 100644 (file)
@@ -61,12 +61,11 @@ class UnixRemote : public CoRemote
 {
 public:
   UnixRemote(const string &path, int timeout=0);
-  ~UnixRemote();
   void sendReceive(const string &send, string &receive) override;
   void receive(string &rcv) override;
   void send(const string &send) override;
 private:
   int d_fd;
-  FILE *d_fp;
+  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
 };
 bool isUnixSocket(const string& fname);
index 103d39e1f1a6bcc7279f7c8c79bbd4e7f52f09dc..208ef2e5d4f0987006db807f19dd98a1ad1a88ec 100644 (file)
@@ -357,7 +357,7 @@ class PipeFactory : public BackendFactory
    public:
       PipeFactory() : BackendFactory("pipe") {}
 
-      void declareArguments(const string &suffix="")
+      void declareArguments(const string &suffix="") override
       {
          declare(suffix,"command","Command to execute for piping questions to","");
          declare(suffix,"timeout","Number of milliseconds to wait for an answer","2000");
@@ -365,7 +365,7 @@ class PipeFactory : public BackendFactory
          declare(suffix,"abi-version","Version of the pipe backend ABI","1");
       }
 
-      DNSBackend *make(const string &suffix="")
+      DNSBackend *make(const string &suffix="") override
       {
          return new PipeBackend(suffix);
       }
index 577ba63904c5ce464dd91353e2892626aa8b7192..20b179b8d3a2df721e8b1a1e658bb52f1299f531 100644 (file)
@@ -102,11 +102,11 @@ class RandomFactory : public BackendFactory
 {
 public:
   RandomFactory() : BackendFactory("random") {}
-  void declareArguments(const string &suffix="")
+  void declareArguments(const string &suffix="") override
   {
     declare(suffix,"hostname","Hostname which is to be random","random.example.com");
   }
-  DNSBackend *make(const string &suffix="")
+  DNSBackend *make(const string &suffix="") override
   {
     return new RandomBackend(suffix);
   }
index d7987a906814a5f286e1dcc569ad1037418d2fe9..974f6cf9a431fd514c9e7394440018f19e57ed44 100644 (file)
@@ -1,13 +1,13 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    ffi (1.9.24)
-    ffi-rzmq (2.0.1)
-      ffi-rzmq-core (>= 1.0.1)
-    ffi-rzmq-core (1.0.3)
-      ffi (~> 1.9)
-    json (1.8.5)
-    sqlite3 (1.3.9)
+    ffi (1.10.0)
+    ffi-rzmq (2.0.7)
+      ffi-rzmq-core (>= 1.0.7)
+    ffi-rzmq-core (1.0.7)
+      ffi
+    json (2.3.0)
+    sqlite3 (1.3.13)
     webrick (1.4.2)
     zeromqrb (0.1.3)
       ffi-rzmq
@@ -22,4 +22,4 @@ DEPENDENCIES
   zeromqrb
 
 BUNDLED WITH
-   1.10.2
+   1.16.1
index e106047624b284c494cf94acea7b3ae4ccfb7bad..fc120a423f168863def3ca1f0d0309350be91a07 100644 (file)
@@ -123,8 +123,8 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/nameserver.cc \
        ../../pdns/rcpgenerator.cc \
        ../../pdns/unix_utility.cc \
-       ../../pdns/gss_context.cc ../../pdns/gss_context.hh \
        ../../pdns/json.hh ../../pdns/json.cc \
+       ../../pdns/shuffle.hh ../../pdns/shuffle.cc \
        httpconnector.cc \
        pipeconnector.cc \
        unixconnector.cc \
@@ -160,13 +160,6 @@ libtestremotebackend_la_CPPFLAGS += \
        $(P11KIT1_CFLAGS)
 endif
 
-if GSS_TSIG
-libtestremotebackend_la_LIBADD += \
-       $(GSS_LIBS)
-libtestremotebackend_la_CPPFLAGS+= \
-       $(GSS_CFLAGS)
-endif
-
 remotebackend_http_test_SOURCES = \
        test-remotebackend.cc \
        test-remotebackend-http.cc \
index 675f1076b12444c8fda5243ce5172fc9f946dd5f..6d5bc3ab0e1af2b39b4483a43f23bd52b70b1bb7 100644 (file)
@@ -384,7 +384,6 @@ int HTTPConnector::recv_message(Json& output) {
     if (d_socket == nullptr ) return -1; // cannot receive :(
     char buffer[4096];
     int rd = -1;
-    bool fail = false;
     time_t t0;
 
     arl.initialize(&resp);
@@ -399,28 +398,22 @@ int HTTPConnector::recv_message(Json& output) {
           throw NetworkError(std::string(strerror(rd)));
         arl.feed(std::string(buffer, rd));
       }
-      // timeout occured.
+      // timeout occurred.
       if (arl.ready() == false)
         throw NetworkError("timeout");
     } catch (NetworkError &ne) {
-      g_log<<Logger::Error<<"While reading from HTTP endpoint "<<d_addr.toStringWithPort()<<": "<<ne.what()<<std::endl; 
       d_socket.reset();
-      fail = true;
+      throw PDNSException("While reading from HTTP endpoint " + d_addr.toStringWithPort() + ": " + ne.what());
     } catch (...) {
-      g_log<<Logger::Error<<"While reading from HTTP endpoint "<<d_addr.toStringWithPort()<<": exception caught"<<std::endl;
       d_socket.reset();
-      fail = true;
-    }
-
-    if (fail) {
-      return -1;
+      throw PDNSException("While reading from HTTP endpoint " + d_addr.toStringWithPort() + ": unknown error");
     }
 
     arl.finalize();
 
-    if (resp.status < 200 || resp.status >= 400) {
+    if ((resp.status < 200 || resp.status >= 400) && resp.status != 404) {
       // bad. 
-      return -1;
+      throw PDNSException("Received unacceptable HTTP status code " + std::to_string(resp.status) + " from HTTP endpoint " + d_addr.toStringWithPort());
     }
 
     int rv = -1;
deleted file mode 100644 (file)
index c1be5e3f2919f001a98c7f0b1199a8f1a2c0818c..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,22 +0,0 @@
-GEM
-  remote: https://rubygems.org/
-  specs:
-    ffi (1.9.24)
-    ffi-rzmq (2.0.1)
-      ffi-rzmq-core (>= 1.0.1)
-    ffi-rzmq-core (1.0.3)
-      ffi (~> 1.9)
-    json (1.8.2)
-    sqlite3 (1.3.9)
-    webrick (1.4.2)
-    zeromqrb (0.1.3)
-      ffi-rzmq
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  json
-  sqlite3
-  webrick
-  zeromqrb
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..412e45f20532fbeedfe46c61b27c05bfeeab6a57
--- /dev/null
@@ -0,0 +1 @@
+../Gemfile.lock
\ No newline at end of file
index b5862edbc273adf6b5663441fc8691e756992524..220b518f0e0a73a5a820ffc5b54a3121a656335d 100644 (file)
@@ -42,18 +42,27 @@ bool Connector::send(Json& value) {
  * result. Logging is performed here, too.
  */
 bool Connector::recv(Json& value) {
-    if (recv_message(value)>0) {
-       bool rv = true;
-       // check for error
-       if (value["result"] == Json())
-         return false;
-       if (value["result"].is_bool() && boolFromJson(value, "result", false) == false)
-         rv = false;
-       for(const auto& message: value["log"].array_items())
-         g_log<<Logger::Info<<"[remotebackend]: "<< message.string_value() <<std::endl;
-       return rv;
+  if (recv_message(value) > 0) {
+    bool retval = true;
+    if (value["result"] == Json()) {
+      throw PDNSException("No 'result' field in response from remote process");
+    } else if (value["result"].is_bool() && boolFromJson(value, "result", false) == false) {
+      retval = false;
     }
-    return false;
+    for(const auto& message: value["log"].array_items()) {
+      g_log<<Logger::Info<<"[remotebackend]: "<< message.string_value() <<std::endl;
+    }
+    return retval;
+  }
+  throw PDNSException("Unknown error while receiving data");
+}
+
+void RemoteBackend::makeErrorAndThrow(Json &value) {
+  std::string msg = "Remote process indicated a failure";
+  for(const auto& message: value["log"].array_items()) {
+    msg += " '" + message.string_value() + "'";
+  }
+  throw PDNSException(msg);
 }
 
 /**
@@ -74,29 +83,31 @@ RemoteBackend::RemoteBackend(const std::string &suffix)
 RemoteBackend::~RemoteBackend() { }
 
 bool RemoteBackend::send(Json& value) {
-   try {
-     return connector->send(value);
-   } catch (PDNSException &ex) {
-     g_log<<Logger::Error<<"Exception caught when sending: "<<ex.reason<<std::endl;
-   }
-
-   this->connector.reset();
-   build();
-   return false;
+  try {
+    if (!connector->send(value)) {
+      // XXX does this work work even though we throw?
+      this->connector.reset();
+      build();
+      throw DBException("Could not send a message to remote process");
+    }
+  } catch (const PDNSException &ex) {
+    throw DBException("Exception caught when sending: " + ex.reason);
+  }
+  return true;
 }
 
 bool RemoteBackend::recv(Json& value) {
-   try {
-     return connector->recv(value);
-   } catch (PDNSException &ex) {
-     g_log<<Logger::Error<<"Exception caught when receiving: "<<ex.reason<<std::endl;
-   } catch (...) {
-     g_log<<Logger::Error<<"Exception caught when receiving"<<std::endl;;
-   }
-
-   this->connector.reset();
-   build();
-   return false;
+  try {
+    return connector->recv(value);
+  } catch (const PDNSException &ex) {
+    this->connector.reset();
+    build();
+    throw DBException("Exception caught when receiving: " + ex.reason);
+  } catch (const std::exception &e) {
+    this->connector.reset();
+    build();
+    throw DBException("Exception caught when receiving: " + std::string(e.what()));
+  }
 }
 
 
@@ -1025,13 +1036,13 @@ class RemoteBackendFactory : public BackendFactory
   public:
       RemoteBackendFactory() : BackendFactory("remote") {}
 
-      void declareArguments(const std::string &suffix="")
+      void declareArguments(const std::string &suffix="") override
       {
           declare(suffix,"dnssec","Enable dnssec support","no");
           declare(suffix,"connection-string","Connection string","");
       }
 
-      DNSBackend *make(const std::string &suffix="")
+      DNSBackend *make(const std::string &suffix="") override
       {
          return new RemoteBackend(suffix);
       }
index 5f94d223bc4c0864a06768f3a3dde8ac24cedc5d..cadc8eed7e8e0742f9e0c0327c5d2b27b7ba9b97 100644 (file)
@@ -207,6 +207,7 @@ class RemoteBackend : public DNSBackend
 
     bool send(Json &value);
     bool recv(Json &value);
+    void makeErrorAndThrow(Json &value);
  
     string asString(const Json& value) {
       if (value.is_number()) return std::to_string(value.int_value());
index 72e31c61d29e50707802af721aa00f0b3b562d67..1de99d5e0232fef5b15401ab76efa1bd43bbd977 100644 (file)
@@ -29,7 +29,7 @@
 
 static string backendname="[TinyDNSBackend] ";
 uint32_t TinyDNSBackend::s_lastId;
-pthread_mutex_t TinyDNSBackend::s_domainInfoLock=PTHREAD_MUTEX_INITIALIZER;
+std::mutex TinyDNSBackend::s_domainInfoLock;
 TinyDNSBackend::TDI_suffix_t TinyDNSBackend::s_domainInfo;
 
 vector<string> TinyDNSBackend::getLocations()
@@ -90,7 +90,7 @@ TinyDNSBackend::TinyDNSBackend(const string &suffix)
 }
 
 void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>* retDomains) {
-  Lock l(&s_domainInfoLock); //TODO: We could actually lock less if we do it per suffix.
+  std::lock_guard<std::mutex> l(s_domainInfoLock); //TODO: We could actually lock less if we do it per suffix.
 
   if (! s_domainInfo.count(d_suffix)) {
     TDI_t tmp;
@@ -133,7 +133,7 @@ void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>* retDomains) {
 }
 
 void TinyDNSBackend::setNotified(uint32_t id, uint32_t serial) {
-  Lock l(&s_domainInfoLock);
+  std::lock_guard<std::mutex> l(s_domainInfoLock);
   if (!s_domainInfo.count(d_suffix)) {
     throw PDNSException("Can't get list of domains to set the serial.");
   }
@@ -348,7 +348,7 @@ class TinyDNSFactory: public BackendFactory
 public:
   TinyDNSFactory() : BackendFactory("tinydns") {}
 
-  void declareArguments(const string &suffix="") {
+  void declareArguments(const string &suffix="") override {
     declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the slave nameservers on startup. Default is no.", "no");
     declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
     declare(suffix, "tai-adjust", "This adjusts the TAI value if timestamps are used. These seconds will be added to the start point (1970) and will allow you to adjust for leap seconds. The default is 11.", "11");
@@ -356,7 +356,7 @@ public:
     declare(suffix, "ignore-bogus-records", "The data.cdb file might have some incorrect record data, this causes PowerDNS to fail, where tinydns would send out truncated data. This option makes powerdns ignore that data!", "no");
   }
 
-  DNSBackend *make(const string &suffix="") {
+  DNSBackend *make(const string &suffix="") override {
     return new TinyDNSBackend(suffix);
   }
 };
index 5029546714523831585b75cd4374f8e556e2b247..ecf28e075ee27ad44240dd907388e3aba610c27b 100644 (file)
@@ -32,6 +32,7 @@
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/hashed_index.hpp>
 #include <boost/multi_index/member.hpp>
+#include <mutex>
 
 using namespace ::boost;
 using namespace ::boost::multi_index;
@@ -102,7 +103,7 @@ private:
   string d_suffix;
 
   // Statics
-  static pthread_mutex_t s_domainInfoLock;
+  static std::mutex s_domainInfoLock;
   static TDI_suffix_t s_domainInfo;
   static uint32_t s_lastId; // used to give a domain an id.
 };
index c17b907b92653a0285c47e96bb37fe50f81078da..41e738c99468f0cabd7bb32aee551c938e000ca1 100644 (file)
@@ -65,4 +65,5 @@ effective_tld_names.dat
 /fuzz_target_dnsdistcache
 /fuzz_target_moadnsparser
 /fuzz_target_packetcache
+/fuzz_target_proxyprotocol
 /fuzz_target_zoneparsertng
index ab62112896dba3e3fc7e7eab02d26b6d6c05984e..10c77ebc79d87611f6c779c7384b9e9c24538826 100644 (file)
@@ -32,10 +32,6 @@ if LUA
 AM_CPPFLAGS +=$(LUA_CFLAGS)
 endif
 
-if GSS_TSIG
-AM_CPPFLAGS +=$(GSS_CFLAGS)
-endif
-
 if LIBSODIUM
 AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
 endif
@@ -47,8 +43,6 @@ EXTRA_DIST = \
        effective_tld_names.dat \
        mtasker.cc \
        inflighter.cc \
-       bind-dnssec.4.2.0_to_4.3.0_schema.sqlite3.sql \
-       bind-dnssec.schema.sqlite3.sql \
        bindparser.h \
        named.conf.parsertest \
        pdns.service.in \
@@ -186,7 +180,6 @@ pdns_server_SOURCES = \
        dynmessenger.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
-       gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
        ixfr.cc ixfr.hh \
        json.cc json.hh \
@@ -204,15 +197,18 @@ pdns_server_SOURCES = \
        packethandler.cc packethandler.hh \
        pdnsexception.hh \
        qtype.cc qtype.hh \
+       query-local-address.hh query-local-address.cc \
        rcpgenerator.cc \
        receiver.cc \
        resolver.cc resolver.hh \
+       axfr-retriever.cc axfr-retriever.hh \
        responsestats.cc responsestats.hh responsestats-auth.cc \
        rfc2136handler.cc \
        secpoll.cc secpoll.hh \
        secpoll-auth.cc secpoll-auth.hh \
        serialtweaker.cc \
        sha.hh \
+       shuffle.cc shuffle.hh \
        signingpipe.cc signingpipe.hh \
        sillyrecords.cc \
        slavecommunicator.cc \
@@ -229,6 +225,7 @@ pdns_server_SOURCES = \
        unix_utility.cc \
        utility.hh \
        version.cc version.hh \
+       views.hh \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-auth.cc ws-auth.hh \
@@ -278,10 +275,6 @@ if LUA
 pdns_server_LDADD += $(LUA_LIBS)
 endif
 
-if GSS_TSIG
-pdns_server_LDADD += $(GSS_LIBS)
-endif
-
 pdnsutil_SOURCES = \
        arguments.cc \
        auth-caches.cc auth-caches.hh \
@@ -310,7 +303,6 @@ pdnsutil_SOURCES = \
        dynlistener.cc \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
-       gss_context.cc gss_context.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc iputils.hh \
        json.cc \
@@ -324,6 +316,7 @@ pdnsutil_SOURCES = \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        serialtweaker.cc \
+       shuffle.cc shuffle.hh \
        signingpipe.cc \
        sillyrecords.cc \
        sstuff.hh \
@@ -376,10 +369,6 @@ if LUA
 pdnsutil_LDADD += $(LUA_LIBS)
 endif
 
-if GSS_TSIG
-pdnsutil_LDADD += $(GSS_LIBS)
-endif
-
 zone2sql_SOURCES = \
        arguments.cc \
        base32.cc \
@@ -486,6 +475,7 @@ sdig_SOURCES = \
        logger.cc \
        misc.cc misc.hh \
        nsecrecords.cc \
+       proxy-protocol.cc proxy-protocol.hh \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        sdig.cc \
@@ -534,6 +524,7 @@ dumresp_SOURCES = \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dumresp.cc \
+       iputils.cc iputils.hh \
        logger.cc \
        misc.cc misc.hh \
        statbag.cc \
@@ -584,7 +575,6 @@ saxfr_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
-       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -605,10 +595,6 @@ saxfr_SOURCES += pkcs11signers.cc pkcs11signers.hh
 saxfr_LDADD += $(P11KIT1_LIBS)
 endif
 
-if GSS_TSIG
-saxfr_LDADD += $(GSS_LIBS)
-endif
-
 ixfrdist_SOURCES = \
        arguments.cc \
        base32.cc \
@@ -621,7 +607,6 @@ ixfrdist_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
-       gss_context.cc gss_context.hh \
        iputils.hh iputils.cc \
        ixfr.cc ixfr.hh \
        ixfrdist.cc \
@@ -632,9 +617,11 @@ ixfrdist_SOURCES = \
        misc.cc misc.hh \
        mplexer.hh \
        nsecrecords.cc \
+       query-local-address.hh query-local-address.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        resolver.cc \
+       axfr-retriever.cc \
        pollmplexer.cc \
        sillyrecords.cc \
        sstuff.hh \
@@ -664,11 +651,6 @@ ixfrdist_SOURCES += pkcs11signers.cc pkcs11signers.hh
 ixfrdist_LDADD += $(P11KIT1_LIBS)
 endif
 
-if GSS_TSIG
-ixfrdist_LDADD += $(GSS_LIBS)
-endif
-
-
 ixplore_SOURCES = \
        arguments.cc \
        base32.cc \
@@ -681,14 +663,15 @@ ixplore_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
-       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
        nsecrecords.cc \
+       query-local-address.hh query-local-address.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        resolver.cc \
+       axfr-retriever.cc \
        ixfr.cc ixfr.hh \
        ixfrutils.cc ixfrutils.hh \
        ixplore.cc \
@@ -706,11 +689,6 @@ ixplore_SOURCES += pkcs11signers.cc pkcs11signers.hh
 ixplore_LDADD += $(P11KIT1_LIBS)
 endif
 
-if GSS_TSIG
-ixplore_LDADD += $(GSS_LIBS)
-endif
-
-
 dnstcpbench_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
@@ -750,7 +728,6 @@ nsec3dig_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
-       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -771,10 +748,6 @@ nsec3dig_SOURCES += pkcs11signers.cc pkcs11signers.hh
 nsec3dig_LDADD += $(P11KIT1_LIBS)
 endif
 
-if GSS_TSIG
-nsec3dig_LDADD += $(GSS_LIBS)
-endif
-
 toysdig_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
@@ -787,7 +760,6 @@ toysdig_SOURCES = \
        dnswriter.cc dnswriter.hh \
        ednssubnet.cc ednssubnet.hh \
        filterpo.hh \
-       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -812,16 +784,13 @@ toysdig_LDFLAGS = $(AM_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS)
 toysdig_LDADD = $(LIBCRYPTO_LIBS)
 
-if GSS_TSIG
-toysdig_LDADD += $(GSS_LIBS)
-endif
-
 if PKCS11
 toysdig_SOURCES += pkcs11signers.cc pkcs11signers.hh
 toysdig_LDADD += $(P11KIT1_LIBS)
 endif
 
 tsig_tests_SOURCES = \
+       axfr-retriever.cc \
        arguments.cc \
        base32.cc \
        base64.cc base64.hh \
@@ -834,10 +803,11 @@ tsig_tests_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
-       gss_context.cc gss_context.hh \
+       iputils.cc \
        logger.cc \
        misc.cc misc.hh \
        nsecrecords.cc \
+       query-local-address.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        resolver.cc \
@@ -856,10 +826,6 @@ tsig_tests_SOURCES += pkcs11signers.cc pkcs11signers.hh
 tsig_tests_LDADD += $(P11KIT1_LIBS)
 endif
 
-if GSS_TSIG
-tsig_tests_LDADD += $(GSS_LIBS)
-endif
-
 speedtest_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
@@ -917,7 +883,9 @@ dnsbulktest_SOURCES = \
        rcpgenerator.cc \
        sillyrecords.cc \
        statbag.cc \
-       unix_utility.cc
+       unix_utility.cc \
+       arguments.cc arguments.hh \
+       dns_random.cc dns_random.hh
 
 dnsbulktest_LDFLAGS = \
        $(AM_LDFLAGS) \
@@ -1080,6 +1048,7 @@ pdns_notify_LDADD = \
 
 if LIBSODIUM
 pdns_notify_LDADD += $(LIBSODIUM_LIBS)
+dnsbulktest_LDADD += $(LIBSODIUM_LIBS)
 endif
 
 dnsscope_SOURCES = \
@@ -1251,7 +1220,7 @@ endif
 endif
 
 pdns.conf-dist: pdns_server
-       $(AM_V_GEN)./pdns_server --no-config --config=default 2>/dev/null > $@
+       $(AM_V_GEN)./pdns_server --config=default 2>/dev/null > $@
 
 testrunner_SOURCES = \
        arguments.cc \
@@ -1279,7 +1248,6 @@ testrunner_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednssubnet.cc \
        gettime.cc gettime.hh \
-       gss_context.cc gss_context.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc \
        ixfr.cc ixfr.hh \
@@ -1292,10 +1260,12 @@ testrunner_SOURCES = \
        nsecrecords.cc \
        opensslsigners.cc opensslsigners.hh \
        pollmplexer.cc \
+       proxy-protocol.cc proxy-protocol.hh \
        qtype.cc \
        rcpgenerator.cc \
        responsestats.cc \
        responsestats-auth.cc \
+       shuffle.cc shuffle.hh \
        sillyrecords.cc \
        statbag.cc \
        test-arguments_cc.cc \
@@ -1322,16 +1292,18 @@ testrunner_SOURCES = \
        test-nameserver_cc.cc \
        test-packetcache_cc.cc \
        test-packetcache_hh.cc \
+       test-proxy_protocol_cc.cc \
        test-rcpgenerator_cc.cc \
        test-signers.cc \
        test-sha_hh.cc \
        test-statbag_cc.cc \
        test-tsig.cc \
+       test-ueberbackend_cc.cc \
        test-zoneparser_tng_cc.cc \
        testrunner.cc \
        threadname.hh threadname.cc \
        tsigverifier.cc tsigverifier.hh \
-       ueberbackend.cc \
+       ueberbackend.cc ueberbackend.hh \
        unix_utility.cc \
        zoneparser-tng.cc zoneparser-tng.hh
 
@@ -1420,6 +1392,7 @@ fuzz_targets_programs =  \
        fuzz_target_dnsdistcache \
        fuzz_target_moadnsparser \
        fuzz_target_packetcache \
+       fuzz_target_proxyprotocol \
        fuzz_target_zoneparsertng
 
 fuzz_targets: $(fuzz_targets_programs)
@@ -1476,6 +1449,16 @@ fuzz_target_packetcache_DEPENDENCIES = $(fuzz_targets_deps)
 fuzz_target_packetcache_LDFLAGS = $(fuzz_targets_ldflags)
 fuzz_target_packetcache_LDADD = $(fuzz_targets_libs)
 
+fuzz_target_proxyprotocol_SOURCES = \
+       fuzz_proxyprotocol.cc \
+       iputils.hh \
+       proxy-protocol.cc \
+       proxy-protocol.hh
+
+fuzz_target_proxyprotocol_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_proxyprotocol_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_proxyprotocol_LDADD = $(fuzz_targets_libs)
+
 fuzz_target_dnsdistcache_SOURCES = \
        fuzz_dnsdistcache.cc \
        dnsdist-cache.cc dnsdist-cache.hh \
@@ -1563,12 +1546,24 @@ endif
 if !HAVE_SYSTEMD_PRIVATE_TMP
        $(AM_V_GEN)perl -ni -e 'print unless /^PrivateTmp/' $@
 endif
+if !HAVE_SYSTEMD_PRIVATE_USERS
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateUsers/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_CLOCK
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectClock/' $@
+endif
 if !HAVE_SYSTEMD_PROTECT_CONTROL_GROUPS
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectControlGroups/' $@
 endif
 if !HAVE_SYSTEMD_PROTECT_HOME
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHome/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_HOSTNAME
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHostname/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_KERNEL_LOGS
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelLogs/' $@
+endif
 if !HAVE_SYSTEMD_PROTECT_KERNEL_MODULES
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelModules/' $@
 endif
@@ -1587,6 +1582,9 @@ endif
 if !HAVE_SYSTEMD_RESTRICT_REALTIME
        $(AM_V_GEN)perl -ni -e 'print unless /^RestrictRealtime/' $@
 endif
+if !HAVE_SYSTEMD_RESTRICT_SUIDSGID
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictSUIDSGID/' $@
+endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_ARCHITECTURES
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallArchitectures/' $@
 endif
@@ -1597,6 +1595,8 @@ endif
 pdns@.service: pdns.service
        $(AM_V_GEN)sed -e 's!/pdns_server!& --config-name=%i!' \
          -e 's!Authoritative Server!& %i!' \
+         -e 's!RuntimeDirectory=.*!&-%i!' \
+         -e 's!SyslogIdentifier=.*!&-%i!' \
          < $< > $@
 
 systemdsystemunitdir = $(SYSTEMD_DIR)
@@ -1612,6 +1612,60 @@ CLEANFILES += \
 if IXFRDIST
 ixfrdist.service: ixfrdist.service.in
        $(AM_V_GEN)sed -e 's![@]bindir[@]!$(bindir)!' < $< > $@
+if !HAVE_SYSTEMD_LOCK_PERSONALITY
+       $(AM_V_GEN)perl -ni -e 'print unless /^LockPersonality/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_DEVICES
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateDevices/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_TMP
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateTmp/' $@
+endif
+if !HAVE_SYSTEMD_PRIVATE_USERS
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateUsers/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_CLOCK
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectClock/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_CONTROL_GROUPS
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectControlGroups/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_HOME
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHome/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_HOSTNAME
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHostname/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_KERNEL_LOGS
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelLogs/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_KERNEL_MODULES
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelModules/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_KERNEL_TUNABLES
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelTunables/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_SYSTEM
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectSystem/' $@
+endif
+if !HAVE_SYSTEMD_RESTRICT_ADDRESS_FAMILIES
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictAddressFamilies/' $@
+endif
+if !HAVE_SYSTEMD_RESTRICT_NAMESPACES
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictNamespaces/' $@
+endif
+if !HAVE_SYSTEMD_RESTRICT_REALTIME
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictRealtime/' $@
+endif
+if !HAVE_SYSTEMD_RESTRICT_SUIDSGID
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictSUIDSGID/' $@
+endif
+if !HAVE_SYSTEMD_SYSTEM_CALL_ARCHITECTURES
+       $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallArchitectures/' $@
+endif
+if !HAVE_SYSTEMD_SYSTEM_CALL_FILTER
+       $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallFilter/' $@
+endif
 
 ixfrdist@.service: ixfrdist.service
        $(AM_V_GEN)sed -e 's!/ixfrdist!& --config $(sysconfdir)/ixfrdist-%i.yml!' \
index c33575a23d87ba702a93ee29a6fac8387ed0f1be..4fd75bb74a9624987200346d3abf8876d4b294f3 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include "auth-caches.hh"
 #include "auth-querycache.hh"
 #include "auth-packetcache.hh"
 
index 09f776aa7c943d62cd5fcd8c90330efa497bb330..3f839d2b6bd2a7c907264de4291b7b4c11b83635 100644 (file)
@@ -40,14 +40,13 @@ try
 
   string namespace_name=arg()["carbon-namespace"];
   string hostname=arg()["carbon-ourname"];
-  if(hostname.empty()) {
-    char tmp[80];
-    memset(tmp, 0, sizeof(tmp));
-    gethostname(tmp, sizeof(tmp));
-    char *p = strchr(tmp, '.');
-    if(p) *p=0;
-    hostname=tmp;
-    boost::replace_all(hostname, ".", "_");
+  if (hostname.empty()) {
+    try {
+      hostname = getCarbonHostName();
+    }
+    catch(const std::exception& e) {
+      throw std::runtime_error(std::string("The 'carbon-ourname' setting has not been set and we are unable to determine the system's hostname: ") + e.what());
+    }
   }
   string instance_name=arg()["carbon-instance"];
 
index 71dbf134d50abdddf88086eb070266400d712b8a..4ed8112fbda5ed178a0b68276224776e697a16e3 100644 (file)
@@ -34,7 +34,7 @@ AuthPacketCache::AuthPacketCache(size_t mapsCount): d_maps(mapsCount), d_lastcle
 {
   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("packetcache-size", "Number of entries in the packet cache", StatType::gauge);
   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");
 
@@ -46,13 +46,11 @@ AuthPacketCache::AuthPacketCache(size_t mapsCount): d_maps(mapsCount), d_lastcle
 AuthPacketCache::~AuthPacketCache()
 {
   try {
-    vector<WriteLock*> locks;
+    vector<WriteLock> locks;
     for(auto& mc : d_maps) {
-      locks.push_back(new WriteLock(&mc.d_mut));
-    }
-    for(auto wl : locks) {
-      delete wl;
+      locks.push_back(WriteLock(mc.d_mut));
     }
+    locks.clear();
   }
   catch(...) {
   }
@@ -74,7 +72,7 @@ bool AuthPacketCache::get(DNSPacket& p, DNSPacket& cached)
 
   cleanupIfNeeded();
 
-  uint32_t hash = canHashPacket(p.getString());
+  uint32_t hash = canHashPacket(p.getString(), /* don't skip ECS */ false);
   p.setHash(hash);
 
   string value;
@@ -110,7 +108,8 @@ bool AuthPacketCache::get(DNSPacket& p, DNSPacket& cached)
 
 bool AuthPacketCache::entryMatches(cmap_t::index<HashTag>::type::iterator& iter, const std::string& query, const DNSName& qname, uint16_t qtype, bool tcp)
 {
-  return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname);
+  static const std::unordered_set<uint16_t> skippedEDNSTypes{ EDNSOptionCode::COOKIE };
+  return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname, skippedEDNSTypes);
 }
 
 void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
index 0d3af47a251ef2cc7f85373a86001b424178f8e6..bceab073de59797980e75056d3bea546d68d5e41 100644 (file)
@@ -108,17 +108,15 @@ private:
   struct MapCombo
   {
     MapCombo() {
-      pthread_rwlock_init(&d_mut, nullptr);
     }
     ~MapCombo() {
-      pthread_rwlock_destroy(&d_mut);
     }
     MapCombo(const MapCombo&) = delete; 
     MapCombo& operator=(const MapCombo&) = delete;
 
     void reserve(size_t numberOfEntries);
 
-    pthread_rwlock_t d_mut;
+    ReadWriteLock d_mut;
     cmap_t d_map;
   };
 
index d4970bd137a8cd7d6851afebb295861ca00327a0..82489e1e9214a4fafa33fdf1941d8221d1e3d491 100644 (file)
@@ -35,7 +35,7 @@ AuthQueryCache::AuthQueryCache(size_t mapsCount): d_maps(mapsCount), d_lastclean
 {
   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("query-cache-size", "Number of entries in the query cache", StatType::gauge);
   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");
 
@@ -47,13 +47,11 @@ AuthQueryCache::AuthQueryCache(size_t mapsCount): d_maps(mapsCount), d_lastclean
 AuthQueryCache::~AuthQueryCache()
 {
   try {
-    vector<WriteLock*> locks;
+    vector<WriteLock> locks;
     for(auto& mc : d_maps) {
-      locks.push_back(new WriteLock(&mc.d_mut));
-    }
-    for(auto wl : locks) {
-      delete wl;
+      locks.push_back(WriteLock(mc.d_mut));
     }
+    locks.clear();
   }
   catch(...) {
   }
index c62230b2280f73f2984f0e9fd5e7d5e09bd7ffd9..f0c477e2f08ce59d0ab55d4c2457658f06399d26 100644 (file)
@@ -91,17 +91,15 @@ private:
   struct MapCombo
   {
     MapCombo() {
-      pthread_rwlock_init(&d_mut, nullptr);
     }
     ~MapCombo() {
-      pthread_rwlock_destroy(&d_mut);
     }
     MapCombo(const MapCombo &) = delete; 
     MapCombo & operator=(const MapCombo &) = delete;
 
     void reserve(size_t numberOfEntries);
 
-    pthread_rwlock_t d_mut;
+    ReadWriteLock d_mut;
     cmap_t d_map;
   };
 
diff --git a/pdns/axfr-retriever.cc b/pdns/axfr-retriever.cc
new file mode 100644 (file)
index 0000000..4d2718e
--- /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.
+ */
+
+#include "axfr-retriever.hh"
+#include "arguments.hh"
+#include "dns_random.hh"
+#include "utility.hh"
+#include "resolver.hh"
+#include "query-local-address.hh"
+
+using pdns::resolver::parseResult;
+
+AXFRRetriever::AXFRRetriever(const ComboAddress& remote,
+                             const DNSName& domain,
+                             const TSIGTriplet& tt, 
+                             const ComboAddress* laddr,
+                             size_t maxReceivedBytes,
+                             uint16_t timeout)
+  : d_tsigVerifier(tt, remote, d_trc), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes)
+{
+  ComboAddress local;
+  if (laddr != nullptr) {
+    local = ComboAddress(*laddr);
+  } else {
+    if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
+      throw ResolverException("Unable to determine source address for AXFR request to " + remote.toStringWithPort() + " for " + domain.toLogString() + ". Address family is not configured for outgoing queries");
+    }
+    local = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
+  }
+  d_sock = -1;
+  try {
+    d_sock = makeQuerySocket(local, false); // make a TCP socket
+    if (d_sock < 0)
+      throw ResolverException("Error creating socket for AXFR request to "+d_remote.toStringWithPort());
+    d_buf = shared_array<char>(new char[65536]);
+    d_remote = remote; // mostly for error reporting
+    this->connect(timeout);
+    d_soacount = 0;
+  
+    vector<uint8_t> packet;
+    DNSPacketWriter pw(packet, domain, QType::AXFR);
+    pw.getHeader()->id = dns_random_uint16();
+  
+    if(!tt.name.empty()) {
+      if (tt.algo == DNSName("hmac-md5"))
+        d_trc.d_algoName = tt.algo + DNSName("sig-alg.reg.int");
+      else
+        d_trc.d_algoName = tt.algo;
+      d_trc.d_time = time(0);
+      d_trc.d_fudge = 300;
+      d_trc.d_origID=ntohs(pw.getHeader()->id);
+      d_trc.d_eRcode=0;
+      addTSIG(pw, d_trc, tt.name, tt.secret, "", false);
+    }
+  
+    uint16_t replen=htons(packet.size());
+    Utility::iovec iov[2];
+    iov[0].iov_base=reinterpret_cast<char*>(&replen);
+    iov[0].iov_len=2;
+    iov[1].iov_base=packet.data();
+    iov[1].iov_len=packet.size();
+  
+    int ret=Utility::writev(d_sock, iov, 2);
+    if(ret < 0)
+      throw ResolverException("Error sending question to "+d_remote.toStringWithPort()+": "+stringerror());
+    if(ret != (int)(2+packet.size())) {
+      throw ResolverException("Partial write on AXFR request to "+d_remote.toStringWithPort());
+    }
+  
+    int res = waitForData(d_sock, timeout, 0);
+    
+    if(!res)
+      throw ResolverException("Timeout waiting for answer from "+d_remote.toStringWithPort()+" during AXFR");
+    if(res<0)
+      throw ResolverException("Error waiting for answer from "+d_remote.toStringWithPort()+": "+stringerror());
+  }
+  catch(...) {
+    if(d_sock >= 0)
+      close(d_sock);
+    d_sock = -1;
+    throw;
+  }
+}
+
+AXFRRetriever::~AXFRRetriever()
+{
+  close(d_sock);
+}
+
+
+
+int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records, uint16_t timeout) // Implementation is making sure RFC2845 4.4 is followed.
+{
+  if(d_soacount > 1)
+    return false;
+
+  // d_sock is connected and is about to spit out a packet
+  int len=getLength(timeout);
+  if(len<0)
+    throw ResolverException("EOF trying to read axfr chunk from remote TCP client");
+
+  if (d_maxReceivedBytes > 0 && (d_maxReceivedBytes - d_receivedBytes) < (size_t) len)
+    throw ResolverException("Reached the maximum number of received bytes during AXFR");
+
+  timeoutReadn(len, timeout);
+
+  d_receivedBytes += (uint16_t) len;
+
+  MOADNSParser mdp(false, d_buf.get(), len);
+
+  int err = mdp.d_header.rcode;
+
+  if(err) {
+    throw ResolverException("AXFR chunk error: " + RCode::to_s(err));
+  }
+
+  try {
+    d_tsigVerifier.check(std::string(d_buf.get(), len), mdp);
+  }
+  catch(const std::runtime_error& re) {
+    throw ResolverException(re.what());
+  }
+
+  if(!records) {
+    err = parseResult(mdp, DNSName(), 0, 0, &res);
+
+    if (!err) {
+      for(const auto& answer :  mdp.d_answers)
+        if (answer.first.d_type == QType::SOA)
+          d_soacount++;
+    }
+  }
+  else {
+    records->clear();
+    records->reserve(mdp.d_answers.size());
+
+    for(auto& r: mdp.d_answers) {
+      if (r.first.d_type == QType::SOA) {
+        d_soacount++;
+      }
+
+      records->push_back(std::move(r.first));
+    }
+  }
+
+  return true;
+}
+
+void AXFRRetriever::timeoutReadn(uint16_t bytes, uint16_t timeoutsec)
+{
+  time_t start=time(nullptr);
+  int n=0;
+  int numread;
+  while(n<bytes) {
+    int res=waitForData(d_sock, timeoutsec-(time(nullptr)-start));
+    if(res<0)
+      throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror());
+    if(!res)
+      throw ResolverException("Timeout while reading data from remote nameserver over TCP");
+
+    numread=recv(d_sock, d_buf.get()+n, bytes-n, 0);
+    if(numread<0)
+      throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror());
+    if(numread==0)
+      throw ResolverException("Remote nameserver closed TCP connection");
+    n+=numread;
+  }
+}
+
+void AXFRRetriever::connect(uint16_t timeout)
+{
+  setNonBlocking( d_sock );
+
+  int err;
+
+  if((err=::connect(d_sock,(struct sockaddr*)&d_remote, d_remote.getSocklen()))<0 && errno!=EINPROGRESS) {
+    try {
+      closesocket(d_sock);
+    }
+    catch(const PDNSException& e) {
+      d_sock=-1;
+      throw ResolverException("Error closing AXFR socket after connect() failed: "+e.reason);
+    }
+
+    throw ResolverException("connect: "+stringerror());
+  }
+
+  if(!err)
+    goto done;
+
+  err=waitForRWData(d_sock, false, timeout, 0); // wait for writeability
+  
+  if(!err) {
+    try {
+      closesocket(d_sock); // timeout
+    }
+    catch(const PDNSException& e) {
+      d_sock=-1;
+      throw ResolverException("Error closing AXFR socket after timeout: "+e.reason);
+    }
+
+    d_sock=-1;
+    errno=ETIMEDOUT;
+    
+    throw ResolverException("Timeout connecting to server");
+  }
+  else if(err < 0) {
+    throw ResolverException("Error connecting: "+stringerror());
+  }
+  else {
+    Utility::socklen_t len=sizeof(err);
+    if(getsockopt(d_sock, SOL_SOCKET,SO_ERROR,(char *)&err,&len)<0)
+      throw ResolverException("Error connecting: "+stringerror()); // Solaris
+
+    if(err)
+      throw ResolverException("Error connecting: "+string(strerror(err)));
+  }
+  
+ done:
+  setBlocking( d_sock );
+  // d_sock now connected
+}
+
+int AXFRRetriever::getLength(uint16_t timeout)
+{
+  timeoutReadn(2, timeout);
+  return (unsigned char)d_buf[0]*256+(unsigned char)d_buf[1];
+}
+
diff --git a/pdns/axfr-retriever.hh b/pdns/axfr-retriever.hh
new file mode 100644 (file)
index 0000000..63f3324
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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/utility.hpp>
+
+#include "iputils.hh"
+#include "dnsname.hh"
+#include "resolver.hh"
+
+class AXFRRetriever : public boost::noncopyable
+{
+  public:
+    AXFRRetriever(const ComboAddress& remote,
+                  const DNSName& zone,
+                  const TSIGTriplet& tt = TSIGTriplet(),
+                  const ComboAddress* laddr = NULL,
+                  size_t maxReceivedBytes=0,
+                  uint16_t timeout=10);
+    ~AXFRRetriever();
+    int getChunk(Resolver::res_t &res, vector<DNSRecord>* records=0, uint16_t timeout=10);
+
+  private:
+    void connect(uint16_t timeout);
+    int getLength(uint16_t timeout);
+    void timeoutReadn(uint16_t bytes, uint16_t timeoutsec=10);
+
+    shared_array<char> d_buf;
+    string d_domain;
+    int d_sock;
+    int d_soacount;
+    ComboAddress d_remote;
+    TSIGTCPVerifier d_tsigVerifier;
+
+    size_t d_receivedBytes;
+    size_t d_maxReceivedBytes;
+    TSIGRecordContent d_trc;
+};
index 3687c61a5a04223ad846f32b98280b0c626a30e5..3f9372fd253de4c5054fe04c68c55fcdceaef399 100644 (file)
@@ -41,7 +41,7 @@
 GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
 {
   setArgPrefix(mode+suffix);
-  d_db=0;
+  d_db = nullptr;
   d_logprefix="["+mode+"Backend"+suffix+"] ";
 
   try
@@ -65,6 +65,7 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   d_InfoOfAllSlaveDomainsQuery=getArg("info-all-slaves-query");
   d_SuperMasterInfoQuery=getArg("supermaster-query");
   d_GetSuperMasterIPs=getArg("supermaster-name-to-ips");
+  d_AddSuperMaster=getArg("supermaster-add"); 
   d_InsertZoneQuery=getArg("insert-zone-query");
   d_InsertRecordQuery=getArg("insert-record-query");
   d_UpdateMasterOfZoneQuery=getArg("update-master-query");
@@ -134,6 +135,7 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   d_InfoOfAllSlaveDomainsQuery_stmt = NULL;
   d_SuperMasterInfoQuery_stmt = NULL;
   d_GetSuperMasterIPs_stmt = NULL;
+  d_AddSuperMaster_stmt = NULL;
   d_InsertZoneQuery_stmt = NULL;
   d_InsertRecordQuery_stmt = NULL;
   d_InsertEmptyNonTerminalOrderQuery_stmt = NULL;
@@ -216,19 +218,26 @@ void GSQLBackend::setFresh(uint32_t domain_id)
   }
 }
 
-bool GSQLBackend::setMaster(const DNSName &domain, const string &ip)
+bool GSQLBackend::setMasters(const DNSName &domain, const vector<ComboAddress> &masters)
 {
+  vector<string> masters_s;
+  for (const auto& master : masters) {
+    masters_s.push_back(master.toStringWithPortExcept(53));
+  }
+
+  auto tmp = boost::join(masters_s, ", ");
+
   try {
     reconnectIfNeeded();
 
     d_UpdateMasterOfZoneQuery_stmt->
-      bind("master", ip)->
+      bind("master", tmp)->
       bind("domain", domain)->
       execute()->
       reset();
   }
   catch (SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to set master of domain '"+domain.toLogString()+"' to IP address " + ip + ": "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to set masters of domain '"+domain.toLogString()+"' to " + tmp + ": "+e.txtReason());
   }
   return true;
 }
@@ -408,11 +417,11 @@ void GSQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
       }
     }
     catch(const std::exception& exp) {
-      g_log<<Logger::Warning<<"Error while parsing SOA data for slave zone '"<<slave.zone.toLogString()<<"': "<<exp.what()<<endl;
+      g_log<<Logger::Warning<<"Error while parsing SOA data for slave zone '"<<slave.zone<<"': "<<exp.what()<<endl;
       continue;
     }
     catch(...) {
-      g_log<<Logger::Warning<<"Error while parsing SOA data for slave zone '"<<slave.zone.toLogString()<<"', skipping"<<endl;
+      g_log<<Logger::Warning<<"Error while parsing SOA data for slave zone '"<<slave.zone<<"', skipping"<<endl;
       continue;
     }
   }
@@ -1206,6 +1215,26 @@ skiprow:
   return false;
 }
 
+bool GSQLBackend::superMasterAdd(const string &ip, const string &nameserver, const string &account)
+{
+  try{
+    reconnectIfNeeded();
+
+    d_AddSuperMaster_stmt -> 
+      bind("ip",ip)->
+      bind("nameserver",nameserver)->
+      bind("account",account)->
+      execute()->
+      reset();
+
+  }
+  catch (SSqlException &e){
+    throw PDNSException("GSQLBackend unable to insert a supermaster with IP " + ip + " and nameserver name '" + nameserver + "' and account '" + account + "': " + e.txtReason()); 
+  }
+  return true;
+
+}
+
 bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **ddb)
 {
   // check if we know the ip/ns couple in the database
@@ -1234,15 +1263,20 @@ bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, co
   return false;
 }
 
-bool GSQLBackend::createDomain(const DNSName &domain, const string &type, const string &masters, const string &account)
+bool GSQLBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account)
 {
+  vector<string> masters_s;
+  for (const auto& master : masters) {
+    masters_s.push_back(master.toStringWithPortExcept(53));
+  }
+
   try {
     reconnectIfNeeded();
 
     d_InsertZoneQuery_stmt->
-      bind("type", type)->
+      bind("type", toUpper(DomainInfo::getKindString(kind)))->
       bind("domain", domain)->
-      bind("masters", masters)->
+      bind("masters", boost::join(masters_s, ", "))->
       bind("account", account)->
       execute()->
       reset();
@@ -1256,7 +1290,7 @@ bool GSQLBackend::createDomain(const DNSName &domain, const string &type, const
 bool GSQLBackend::createSlaveDomain(const string &ip, const DNSName &domain, const string &nameserver, const string &account)
 {
   string name;
-  string masters(ip);
+  vector<ComboAddress> masters({ComboAddress(ip, 53)});
   try {
     if (!nameserver.empty()) {
       // figure out all IP addresses for the master
@@ -1270,16 +1304,16 @@ bool GSQLBackend::createSlaveDomain(const string &ip, const DNSName &domain, con
         reset();
       if (!d_result.empty()) {
         // collect all IP addresses
-        vector<string> tmp;
+        vector<ComboAddress> tmp;
         for(const auto& row: d_result) {
           if (account == row[1])
-            tmp.push_back(row[0]);
+            tmp.emplace_back(row[0], 53);
         }
         // set them as domain's masters, comma separated
-        masters = boost::join(tmp, ", ");
+        masters = tmp;
       }
     }
-    createDomain(domain, "SLAVE", masters, account);
+    createDomain(domain, DomainInfo::Slave, masters, account);
   }
   catch(SSqlException &e) {
     throw PDNSException("Database error trying to insert new slave domain '"+domain.toLogString()+"': "+ e.txtReason());
index a98b3a83805841dfb91ffc3a42004a3299c9b3f3..563082f92dce273fcfbf795c101cadd9a4084f9b 100644 (file)
@@ -48,11 +48,11 @@ public:
     d_db=std::unique_ptr<SSql>(db);
     if (d_db) {
       d_db->setLog(::arg().mustDo("query-logging"));
-      allocateStatements();
     }
   }
 
-  void allocateStatements()
+protected:
+  virtual void allocateStatements()
   {
     if (d_db) {
       d_NoIdQuery_stmt = d_db->prepare(d_NoIdQuery, 2);
@@ -66,6 +66,7 @@ public:
       d_InfoOfAllSlaveDomainsQuery_stmt = d_db->prepare(d_InfoOfAllSlaveDomainsQuery, 0);
       d_SuperMasterInfoQuery_stmt = d_db->prepare(d_SuperMasterInfoQuery, 2);
       d_GetSuperMasterIPs_stmt = d_db->prepare(d_GetSuperMasterIPs, 2);
+      d_AddSuperMaster_stmt = d_db->prepare(d_AddSuperMaster, 3); 
       d_InsertZoneQuery_stmt = d_db->prepare(d_InsertZoneQuery, 4);
       d_InsertRecordQuery_stmt = d_db->prepare(d_InsertRecordQuery, 9);
       d_InsertEmptyNonTerminalOrderQuery_stmt = d_db->prepare(d_InsertEmptyNonTerminalOrderQuery, 4);
@@ -117,7 +118,7 @@ public:
     }
   }
 
-  void freeStatements() {
+  virtual void freeStatements() {
     d_NoIdQuery_stmt.reset();
     d_IdQuery_stmt.reset();
     d_ANYNoIdQuery_stmt.reset();
@@ -129,6 +130,7 @@ public:
     d_InfoOfAllSlaveDomainsQuery_stmt.reset();
     d_SuperMasterInfoQuery_stmt.reset();
     d_GetSuperMasterIPs_stmt.reset();
+    d_AddSuperMaster_stmt.reset();
     d_InsertZoneQuery_stmt.reset();
     d_InsertRecordQuery_stmt.reset();
     d_InsertEmptyNonTerminalOrderQuery_stmt.reset();
@@ -179,6 +181,7 @@ public:
     d_SearchCommentsQuery_stmt.reset();
   }
 
+public:
   void lookup(const QType &, const DNSName &qdomain, int zoneId, DNSPacket *p=nullptr) override;
   bool list(const DNSName &target, int domain_id, bool include_disabled=false) override;
   bool get(DNSResourceRecord &r) override;
@@ -190,18 +193,17 @@ public:
   bool feedRecord(const DNSResourceRecord &r, const DNSName &ordername, bool ordernameIsNSEC3=false) override;
   bool feedEnts(int domain_id, map<DNSName,bool>& nonterm) override;
   bool feedEnts3(int domain_id, const DNSName &domain, map<DNSName,bool> &nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow) override;
-  bool createDomain(const DNSName &domain) override {
-    return createDomain(domain, "NATIVE", "", "");
-  };
+  bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account) override;
   bool createSlaveDomain(const string &ip, const DNSName &domain, const string &nameserver, const string &account) override;
   bool deleteDomain(const DNSName &domain) override;
+  bool superMasterAdd(const string &ip, const string &nameserver, const string &account) override; 
   bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db) override;
   void setFresh(uint32_t domain_id) override;
   void getUnfreshSlaveInfos(vector<DomainInfo> *domains) override;
   void getUpdatedMasters(vector<DomainInfo> *updatedDomains) override;
   bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true) override;
   void setNotified(uint32_t domain_id, uint32_t serial) override;
-  bool setMaster(const DNSName &domain, const string &ip) override;
+  bool setMasters(const DNSName &domain, const vector<ComboAddress> &masters) override;
   bool setKind(const DNSName &domain, const DomainInfo::DomainKind kind) override;
   bool setAccount(const DNSName &domain, const string &account) override;
 
@@ -239,7 +241,6 @@ public:
   bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
 
 protected:
-  bool createDomain(const DNSName &domain, const string &type, const string &masters, const string &account);
   string pattern2SQLPattern(const string& pattern);
   void extractRecord(SSqlStatement::row_t& row, DNSResourceRecord& rr);
   void extractComment(SSqlStatement::row_t& row, Comment& c);
@@ -263,11 +264,12 @@ protected:
     return d_inTransaction;
   }
 
-private:
   string d_query_name;
   DNSName d_qname;
   SSqlStatement::result_t d_result;
+  unique_ptr<SSqlStatement>* d_query_stmt;
 
+private:
   string d_NoIdQuery;
   string d_IdQuery;
   string d_ANYNoIdQuery;
@@ -283,6 +285,7 @@ private:
   string d_SuperMasterInfoQuery;
   string d_GetSuperMasterName;
   string d_GetSuperMasterIPs;
+  string d_AddSuperMaster;
 
   string d_InsertZoneQuery;
   string d_InsertRecordQuery;
@@ -342,7 +345,6 @@ private:
   string d_SearchRecordsQuery;
   string d_SearchCommentsQuery;
 
-  unique_ptr<SSqlStatement>* d_query_stmt;
 
   unique_ptr<SSqlStatement> d_NoIdQuery_stmt;
   unique_ptr<SSqlStatement> d_IdQuery_stmt;
@@ -355,6 +357,7 @@ private:
   unique_ptr<SSqlStatement> d_InfoOfAllSlaveDomainsQuery_stmt;
   unique_ptr<SSqlStatement> d_SuperMasterInfoQuery_stmt;
   unique_ptr<SSqlStatement> d_GetSuperMasterIPs_stmt;
+  unique_ptr<SSqlStatement> d_AddSuperMaster_stmt;
   unique_ptr<SSqlStatement> d_InsertZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_InsertRecordQuery_stmt;
   unique_ptr<SSqlStatement> d_InsertEmptyNonTerminalOrderQuery_stmt;
index 16d9d4bab44469bc2b44e3b0fb0eff99edcd3053..b07ed50cee6f4d7a1595395f60a547318b991ee7 100644 (file)
@@ -33,7 +33,7 @@
 
 /* based on freebsd:src/contrib/opie/libopie/btoe.c extract: get bit ranges from a char* */
 /* NOTE: length should not exceed 8; all callers inside PowerDNS only pass length=5 though */
-unsigned char extract_bits(const char *s, int start, int length)
+static unsigned char extract_bits(const char *s, int start, int length)
 {
   uint16_t x;
   unsigned char cl, cc;
index d334fecdfccc953866dfefc967c25178e2b19e11..f21fcfaec74d58edc3c969ad43033a8df627177a 100644 (file)
@@ -1 +1,16 @@
-ALTER TABLE cryptokeys ADD published BOOL DEFAULT 1;
+BEGIN TRANSACTION;
+  CREATE TABLE cryptokeys_temp (
+    id                  INTEGER PRIMARY KEY,
+    domain              VARCHAR(255) COLLATE NOCASE,
+    flags               INT NOT NULL,
+    active              BOOL,
+    published           BOOL DEFAULT 1,
+    content             TEXT
+  );
+
+  INSERT INTO cryptokeys_temp SELECT id,domain,flags,active,1,content FROM cryptokeys;
+  DROP TABLE cryptokeys;
+  ALTER TABLE cryptokeys_temp RENAME TO cryptokeys;
+
+  CREATE INDEX domainnameindex ON cryptokeys(domain);
+COMMIT;
index d9046bc55c35a9b36b39a6cc85f6791d486b2c6a..afef620088d4f0ca4e3d71c18d1ebd605918650b 100644 (file)
@@ -12,7 +12,7 @@ create table cryptokeys (
  domain     VARCHAR(255) COLLATE NOCASE,
  flags      INT NOT NULL,
  active     BOOL,
- published  BOOL,
+ published  BOOL DEFAULT 1,
  content    TEXT
 );
 
index df05341467d5178de4a6e22d201d3360b77e1ef1..4557c832a1de157865f12e683ac7317eb6cbfce1 100644 (file)
@@ -22,7 +22,8 @@ extern "C"
        int yyparse(void);
        int yylex(void);
        void yyrestart(FILE *);
-       int yywrap()
+       int yywrap(void);
+       int yywrap(void)
        {
                return 1;
        }
index 18e5ea147494a65d3ec505ff993aab50e1276d58..a06a35e0865d5e59161262d8eecf93e1915929ca 100644 (file)
 // this function can clean any cache that has a getTTD() method on its entries, a preRemoval() method 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
 // on a miss, move it to the beginning
-template <typename S, typename C, typename T> void pruneCollection(C& container, T& collection, unsigned int maxCached, unsigned int scanFraction=1000)
+template <typename S, typename C, typename T> void pruneCollection(C& container, T& collection, size_t maxCached, size_t scanFraction = 1000)
 {
-  time_t now=time(0);
-  unsigned int toTrim=0;
-  
-  unsigned int cacheSize=collection.size();
+  const time_t now = time(0);
+  size_t toTrim = 0;
+  const size_t cacheSize = collection.size();
 
-  if(cacheSize > maxCached) {
+  if (cacheSize > maxCached) {
     toTrim = cacheSize - maxCached;
   }
 
-//  cout<<"Need to trim "<<toTrim<<" from cache to meet target!\n";
-
-  typedef typename T::template index<S>::type sequence_t;
-  sequence_t& sidx=collection.template get<S>();
-
-  unsigned int tried=0, lookAt, erased=0;
+  auto& sidx = collection.template get<S>();
 
   // two modes - if toTrim is 0, just look through 1/scanFraction of all records 
   // and nuke everything that is expired
   // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
-  if(toTrim)
-    lookAt=5*toTrim;
-  else
-    lookAt=cacheSize/scanFraction;
+  const size_t lookAt = toTrim ? 5 * toTrim : cacheSize / scanFraction;
+  size_t tried = 0, erased = 0;
 
-  typename sequence_t::iterator iter=sidx.begin(), eiter;
-  for(; iter != sidx.end() && tried < lookAt ; ++tried) {
-    if(iter->getTTD() < now) {
+  for (auto iter = sidx.begin(); iter != sidx.end() && tried < lookAt ; ++tried) {
+    if (iter->getTTD() < now) {
       container.preRemoval(*iter);
       iter = sidx.erase(iter);
       erased++;
     }
-    else
+    else {
       ++iter;
+    }
 
-    if(toTrim && erased >= toTrim)
+    if (toTrim && erased >= toTrim) {
       break;
+    }
   }
 
-  //cout<<"erased "<<erased<<" records based on ttd\n";
-  
-  if(erased >= toTrim) // done
+  if (erased >= toTrim) { // done
     return;
+  }
 
   toTrim -= erased;
 
-  //if(toTrim)
-    // cout<<"Still have "<<toTrim - erased<<" entries left to erase to meet target\n"; 
-
-  eiter=iter=sidx.begin();
-  std::advance(eiter, toTrim);
   // just lob it off from the beginning
-  for (auto i = iter; ; ) {
-    if (i == eiter) {
-      break;
-    }
-
-    container.preRemoval(*i);
-    sidx.erase(i++);
+  auto iter = sidx.begin();
+  for (size_t i = 0; i < toTrim && iter != sidx.end(); i++) {
+    container.preRemoval(*iter);
+    iter = sidx.erase(iter);
   }
 }
 
@@ -160,7 +144,7 @@ template <typename S, typename C, typename T> uint64_t pruneMutexCollectionsVect
 
   for (auto& mc : maps) {
     const typename C::lock l(mc);
-    mc.d_cachecachevalid = false;
+    mc.invalidate();
     auto& sidx = boost::multi_index::get<S>(mc.d_map);
     uint64_t erased = 0, lookedAt = 0;
     for (auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
@@ -190,11 +174,11 @@ template <typename S, typename C, typename T> uint64_t pruneMutexCollectionsVect
 
   toTrim -= totErased;
 
-  while (toTrim > 0) {
+    while (true) {
     size_t pershard = toTrim / maps_size + 1;
     for (auto& mc : maps) {
       const typename C::lock l(mc);
-      mc.d_cachecachevalid = false;
+      mc.invalidate();
       auto& sidx = boost::multi_index::get<S>(mc.d_map);
       size_t removed = 0;
       for (auto i = sidx.begin(); i != sidx.end() && removed < pershard; removed++) {
@@ -204,11 +188,12 @@ template <typename S, typename C, typename T> uint64_t pruneMutexCollectionsVect
         totErased++;
         toTrim--;
         if (toTrim == 0) {
-          break;
+          return totErased;
         }
       }
     }
   }
+  // Not reached
   return totErased;
 }
 
@@ -264,13 +249,13 @@ template <typename N, typename T> uint64_t purgeExactLockedCollection(T& mc, con
 }
 
 template<typename S, typename Index>
-std::pair<typename Index::iterator,bool>
-lruReplacingInsert(Index& i,const typename Index::value_type& x)
+bool lruReplacingInsert(Index& i, const typename Index::value_type& x)
 {
-  std::pair<typename Index::iterator,bool> res = i.insert(x);
-  if (!res.second) {
-    moveCacheItemToBack<S>(i, res.first);
-    res.second = i.replace(res.first, x);
+  auto inserted = i.insert(x);
+  if (!inserted.second) {
+    moveCacheItemToBack<S>(i, inserted.first);
+    i.replace(inserted.first, x);
+    return false;
   }
-  return res;
+  return true;
 }
index 39796e5f715ba3a63f8f00290b1bb2d4add0cd7a..d07ac19f4ad440a2b5b942c2f500000d2debd967 100644 (file)
@@ -408,7 +408,8 @@ try
     }
     unknown.emplace_back(std::make_shared<vector<uint8_t>>(packet));
   }
-  random_shuffle(unknown.begin(), unknown.end());
+
+  shuffle(unknown.begin(), unknown.end(), pdns::dns_random_engine());
   if (!g_quiet) {
     cout<<"Generated "<<unknown.size()<<" ready to use queries"<<endl;
   }
@@ -473,7 +474,8 @@ try
     for(;n < total; ++n) {
       toSend.push_back(known[dns_random(known.size())].get());
     }
-    random_shuffle(toSend.begin(), toSend.end());
+
+    shuffle(toSend.begin(), toSend.end(), pdns::dns_random_engine());
     g_recvcounter.store(0);
     g_recvbytes=0;
     DTime dt;
index da3c8e337153e386f30219efe87c24e6bdd674d0..05b0cca5716b54b764c48faabeeeb6187ecb43fc 100644 (file)
@@ -31,6 +31,7 @@
 #include "dnsseckeeper.hh"
 #include "threadname.hh"
 #include "misc.hh"
+#include "query-local-address.hh"
 
 #include <thread>
 
@@ -70,7 +71,16 @@ void declareArguments()
 {
   ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=SYSCONFDIR;
   ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
-  ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns when unset and not chrooted" )="";
+  ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns when unset and not chrooted"
+#ifdef HAVE_SYSTEMD
+      + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")="";
+   auto runtimeDir = getenv("RUNTIME_DIRECTORY");
+   if (runtimeDir != nullptr) {
+     ::arg().set("socket-dir") = runtimeDir;
+   }
+#else
+      )="";
+#endif
   ::arg().set("module-dir","Default directory for modules")=PKGLIBDIR;
   ::arg().set("chroot","If set, chroot to this directory for more security")="";
   ::arg().set("logging-facility","Log under a specific facility")="";
@@ -91,8 +101,8 @@ void declareArguments()
   ::arg().setSwitch("local-address-nonexist-fail","Fail to start if one or more of the local-address's do not exist on this server")="yes";
   ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options")="no";
   ::arg().setSwitch("reuseport","Enable higher performance on compliant kernels by using SO_REUSEPORT allowing each receiver thread to open its own socket")="no";
-  ::arg().set("query-local-address","Source IP address for sending queries")="0.0.0.0";
-  ::arg().set("query-local-address6","Source IPv6 address for sending queries")="::";
+  ::arg().set("query-local-address","Source IP addresses for sending queries")="0.0.0.0 ::";
+  ::arg().set("query-local-address6","DEPRECATED: Use query-local-address. Source IPv6 address for sending queries")="";
   ::arg().set("overload-queue-length","Maximum queuelength moving to packetcache only")="0";
   ::arg().set("max-queue-length","Maximum queuelength before considering situation lost")="5000";
 
@@ -156,7 +166,6 @@ void declareArguments()
   ::arg().set("webserver-loglevel", "Amount of logging in the webserver (none, normal, detailed)") = "normal";
   ::arg().set("webserver-max-bodysize","Webserver/API maximum request/response body size in megabytes")="2";
 
-  ::arg().setSwitch("do-ipv6-additional-processing", "Do AAAA additional processing")="yes";
   ::arg().setSwitch("query-logging","Hint backends that queries should be logged")="no";
 
   ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string")="pdns";
@@ -323,9 +332,9 @@ void declareStats(void)
   S.declare("tcp6-queries","Number of IPv6 TCP queries received");
   S.declare("tcp6-answers","Number of IPv6 answers sent out over TCP");
 
-  S.declare("open-tcp-connections","Number of currently open TCP connections", getTCPConnectionCount);;
+  S.declare("open-tcp-connections","Number of currently open TCP connections", getTCPConnectionCount, StatType::gauge);
 
-  S.declare("qsize-q","Number of questions waiting for database attention", getQCount);
+  S.declare("qsize-q","Number of questions waiting for database attention", getQCount, StatType::gauge);
 
   S.declare("dnsupdate-queries", "DNS update packets received.");
   S.declare("dnsupdate-answers", "DNS update packets successfully answered.");
@@ -334,33 +343,33 @@ void declareStats(void)
 
   S.declare("incoming-notifications", "NOTIFY packets received.");
 
-  S.declare("uptime", "Uptime of process in seconds", uptimeOfProcess);
-  S.declare("real-memory-usage", "Actual unique use of memory in bytes (approx)", getRealMemoryUsage);
-  S.declare("special-memory-usage", "Actual unique use of memory in bytes (approx)", getSpecialMemoryUsage);
-  S.declare("fd-usage", "Number of open filedescriptors", getOpenFileDescriptors);
+  S.declare("uptime", "Uptime of process in seconds", uptimeOfProcess, StatType::counter);
+  S.declare("real-memory-usage", "Actual unique use of memory in bytes (approx)", getRealMemoryUsage, StatType::gauge);
+  S.declare("special-memory-usage", "Actual unique use of memory in bytes (approx)", getSpecialMemoryUsage, StatType::gauge);
+  S.declare("fd-usage", "Number of open filedescriptors", getOpenFileDescriptors, StatType::gauge);
 #ifdef __linux__
-  S.declare("udp-recvbuf-errors", "UDP 'recvbuf' errors", udpErrorStats);
-  S.declare("udp-sndbuf-errors", "UDP 'sndbuf' errors", udpErrorStats);
-  S.declare("udp-noport-errors", "UDP 'noport' errors", udpErrorStats);
-  S.declare("udp-in-errors", "UDP 'in' errors", udpErrorStats);
+  S.declare("udp-recvbuf-errors", "UDP 'recvbuf' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-sndbuf-errors", "UDP 'sndbuf' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-noport-errors", "UDP 'noport' errors", udpErrorStats, StatType::counter);
+  S.declare("udp-in-errors", "UDP 'in' errors", udpErrorStats, StatType::counter);
 #endif
 
-  S.declare("sys-msec", "Number of msec spent in system time", getSysUserTimeMsec);
-  S.declare("user-msec", "Number of msec spent in user time", getSysUserTimeMsec);
+  S.declare("sys-msec", "Number of msec spent in system time", getSysUserTimeMsec, StatType::counter);
+  S.declare("user-msec", "Number of msec spent in user time", getSysUserTimeMsec, StatType::counter);
 
 #ifdef __linux__
-  S.declare("cpu-iowait", "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ", getCPUIOWait);
-  S.declare("cpu-steal", "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ", getCPUSteal);
+  S.declare("cpu-iowait", "Time spent waiting for I/O to complete by the whole system, in units of USER_HZ", getCPUIOWait, StatType::counter);
+  S.declare("cpu-steal", "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ", getCPUSteal, StatType::counter);
 #endif
 
-  S.declare("meta-cache-size", "Number of entries in the metadata cache", DNSSECKeeper::dbdnssecCacheSizes);
-  S.declare("key-cache-size", "Number of entries in the key cache", DNSSECKeeper::dbdnssecCacheSizes);
-  S.declare("signature-cache-size", "Number of entries in the signature cache", signatureCacheSize);
+  S.declare("meta-cache-size", "Number of entries in the metadata cache", DNSSECKeeper::dbdnssecCacheSizes, StatType::gauge);
+  S.declare("key-cache-size", "Number of entries in the key cache", DNSSECKeeper::dbdnssecCacheSizes, StatType::gauge);
+  S.declare("signature-cache-size", "Number of entries in the signature cache", signatureCacheSize, StatType::gauge);
 
   S.declare("servfail-packets","Number of times a server-failed packet was sent out");
-  S.declare("latency","Average number of microseconds needed to answer a question", getLatency);
+  S.declare("latency","Average number of microseconds needed to answer a question", getLatency, StatType::gauge);
   S.declare("timedout-packets","Number of packets which weren't answered within timeout set");
-  S.declare("security-status", "Security status based on regular polling");
+  S.declare("security-status", "Security status based on regular polling", StatType::gauge);
   S.declareDNSNameQTypeRing("queries","UDP Queries Received");
   S.declareDNSNameQTypeRing("nxdomain-queries","Queries for non-existent records within existent domains");
   S.declareDNSNameQTypeRing("noerror-queries","Queries for existing records, but for type we don't have");
@@ -506,18 +515,14 @@ catch(PDNSException& pe)
   _exit(1);
 }
 
-static void* dummyThread(void *)
+static void dummyThread()
 {
-  void* ignore=0;
-  pthread_exit(ignore);
 }
 
 static void triggerLoadOfLibraries()
 {
-  pthread_t tid;
-  pthread_create(&tid, 0, dummyThread, 0);
-  void* res;
-  pthread_join(tid, &res);
+  std::thread dummy(dummyThread);
+  dummy.join();
 }
 
 void mainthread()
@@ -628,6 +633,12 @@ void mainthread()
     }
   }
 
+  pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
+  if (!::arg()["query-local-address6"].empty()) {
+    g_log<<Logger::Warning<<"query-local-address6 is deprecated and will be removed in a future version. Please use query-local-address for IPv6 addresses as well"<<endl;
+    pdns::parseQueryLocalAddress(::arg()["query-local-address6"]);
+  }
+
   // NOW SAFE TO CREATE THREADS!
   dl->go();
 
index 45c8045c0c817bcc267bfc483ec131ac0509331f..9ce7101b941a66ba7054d3d65fa000d7b48f7fcb 100644 (file)
@@ -45,7 +45,6 @@ extern std::shared_ptr<UDPNameserver> N;
 extern vector<std::shared_ptr<UDPNameserver> > g_udpReceivers;
 extern int avg_latency;
 extern std::unique_ptr<TCPNameserver> TN;
-extern ArgvMap & arg( void );
 extern void declareArguments();
 extern void declareStats();
 extern void mainthread();
index 816b489673ddd05228c8fd71d22e22fc5a03b4ff..fd1f168f0bfd66e75991ee03755324bf302ccdaa 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
+#include <set>
+#include <thread>
+#include <boost/utility.hpp>
+
 #include "packetcache.hh"
 #include "utility.hh"
 #include "communicator.hh"
-#include <set>
-#include <boost/utility.hpp>
 #include "dnsbackend.hh"
 #include "ueberbackend.hh"
 #include "packethandler.hh"
-#include "resolver.hh"
 #include "logger.hh"
 #include "dns.hh"
 #include "arguments.hh"
@@ -45,14 +47,14 @@ void CommunicatorClass::retrievalLoopThread(void)
     d_suck_sem.wait();
     SuckRequest sr;
     {
-      Lock l(&d_lock);
+      std::lock_guard<std::mutex> l(d_lock);
       if(d_suckdomains.empty()) 
         continue;
         
       sr=d_suckdomains.front();
       d_suckdomains.pop_front();
     }
-    suck(sr.domain, sr.master);
+    suck(sr.domain, sr.master, sr.force);
   }
 }
 
@@ -82,10 +84,13 @@ void CommunicatorClass::go()
     _exit(1);
   }
 
-  pthread_t tid;
-  pthread_create(&tid,0,&launchhelper,this); // Starts CommunicatorClass::mainloop()
-  for(int n=0; n < ::arg().asNum("retrieval-threads", 1); ++n)
-    pthread_create(&tid, 0, &retrieveLaunchhelper, this); // Starts CommunicatorClass::retrievalLoopThread()
+  std::thread mainT(std::bind(&CommunicatorClass::mainloop, this));
+  mainT.detach();
+
+  for(int n=0; n < ::arg().asNum("retrieval-threads", 1); ++n) {
+    std::thread retrieve(std::bind(&CommunicatorClass::retrievalLoopThread, this));
+    retrieve.detach();
+  }
 
   d_preventSelfNotification = ::arg().mustDo("prevent-self-notification");
 
@@ -131,7 +136,7 @@ void CommunicatorClass::mainloop(void)
           bool extraSlaveRefresh = false;
           Utility::sleep(1);
           {
-            Lock l(&d_lock);
+            std::lock_guard<std::mutex> l(d_lock);
             if (d_tocheck.size())
               extraSlaveRefresh = true;
           }
index 0c3d479b112ab69bf6ebeb9fc2276f387896314d..c0ac4afd698c92fc605a08d592889a30dc767682 100644 (file)
@@ -45,6 +45,7 @@ struct SuckRequest
 {
   DNSName domain;
   ComboAddress master;
+  bool force;
   bool operator<(const SuckRequest& b) const
   {
     return tie(domain, master) < tie(b.domain, b.master);
@@ -149,9 +150,6 @@ class CommunicatorClass
 public:
   CommunicatorClass() 
   {
-    pthread_mutex_init(&d_lock,0);
-    pthread_mutex_init(&d_holelock,0);
-
     d_tickinterval=60;
     d_masterschanged=d_slaveschanged=true;
     d_nsock4 = -1;
@@ -164,24 +162,13 @@ public:
   
   void drillHole(const DNSName &domain, const string &ip);
   bool justNotified(const DNSName &domain, const string &ip);
-  void addSuckRequest(const DNSName &domain, const ComboAddress& master);
+  void addSuckRequest(const DNSName &domain, const ComboAddress& master, bool force=false);
   void addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote);
   void addTrySuperMasterRequest(const DNSPacket& p);
   void notify(const DNSName &domain, const string &ip);
   void mainloop();
   void retrievalLoopThread();
   void sendNotification(int sock, const DNSName &domain, const ComboAddress& remote, uint16_t id, UeberBackend* B);
-
-  static void *launchhelper(void *p)
-  {
-    static_cast<CommunicatorClass *>(p)->mainloop();
-    return 0;
-  }
-  static void *retrieveLaunchhelper(void *p)
-  {
-    static_cast<CommunicatorClass *>(p)->retrievalLoopThread();
-    return 0;
-  }
   bool notifyDomain(const DNSName &domain, UeberBackend* B);
 private:
   void loadArgsIntoSet(const char *listname, set<string> &listset);
@@ -189,14 +176,14 @@ private:
   void queueNotifyDomain(const DomainInfo& di, UeberBackend* B);
   int d_nsock4, d_nsock6;
   map<pair<DNSName,string>,time_t>d_holes;
-  pthread_mutex_t d_holelock;
-  void suck(const DNSName &domain, const ComboAddress& remote);
+  std::mutex d_holelock;
+  void suck(const DNSName &domain, const ComboAddress& remote, bool force=false);
   void ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, std::unique_ptr<AuthLua4>& pdl,
                 ZoneStatus& zs, vector<DNSRecord>* axfr);
 
   void slaveRefresh(PacketHandler *P);
   void masterUpdateCheck(PacketHandler *P);
-  pthread_mutex_t d_lock;
+  std::mutex d_lock;
   
   UniQueue d_suckdomains;
   set<DNSName> d_inprogress;
@@ -232,7 +219,7 @@ private:
     ~RemoveSentinel()
     {
       try {
-        Lock l(&d_cc->d_lock);
+        std::lock_guard<std::mutex> l(d_cc->d_lock);
         d_cc->d_inprogress.erase(d_dn);
       }
       catch(...) {
index c2485628249d932353db032a9c54a126cd3dd490..47cee9baa536e3cedf67978898cedbbcaf1b2a34 100644 (file)
@@ -47,8 +47,9 @@ using namespace boost::assign;
 
 DNSSECKeeper::keycache_t DNSSECKeeper::s_keycache;
 DNSSECKeeper::metacache_t DNSSECKeeper::s_metacache;
-pthread_rwlock_t DNSSECKeeper::s_metacachelock = PTHREAD_RWLOCK_INITIALIZER;
-pthread_rwlock_t DNSSECKeeper::s_keycachelock = PTHREAD_RWLOCK_INITIALIZER;
+int64_t DNSSECKeeper::s_metaCacheCleanActions = 0;
+ReadWriteLock DNSSECKeeper::s_metacachelock;
+ReadWriteLock DNSSECKeeper::s_keycachelock;
 AtomicCounter DNSSECKeeper::s_ops;
 time_t DNSSECKeeper::s_last_prune;
 size_t DNSSECKeeper::s_maxEntries = 0;
@@ -80,6 +81,7 @@ bool DNSSECKeeper::isPresigned(const DNSName& name)
   return meta=="1";
 }
 
+
 bool DNSSECKeeper::addKey(const DNSName& name, bool setSEPBit, int algorithm, int64_t& id, int bits, bool active, bool published)
 {
   if(!bits) {
@@ -107,7 +109,7 @@ bool DNSSECKeeper::addKey(const DNSName& name, bool setSEPBit, int algorithm, in
   dspk.setKey(dpk);
   dspk.d_algorithm = algorithm;
   dspk.d_flags = setSEPBit ? 257 : 256;
-  return addKey(name, dspk, id, active, published);
+  return addKey(name, dspk, id, active, published) && clearKeyCache(name);
 }
 
 void DNSSECKeeper::clearAllCaches() {
@@ -119,29 +121,37 @@ void DNSSECKeeper::clearAllCaches() {
   s_metacache.clear();
 }
 
-void DNSSECKeeper::clearCaches(const DNSName& name)
+
+bool DNSSECKeeper::clearKeyCache(const DNSName& name)
+{
+  WriteLock l(&s_keycachelock);
+  s_keycache.erase(name);
+  return true;
+}
+
+bool DNSSECKeeper::clearMetaCache(const DNSName& name)
 {
-  {
-    WriteLock l(&s_keycachelock);
-    s_keycache.erase(name); 
-  }
   WriteLock l(&s_metacachelock);
-  pair<metacache_t::iterator, metacache_t::iterator> range = s_metacache.equal_range(tie(name));
-  while(range.first != range.second)
-    s_metacache.erase(range.first++);
+  s_metacache.erase(name);
+  ++s_metaCacheCleanActions;
+  return true;
 }
 
+void DNSSECKeeper::clearCaches(const DNSName& name)
+{
+  clearKeyCache(name);
+  clearMetaCache(name);
+}
 
 bool DNSSECKeeper::addKey(const DNSName& name, const DNSSECPrivateKey& dpk, int64_t& id, bool active, bool published)
 {
-  clearCaches(name);
   DNSBackend::KeyData kd;
   kd.flags = dpk.d_flags; // the dpk doesn't get stored, only they key part
   kd.active = active;
   kd.published = published;
   kd.content = dpk.getKey()->convertToISC();
  // now store it
-  return d_keymetadb->addDomainKey(name, kd, id);
+  return d_keymetadb->addDomainKey(name, kd, id) && clearKeyCache(name);
 }
 
 
@@ -173,32 +183,27 @@ DNSSECPrivateKey DNSSECKeeper::getKeyById(const DNSName& zname, unsigned int id)
 
 bool DNSSECKeeper::removeKey(const DNSName& zname, unsigned int id)
 {
-  clearCaches(zname);
-  return d_keymetadb->removeDomainKey(zname, id);
+  return d_keymetadb->removeDomainKey(zname, id) && clearKeyCache(zname);
 }
 
 bool DNSSECKeeper::deactivateKey(const DNSName& zname, unsigned int id)
 {
-  clearCaches(zname);
-  return d_keymetadb->deactivateDomainKey(zname, id);
+  return d_keymetadb->deactivateDomainKey(zname, id) && clearKeyCache(zname);
 }
 
 bool DNSSECKeeper::activateKey(const DNSName& zname, unsigned int id)
 {
-  clearCaches(zname);
-  return d_keymetadb->activateDomainKey(zname, id);
+  return d_keymetadb->activateDomainKey(zname, id) && clearKeyCache(zname);
 }
 
 bool DNSSECKeeper::unpublishKey(const DNSName& zname, unsigned int id)
 {
-  clearCaches(zname);
-  return d_keymetadb->unpublishDomainKey(zname, id);
+  return d_keymetadb->unpublishDomainKey(zname, id) && clearKeyCache(zname);
 }
 
 bool DNSSECKeeper::publishKey(const DNSName& zname, unsigned int id)
 {
-  clearCaches(zname);
-  return d_keymetadb->publishDomainKey(zname, id);
+  return d_keymetadb->publishDomainKey(zname, id) && clearKeyCache(zname);
 }
 
 void DNSSECKeeper::getFromMetaOrDefault(const DNSName& zname, const std::string& key, std::string& value, const std::string& defaultvalue)
@@ -212,43 +217,57 @@ void DNSSECKeeper::getFromMetaOrDefault(const DNSName& zname, const std::string&
 bool DNSSECKeeper::getFromMeta(const DNSName& zname, const std::string& key, std::string& value)
 {
   static int ttl = ::arg().asNum("domain-metadata-cache-ttl");
-  bool isset = false;
-  value.clear();
-  unsigned int now = time(0);
 
   if(!((++s_ops) % 100000)) {
     cleanup();
   }
 
-  if (ttl > 0) {
-    ReadLock l(&s_metacachelock);
+  value.clear();
+  time_t now = time(nullptr);
 
-    metacache_t::const_iterator iter = s_metacache.find(tie(zname, key));
+  bool ret = false;
+  bool fromCache = false;
+  METAValues meta;
+
+  if (ttl) {
+    ReadLock l(&s_metacachelock);
+    auto iter = s_metacache.find(zname);
     if(iter != s_metacache.end() && iter->d_ttd > now) {
-      value = iter->d_value;
-      return iter->d_isset;
+      meta = iter->d_value;
+      fromCache = true;
+    }
+    else {
+      d_metaCacheCleanAction = s_metaCacheCleanActions;
     }
   }
-  vector<string> meta;
-  d_keymetadb->getDomainMetadata(zname, key, meta);
-  if(!meta.empty()) {
-    value=std::move(*meta.begin());
-    isset = true;
+
+  if (!fromCache) {
+    d_keymetadb->getAllDomainMetadata(zname, meta);
   }
 
-  if (ttl > 0) {
+  auto iter = meta.find(key);
+  if (iter != meta.end()) {
+    if (!iter->second.empty()) {
+      value = *iter->second.begin();
+    }
+    ret = true;
+  }
+
+  if (ttl && !fromCache) {
     METACacheEntry nce;
     nce.d_domain=zname;
     nce.d_ttd = now + ttl;
-    nce.d_key= key;
-    nce.d_value = value;
-    nce.d_isset = isset;
+    nce.d_value = std::move(meta);
     {
       WriteLock l(&s_metacachelock);
+      if(d_metaCacheCleanAction != s_metaCacheCleanActions) {
+        return false;
+      }
       lruReplacingInsert<SequencedTag>(s_metacache, nce);
     }
   }
-  return isset;
+
+  return ret;
 }
 
 void DNSSECKeeper::getSoaEdit(const DNSName& zname, std::string& value)
@@ -276,7 +295,7 @@ void DNSSECKeeper::getSoaEdit(const DNSName& zname, std::string& value)
 uint64_t DNSSECKeeper::dbdnssecCacheSizes(const std::string& str)
 {
   if(str=="meta-cache-size") {
-    ReadLock l(&s_metacachelock); 
+    ReadLock l(&s_metacachelock);
     return s_metacache.size();
   }
   else if(str=="key-cache-size") {
@@ -345,7 +364,6 @@ bool DNSSECKeeper::setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordCon
   if (!checkNSEC3PARAM(ns3p, error_msg))
     throw runtime_error("NSEC3PARAMs provided for zone '"+zname.toLogString()+"' are invalid: " + error_msg);
 
-  clearCaches(zname);
   string descr = ns3p.getZoneRepresentation();
   vector<string> meta;
   meta.push_back(descr);
@@ -355,30 +373,27 @@ bool DNSSECKeeper::setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordCon
     if(narrow)
       meta.push_back("1");
     
-    return d_keymetadb->setDomainMetadata(zname, "NSEC3NARROW", meta);
+    return d_keymetadb->setDomainMetadata(zname, "NSEC3NARROW", meta) && clearMetaCache(zname);
   }
   return false;
 }
 
 bool DNSSECKeeper::unsetNSEC3PARAM(const DNSName& zname)
 {
-  clearCaches(zname);
-  return (d_keymetadb->setDomainMetadata(zname, "NSEC3PARAM", vector<string>()) && d_keymetadb->setDomainMetadata(zname, "NSEC3NARROW", vector<string>()));
+  return (d_keymetadb->setDomainMetadata(zname, "NSEC3PARAM", vector<string>()) && d_keymetadb->setDomainMetadata(zname, "NSEC3NARROW", vector<string>())) && clearMetaCache(zname);
 }
 
 
 bool DNSSECKeeper::setPresigned(const DNSName& zname)
 {
-  clearCaches(zname);
   vector<string> meta;
   meta.push_back("1");
-  return d_keymetadb->setDomainMetadata(zname, "PRESIGNED", meta);
+  return d_keymetadb->setDomainMetadata(zname, "PRESIGNED", meta) && clearMetaCache(zname);
 }
 
 bool DNSSECKeeper::unsetPresigned(const DNSName& zname)
 {
-  clearCaches(zname);
-  return d_keymetadb->setDomainMetadata(zname, "PRESIGNED", vector<string>());
+  return d_keymetadb->setDomainMetadata(zname, "PRESIGNED", vector<string>()) && clearMetaCache(zname);
 }
 
 /**
@@ -392,10 +407,9 @@ bool DNSSECKeeper::unsetPresigned(const DNSName& zname)
  */
 bool DNSSECKeeper::setPublishCDS(const DNSName& zname, const string& digestAlgos)
 {
-  clearCaches(zname);
   vector<string> meta;
   meta.push_back(digestAlgos);
-  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDS", meta);
+  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDS", meta) && clearMetaCache(zname);
 }
 
 void DNSSECKeeper::getPublishCDS(const DNSName& zname, std::string& value)
@@ -411,8 +425,7 @@ void DNSSECKeeper::getPublishCDS(const DNSName& zname, std::string& value)
  */
 bool DNSSECKeeper::unsetPublishCDS(const DNSName& zname)
 {
-  clearCaches(zname);
-  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDS", vector<string>());
+  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDS", vector<string>()) && clearMetaCache(zname);
 }
 
 /**
@@ -423,10 +436,9 @@ bool DNSSECKeeper::unsetPublishCDS(const DNSName& zname)
  */
 bool DNSSECKeeper::setPublishCDNSKEY(const DNSName& zname)
 {
-  clearCaches(zname);
   vector<string> meta;
   meta.push_back("1");
-  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDNSKEY", meta);
+  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDNSKEY", meta) && clearMetaCache(zname);
 }
 
 void DNSSECKeeper::getPublishCDNSKEY(const DNSName& zname, std::string& value)
@@ -442,8 +454,7 @@ void DNSSECKeeper::getPublishCDNSKEY(const DNSName& zname, std::string& value)
  */
 bool DNSSECKeeper::unsetPublishCDNSKEY(const DNSName& zname)
 {
-  clearCaches(zname);
-  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDNSKEY", vector<string>());
+  return d_keymetadb->setDomainMetadata(zname, "PUBLISH-CDNSKEY", vector<string>()) && clearMetaCache(zname);
 }
 
 /**
index 721b611622acc7125bce1ecfd787f7619d3a2ecc..167f355fd7b8dbf4cb70f39ba539b463c958cf4b 100644 (file)
@@ -59,45 +59,42 @@ void ObjectPipe<T>::write(T& t)
   }
 }
 
-template<class T>
-bool ObjectPipe<T>::read(T* t)
-{
-  T* ptr;
-  int ret = ::read(d_fds[0], &ptr, sizeof(ptr));
-
-  if(ret < 0)
-    unixDie("read");
-  if(ret==0)
-    return false;
-  if(ret != sizeof(ptr))
-    throw std::runtime_error("Partial read, should not happen");    
-  *t=*ptr;
-  delete ptr;
-  return true;
-}
-
 template<class T>
 int ObjectPipe<T>::readTimeout(T* t, double msec)
 {
-  T* ptr;
-
-  int ret = waitForData(d_fds[0], 0, 1000*msec);
-  if(ret < 0)
-    unixDie("waiting for data in object pipe");
-  if(ret == 0) 
-    return -1;
-
-  ret = ::read(d_fds[0], &ptr, sizeof(ptr)); // this is BLOCKING!
-
-  if(ret < 0)
-    unixDie("read");
-  if(ret==0)
-    return false;
-  if(ret != sizeof(ptr))
-    throw std::runtime_error("Partial read, should not happen 2");    
-  *t=*ptr;
-  delete ptr;
-  return 1;
+  while (true) {
+    int ret = waitForData(d_fds[0], 0, 1000*msec);
+    if (ret < 0) {
+      if (errno == EINTR) {
+        continue;
+      }
+      unixDie("waiting for data in object pipe");
+    }
+    else if (ret == 0) {
+      return -1;
+    }
+
+    T* ptr = nullptr;
+    ret = ::read(d_fds[0], &ptr, sizeof(ptr)); // this is BLOCKING!
+
+    if (ret < 0) {
+      if (errno == EINTR) {
+        continue;
+      }
+      unixDie("read");
+    }
+    else if (ret == 0) {
+      return false;
+    }
+
+    if (ret != sizeof(ptr)) {
+      throw std::runtime_error("Partial read, should not happen 2");
+    }
+
+    *t = *ptr;
+    delete ptr;
+    return 1;
+  }
 }
 
 
index b44159132b3fe399e9ebea153288ef02a50cc5e6..ad1626a9990eca6727aa5b3794a0e132089f7893 100644 (file)
@@ -43,8 +43,7 @@ public:
   ObjectPipe();
   ~ObjectPipe();
   void write(T& t);
-  bool read(T* t); // returns false on EOF
-  int readTimeout(T* t, double msec); //!< -1 is timeout, 0 is no data, 1 is data. msec<0 waits infinitely wrong. msec==0 = undefined
+  int readTimeout(T* t, double msec); //!< -1 is timeout, 0 is no data, 1 is data. msec<0 waits infinitely long. msec==0 = undefined
   void close(); 
 private:
   int d_fds[2];
index 013aa0923e24a4b55b22406d18a1ffc06d822ce8..ff31b01819ca4bfe0cb0a366252aac0ba9d1497c 100644 (file)
@@ -114,22 +114,20 @@ void DevPollFDMultiplexer::removeFD(callbackmap_t& cbmap, int fd)
 
 void DevPollFDMultiplexer::getAvailableFDs(std::vector<int>& fds, int timeout)
 {
+  std::vector<struct pollfd> pollfds(d_readCallbacks.size() + d_writeCallbacks.size());
   struct dvpoll dvp;
   dvp.dp_nfds = d_readCallbacks.size() + d_writeCallbacks.size();
-  dvp.dp_fds = new pollfd[dvp.dp_nfds];
+  dvp.dp_fds = pollfds.data();
   dvp.dp_timeout = timeout;
   int ret=ioctl(d_devpollfd, DP_POLL, &dvp);
 
   if(ret < 0 && errno!=EINTR) {
-    delete[] dvp.dp_fds;
     throw FDMultiplexerException("/dev/poll returned error: "+stringerror());
   }
 
   for(int n=0; n < ret; ++n) {
-    fds.push_back(dvp.dp_fds[n].fd);
+    fds.push_back(pollfds.at(n).fd);
   }
-
-  delete[] dvp.dp_fds;
 }
 
 int DevPollFDMultiplexer::run(struct timeval* now, int timeout)
@@ -137,39 +135,38 @@ int DevPollFDMultiplexer::run(struct timeval* now, int timeout)
   if(d_inrun) {
     throw FDMultiplexerException("FDMultiplexer::run() is not reentrant!\n");
   }
+  std::vector<struct pollfd> fds(d_readCallbacks.size() + d_writeCallbacks.size());
   struct dvpoll dvp;
   dvp.dp_nfds = d_readCallbacks.size() + d_writeCallbacks.size();
-  dvp.dp_fds = new pollfd[dvp.dp_nfds];
+  dvp.dp_fds = fds.data();
   dvp.dp_timeout = timeout;
   int ret=ioctl(d_devpollfd, DP_POLL, &dvp);
   int err = errno;
   gettimeofday(now,0); // MANDATORY!
 
   if(ret < 0 && err!=EINTR) {
-    delete[] dvp.dp_fds;
     throw FDMultiplexerException("/dev/poll returned error: "+stringerror(err));
   }
 
   if(ret < 1) { // thanks AB!
-    delete[] dvp.dp_fds;
     return 0;
   }
 
   d_inrun=true;
   for(int n=0; n < ret; ++n) {
-    d_iter=d_readCallbacks.find(dvp.dp_fds[n].fd);
+    d_iter=d_readCallbacks.find(fds.at(n).fd);
     
     if(d_iter != d_readCallbacks.end()) {
       d_iter->d_callback(d_iter->d_fd, d_iter->d_parameter);
       continue; // so we don't refind ourselves as writable!
     }
-    d_iter=d_writeCallbacks.find(dvp.dp_fds[n].fd);
+    d_iter=d_writeCallbacks.find(fds.at(n).fd);
     
     if(d_iter != d_writeCallbacks.end()) {
       d_iter->d_callback(d_iter->d_fd, d_iter->d_parameter);
     }
   }
-  delete[] dvp.dp_fds;
+
   d_inrun=false;
   return ret;
 }
index c025d897f5db4eecceb3bed22ec7406bd4d6cb40..38f08005ef58a2e61f45e658f64416fc87a9c758 100644 (file)
@@ -24,6 +24,7 @@
 #include <deque>
 #include <queue>
 #include <vector>
+#include <thread>
 #include <pthread.h>
 #include "threadname.hh"
 #include <unistd.h>
@@ -88,7 +89,7 @@ public:
   MultiThreadDistributor(int n);
   typedef std::function<void(std::unique_ptr<Answer>&)> callback_t;
   int question(Question&, callback_t callback) override; //!< Submit a question to the Distributor
-  static void* makeThread(void *); //!< helper function to create our n threads
+  void distribute(int n);
   int getQueueSize() override {
     return d_queued;
   }
@@ -114,7 +115,7 @@ private:
   time_t d_last_started;
   unsigned int d_overloadQueueLength, d_maxQueueLength;
   int d_num_threads;
-  std::atomic<unsigned int> d_queued{0}, d_running{0};
+  std::atomic<unsigned int> d_queued{0};
   std::vector<std::pair<int,int>> d_pipes;
 };
 
@@ -137,6 +138,10 @@ template<class Answer, class Question, class Backend>SingleThreadDistributor<Ans
     g_log<<Logger::Error<<"Distributor caught fatal exception: "<<AE.reason<<endl;
     _exit(1);
   }
+  catch(const std::exception& e) {
+    g_log<<Logger::Error<<"Distributor caught fatal exception: "<<e.what()<<endl;
+    _exit(1);
+  }
   catch(...) {
     g_log<<Logger::Error<<"Caught an unknown exception when creating backend, probably"<<endl;
     _exit(1);
@@ -151,9 +156,6 @@ template<class Answer, class Question, class Backend>MultiThreadDistributor<Answ
   nextid=0;
   d_last_started=time(0);
 
-  pthread_t tid;
-  
-
   for(int i=0; i < n; ++i) {
     int fds[2];
     if(pipe(fds) < 0)
@@ -168,7 +170,8 @@ template<class Answer, class Question, class Backend>MultiThreadDistributor<Answ
 
   g_log<<Logger::Warning<<"About to create "<<n<<" backend threads for UDP"<<endl;
   for(int i=0;i<n;i++) {
-    pthread_create(&tid,0,&makeThread,static_cast<void *>(this));
+    std::thread t(std::bind(&MultiThreadDistributor<Answer,Question,Backend>::distribute, this, i));
+    t.detach();
     Utility::usleep(50000); // we've overloaded mysql in the past :-)
   }
   g_log<<Logger::Warning<<"Done launching threads, ready to distribute questions"<<endl;
@@ -176,12 +179,9 @@ template<class Answer, class Question, class Backend>MultiThreadDistributor<Answ
 
 
 // start of a new thread
-template<class Answer, class Question, class Backend>void *MultiThreadDistributor<Answer,Question,Backend>::makeThread(void *p)
+template<class Answer, class Question, class Backend>void MultiThreadDistributor<Answer,Question,Backend>::distribute(int ournum)
 {
   setThreadName("pdns/distributo");
-  pthread_detach(pthread_self());
-  MultiThreadDistributor *us=static_cast<MultiThreadDistributor *>(p);
-  int ournum=us->d_running++;
 
   try {
     std::unique_ptr<Backend> b= make_unique<Backend>(); // this will answer our questions
@@ -190,9 +190,9 @@ template<class Answer, class Question, class Backend>void *MultiThreadDistributo
     for(;;) {
     
       QuestionData* tempQD = nullptr;
-      if(read(us->d_pipes[ournum].first, &tempQD, sizeof(tempQD)) != sizeof(tempQD))
+      if(read(d_pipes.at(ournum).first, &tempQD, sizeof(tempQD)) != sizeof(tempQD))
        unixDie("read");
-      --us->d_queued;
+      --d_queued;
       std::unique_ptr<QuestionData> QD = std::unique_ptr<QuestionData>(tempQD);
       tempQD = nullptr;
       std::unique_ptr<Answer> a = nullptr;
@@ -251,11 +251,14 @@ retry:
     g_log<<Logger::Error<<"Distributor caught fatal exception: "<<AE.reason<<endl;
     _exit(1);
   }
+  catch(const std::exception& e) {
+    g_log<<Logger::Error<<"Distributor caught fatal exception: "<<e.what()<<endl;
+    _exit(1);
+  }
   catch(...) {
     g_log<<Logger::Error<<"Caught an unknown exception when creating backend, probably"<<endl;
     _exit(1);
   }
-  return 0;
 }
 
 template<class Answer, class Question, class Backend>int SingleThreadDistributor<Answer,Question,Backend>::question(Question& q, callback_t callback)
@@ -312,7 +315,7 @@ template<class Answer, class Question, class Backend>int MultiThreadDistributor<
   QD->callback=callback;
 
   ++d_queued;
-  if(write(d_pipes[QD->id % d_pipes.size()].second, &QD, sizeof(QD)) != sizeof(QD)) {
+  if(write(d_pipes.at(QD->id % d_pipes.size()).second, &QD, sizeof(QD)) != sizeof(QD)) {
     --d_queued;
     delete QD;
     unixDie("write");
index 237975cc3292d7d31a73e0e3333dd9778f83245e..2201f7392626aaac8c9ad3c197a8c1e1fd66a9c8 100644 (file)
@@ -96,7 +96,7 @@ public:
   DNSName wildcardname;
   string content; //!< what this record points to. Example: 10.1.2.3
 
-  // Aligned on 8-byte boundries on systems where time_t is 8 bytes and int
+  // Aligned on 8-byte boundaries on systems where time_t is 8 bytes and int
   // is 4 bytes, aka modern linux on x86_64
   time_t last_modified; //!< For autocalculating SOA serial numbers - the backend needs to fill this in
 
index 5618e13904909085e2d325d2f6d581a67ca1d2bd..b6c70a46f0e669cf26f9b17c180478db826a554a 100644 (file)
@@ -64,7 +64,7 @@ static void
 kiss_init(unsigned int seed)
 {
   kiss_seed = seed;
-  kiss_jsr = 0x5eed5eed; /* simply musn't be 0 */
+  kiss_jsr = 0x5eed5eed; /* simply mustn't be 0 */
   kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */
 }
 
index 8afda61e60353a6645bebefed53191e38faadc88..b968b0766b0d432422e200c8db8ccad82476dc50 100644 (file)
  */
 #pragma once
 #include <cstdint>
+#include <limits>
 
 void dns_random_init(const std::string& data = "", bool force_reinit = false);
 uint32_t dns_random(uint32_t n);
 uint16_t dns_random_uint16();
+
+namespace pdns {
+  struct dns_random_engine {
+
+    typedef uint32_t result_type;
+
+    static constexpr result_type min()
+    {
+      return 0;
+    }
+
+    static constexpr result_type max()
+    {
+      return std::numeric_limits<result_type>::max() - 1;
+    }
+
+    result_type operator()()
+    {
+      return dns_random(std::numeric_limits<result_type>::max());
+    }
+  };
+}
+
index 07fe9c6d42f4f443486437957658856395374334..5c8d2950291d9f901277ab3d0c282d62b8aaee4b 100644 (file)
@@ -85,6 +85,15 @@ void BackendMakerClass::report(BackendFactory *bf)
   d_repository[bf->getName()]=bf;
 }
 
+void BackendMakerClass::clear()
+{
+  d_instances.clear();
+  for (auto& repo : d_repository) {
+    delete repo.second;
+    repo.second = nullptr;
+  }
+  d_repository.clear();
+}
 
 vector<string> BackendMakerClass::getModules()
 {
@@ -139,7 +148,7 @@ void BackendMakerClass::launch(const string &instr)
   vector<string> parts;
   stringtok(parts,instr,", ");
 
-  for (const auto part : parts)
+  for (const auto& part : parts)
     if (count(parts.begin(), parts.end(), part) > 1)
       throw ArgException("Refusing to launch multiple backends with the same name '" + part + "', verify all 'launch' statements in your configuration");
 
@@ -164,41 +173,50 @@ void BackendMakerClass::launch(const string &instr)
   }
 }
 
-int BackendMakerClass::numLauncheable()
+size_t BackendMakerClass::numLauncheable() const
 {
   return d_instances.size();
 }
 
-vector<DNSBackend *>BackendMakerClass::all(bool metadataOnly)
+vector<DNSBackend *> BackendMakerClass::all(bool metadataOnly)
 {
-  vector<DNSBackend *>ret;
+  vector<DNSBackend *> ret;
   if(d_instances.empty())
     throw PDNSException("No database backends configured for launch, unable to function");
 
+  ret.reserve(d_instances.size());
+
   try {
-    for(vector<pair<string,string> >::const_iterator i=d_instances.begin();i!=d_instances.end();++i) {
-      DNSBackend *made;
-      if(metadataOnly)
-        made = d_repository[i->first]->makeMetadataOnly(i->second);
-      else
-        made = d_repository[i->first]->make(i->second);
-      if(!made)
-        throw PDNSException("Unable to launch backend '"+i->first+"'");
+    for (const auto& instance : d_instances) {
+      DNSBackend *made = nullptr;
+
+      if (metadataOnly) {
+        made = d_repository[instance.first]->makeMetadataOnly(instance.second);
+      }
+      else {
+        made = d_repository[instance.first]->make(instance.second);
+      }
+
+      if (!made) {
+        throw PDNSException("Unable to launch backend '" + instance.first + "'");
+      }
 
       ret.push_back(made);
     }
   }
-  catch(PDNSException &ae) {
+  catch(const PDNSException &ae) {
     g_log<<Logger::Error<<"Caught an exception instantiating a backend: "<<ae.reason<<endl;
     g_log<<Logger::Error<<"Cleaning up"<<endl;
-    for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
-      delete *i;
+    for (auto i : ret) {
+      delete i;
+    }
     throw;
   } catch(...) {
     // and cleanup
     g_log<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl;
-    for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
-      delete *i;
+    for (auto i : ret) {
+      delete i;
+    }
     throw;
   }
 
index c45b07e0096a90d10d3af232a7b627dfedfa3f4b..c086e2ddf76cfac47ec28562e5c1dab02796351d 100644 (file)
@@ -315,8 +315,8 @@ public:
   {
   }
 
-  //! Called when the Master of a domain should be changed
-  virtual bool setMaster(const DNSName &domain, const string &ip)
+  //! Called when the Master list of a domain should be changed
+  virtual bool setMasters(const DNSName &domain, const vector<ComboAddress> &masters)
   {
     return false;
   }
@@ -336,6 +336,12 @@ public:
   //! Can be called to seed the getArg() function with a prefix
   void setArgPrefix(const string &prefix);
 
+  //! Add an entry for a super master
+  virtual bool superMasterAdd(const string &ip, const string &nameserver, const string &account) 
+  {
+    return false; 
+  }
+
   //! determine if ip is a supermaster or a domain
   virtual bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
   {
@@ -343,7 +349,7 @@ public:
   }
 
   //! called by PowerDNS to create a new domain
-  virtual bool createDomain(const DNSName &domain)
+  virtual bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account)
   {
     return false;
   }
@@ -412,10 +418,11 @@ class BackendMakerClass
 public:
   void report(BackendFactory *bf);
   void launch(const string &instr);
-  vector<DNSBackend *>all(bool skipBIND=false);
+  vector<DNSBackend *> all(bool skipBIND=false);
   void load(const string &module);
-  int numLauncheable();
+  size_t numLauncheable() const;
   vector<string> getModules();
+  void clear();
 
 private:
   void load_all();
index 18bfeee6cf07776ae98909a899b570cc57cadd6b..206f3e073c41ef540e441595e1c07848883b3788 100644 (file)
@@ -34,6 +34,8 @@
 #include "misc.hh"
 #include "dnswriter.hh"
 #include "dnsrecords.hh"
+#include "dns_random.hh"
+#include "arguments.hh"
 
 using namespace boost::accumulators;
 namespace po = boost::program_options;
@@ -42,6 +44,12 @@ po::variables_map g_vm;
 
 StatBag S;
 
+ArgvMap &arg()
+{
+  static ArgvMap theArg;
+  return theArg;
+}
+
 bool g_quiet=false;
 bool g_envoutput=false;
 
@@ -207,7 +215,7 @@ struct SendReceive
   unsigned int d_receiveds, d_receiveerrors, d_senderrors;
 };
 
-void usage(po::options_description &desc) {
+static void usage(po::options_description &desc) {
   cerr << "Usage: dnsbulktest [OPTION].. IPADDRESS PORTNUMBER [LIMIT]"<<endl;
   cerr << desc << "\n";
 }
@@ -215,6 +223,9 @@ void usage(po::options_description &desc) {
 int main(int argc, char** argv)
 try
 {
+  ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
+  ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
+
   po::options_description desc("Allowed options");
   desc.add_options()
     ("help,h", "produce help message")
@@ -307,7 +318,7 @@ try
       domains.push_back(TypedQuery("www."+split.second, qtype));
   }
   cerr<<"Read "<<domains.size()<<" domains!"<<endl;
-  random_shuffle(domains.begin(), domains.end());
+  shuffle(domains.begin(), domains.end(), pdns::dns_random_engine());
 
   boost::format datafmt("%s %|20t|%+15s  %|40t|%s %|60t|%+15s\n");
 
index 71535acdf6a64266a992bda517bc9b72bf561970..17eb99666fba4d7d13c309c92bf0f8bc41d1d8a3 100644 (file)
@@ -25,7 +25,6 @@
 #include "dolog.hh"
 #include "dnscrypt.hh"
 #include "dnswriter.hh"
-#include "lock.hh"
 
 DNSCryptPrivateKey::DNSCryptPrivateKey()
 {
@@ -125,20 +124,15 @@ DNSCryptQuery::~DNSCryptQuery()
 
 
 DNSCryptContext::~DNSCryptContext() {
-  pthread_rwlock_destroy(&d_lock);
 }
 
 DNSCryptContext::DNSCryptContext(const std::string& pName, const std::vector<CertKeyPaths>& certKeys): d_certKeyPaths(certKeys), providerName(pName)
 {
-  pthread_rwlock_init(&d_lock, 0);
-
   reloadCertificates();
 }
 
 DNSCryptContext::DNSCryptContext(const std::string& pName, const DNSCryptCert& certificate, const DNSCryptPrivateKey& pKey): providerName(pName)
 {
-  pthread_rwlock_init(&d_lock, 0);
-
   addNewCertificate(certificate, pKey);
 }
 
@@ -430,7 +424,7 @@ void DNSCryptContext::getCertificateResponse(time_t now, const DNSName& qname, u
   dh->rcode = RCode::NoError;
 
   ReadLock r(&d_lock);
-  for (const auto pair : d_certs) {
+  for (const auto& pair : d_certs) {
     if (!pair->active || !pair->cert.isValid(now)) {
       continue;
     }
@@ -504,7 +498,7 @@ void DNSCryptQuery::getDecrypted(bool tcp, char* packet, uint16_t packetSize, ui
 
   unsigned char nonce[DNSCRYPT_NONCE_SIZE];
   static_assert(sizeof(nonce) == (2* sizeof(d_header.clientNonce)), "Nonce should be larger than clientNonce (half)");
-  static_assert(sizeof(d_header.clientPK) == DNSCRYPT_PUBLIC_KEY_SIZE, "Client Publick key size is not right");
+  static_assert(sizeof(d_header.clientPK) == DNSCRYPT_PUBLIC_KEY_SIZE, "Client Public key size is not right");
   static_assert(sizeof(d_pair->privateKey.key) == DNSCRYPT_PRIVATE_KEY_SIZE, "Private key size is not right");
 
   memcpy(nonce, &d_header.clientNonce, sizeof(d_header.clientNonce));
index 53b09f5fadf52c7b7b0baaed9a8ed4ab0ba2f44b..a010ebdc5a7a99bf64aed7ec37b5f02ef0222dae 100644 (file)
@@ -51,6 +51,7 @@ private:
 #include <sodium.h>
 
 #include "dnsname.hh"
+#include "lock.hh"
 
 #define DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE (crypto_sign_ed25519_PUBLICKEYBYTES)
 #define DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE (crypto_sign_ed25519_SECRETKEYBYTES)
@@ -285,7 +286,7 @@ private:
 
   void addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>& newCert, bool reload=false);
 
-  pthread_rwlock_t d_lock;
+  ReadWriteLock d_lock;
   std::vector<std::shared_ptr<DNSCryptCertificatePair>> d_certs;
   std::vector<CertKeyPaths> d_certKeyPaths;
   DNSName providerName;
index 2736dc0480ce441d59bf0d1e3facc3b681a2a15d..79f12234f2222b48eb1b167c155a4c7f576fe303 100644 (file)
@@ -28,6 +28,7 @@
 #include "dnsdist-ecs.hh"
 #include "ednsoptions.hh"
 #include "ednssubnet.hh"
+#include "packetcache.hh"
 
 DNSDistPacketCache::DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL, uint32_t minTTL, uint32_t tempFailureTTL, uint32_t maxNegativeTTL, uint32_t staleTTL, bool dontAge, uint32_t shards, bool deferrableInsertLock, bool parseECS): d_maxEntries(maxEntries), d_shardCount(shards), d_maxTTL(maxTTL), d_tempFailureTTL(tempFailureTTL), d_maxNegativeTTL(maxNegativeTTL), d_minTTL(minTTL), d_staleTTL(staleTTL), d_dontAge(dontAge), d_deferrableInsertLock(deferrableInsertLock), d_parseECS(parseECS)
 {
@@ -203,11 +204,12 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& su
 
 bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t queryId, char* response, uint16_t* responseLen, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, uint32_t allowExpired, bool skipAging)
 {
-  std::string dnsQName(dq.qname->toDNSString());
+  const auto& dnsQName = dq.qname->getStorage();
   uint32_t key = getKey(dnsQName, consumed, reinterpret_cast<const unsigned char*>(dq.dh), dq.len, dq.tcp);
 
-  if (keyOut)
+  if (keyOut) {
     *keyOut = key;
+  }
 
   if (d_parseECS) {
     getClientSubnet(reinterpret_cast<const char*>(dq.dh), consumed, dq.len, subnet);
@@ -411,20 +413,27 @@ uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool
   return getDNSPacketMinTTL(packet, length, seenNoDataSOA);
 }
 
-uint32_t DNSDistPacketCache::getKey(const std::string& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp)
+uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp)
 {
   uint32_t result = 0;
   /* skip the query ID */
-  if (packetLen < sizeof(dnsheader))
-    throw std::range_error("Computing packet cache key for an invalid packet size");
+  if (packetLen < sizeof(dnsheader)) {
+    throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packetLen) +")");
+  }
+
   result = burtle(packet + 2, sizeof(dnsheader) - 2, result);
-  string lc(toLower(qname));
-  result = burtle((const unsigned char*) lc.c_str(), lc.length(), result);
+  result = burtleCI((const unsigned char*) qname.c_str(), qname.length(), result);
   if (packetLen < sizeof(dnsheader) + consumed) {
-    throw std::range_error("Computing packet cache key for an invalid packet");
+    throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packetLen) + " < " + std::to_string(sizeof(dnsheader) + consumed) + ")");
   }
   if (packetLen > ((sizeof(dnsheader) + consumed))) {
-    result = burtle(packet + sizeof(dnsheader) + consumed, packetLen - (sizeof(dnsheader) + consumed), result);
+    if (!d_cookieHashing) {
+      /* skip EDNS Cookie options if any */
+      result = PacketCache::hashAfterQname(pdns_string_view(reinterpret_cast<const char*>(packet), packetLen), result, sizeof(dnsheader) + consumed, false);
+    }
+    else {
+      result = burtle(packet + sizeof(dnsheader) + consumed, packetLen - (sizeof(dnsheader) + consumed), result);
+    }
   }
   result = burtle((const unsigned char*) &tcp, sizeof(tcp), result);
   return result;
@@ -447,12 +456,12 @@ uint64_t DNSDistPacketCache::getEntriesCount()
 
 uint64_t DNSDistPacketCache::dump(int fd)
 {
-  FILE * fp = fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if (fp == nullptr) {
     return 0;
   }
 
-  fprintf(fp, "; dnsdist's packet cache dump follows\n;\n");
+  fprintf(fp.get(), "; dnsdist's packet cache dump follows\n;\n");
 
   uint64_t count = 0;
   time_t now = time(nullptr);
@@ -460,19 +469,18 @@ uint64_t DNSDistPacketCache::dump(int fd)
     ReadLock w(&d_shards.at(shardIndex).d_lock);
     auto& map = d_shards[shardIndex].d_map;
 
-    for(const auto entry : map) {
+    for (const auto& entry : map) {
       const CacheValue& value = entry.second;
       count++;
 
       try {
-        fprintf(fp, "%s %" PRId64 " %s ; key %" PRIu32 ", length %" PRIu16 ", tcp %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).getName().c_str(), entry.first, value.len, value.tcp, static_cast<int64_t>(value.added));
+        fprintf(fp.get(), "%s %" PRId64 " %s ; key %" PRIu32 ", length %" PRIu16 ", tcp %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).getName().c_str(), entry.first, value.len, value.tcp, static_cast<int64_t>(value.added));
       }
       catch(...) {
-        fprintf(fp, "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str());
+        fprintf(fp.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str());
       }
     }
   }
 
-  fclose(fp);
   return count;
 }
index 4b03ad4151aede5e3e748a6a47e90b36c79ce753..89ccbfec91de6629a8830100bc84a9046af6b2f2 100644 (file)
@@ -55,6 +55,7 @@ public:
   uint64_t dump(int fd);
 
   bool isECSParsingEnabled() const { return d_parseECS; }
+  bool isCookieHashingEnabled() const { return d_cookieHashing; }
 
   bool keepStaleData() const
   {
@@ -65,8 +66,19 @@ public:
     d_keepStaleData = keep;
   }
 
+  void setCookieHashing(bool hashing)
+  {
+    d_cookieHashing = hashing;
+  }
+
+  void setECSParsingEnabled(bool enabled)
+  {
+    d_parseECS = enabled;
+  }
+
+  uint32_t getKey(const DNSName::string_t& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp);
+
   static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
-  static uint32_t getKey(const std::string& qname, uint16_t consumed, const unsigned char* packet, uint16_t packetLen, bool tcp);
   static bool getClientSubnet(const char* packet, unsigned int consumed, uint16_t len, boost::optional<Netmask>& subnet);
 
 private:
@@ -92,22 +104,18 @@ private:
   public:
     CacheShard(): d_entriesCount(0)
     {
-      pthread_rwlock_init(&d_lock, nullptr);
     }
     CacheShard(const CacheShard& old): d_entriesCount(0)
     {
-      pthread_rwlock_init(&d_lock, nullptr);
-    }
-    ~CacheShard() {
-      pthread_rwlock_destroy(&d_lock);
     }
+
     void setSize(size_t maxSize)
     {
       d_map.reserve(maxSize);
     }
 
     std::unordered_map<uint32_t,CacheValue> d_map;
-    pthread_rwlock_t d_lock;
+    ReadWriteLock d_lock;
     std::atomic<uint64_t> d_entriesCount;
   };
 
@@ -137,4 +145,5 @@ private:
   bool d_deferrableInsertLock;
   bool d_parseECS;
   bool d_keepStaleData{false};
+  bool d_cookieHashing{false};
 };
index 26702dcb65db8e6320e31fc157dec8740f515920..53475dfa9e60823920bdc088113fd1becd457443 100644 (file)
@@ -58,14 +58,13 @@ try
       const auto& server = conf.server;
       const std::string& namespace_name = conf.namespace_name;
       std::string hostname = conf.ourname;
-      if(hostname.empty()) {
-        char tmp[80];
-        memset(tmp, 0, sizeof(tmp));
-        gethostname(tmp, sizeof(tmp));
-        char *p = strchr(tmp, '.');
-        if(p) *p=0;
-        hostname=tmp;
-        boost::replace_all(hostname, ".", "_");
+      if (hostname.empty()) {
+        try {
+          hostname = getCarbonHostName();
+        }
+        catch(const std::exception& e) {
+          throw std::runtime_error(std::string("The 'ourname' setting in 'carbonServer()' has not been set and we are unable to determine the system's hostname: ") + e.what());
+        }
       }
       const std::string& instance_name = conf.instance_name;
 
@@ -87,7 +86,7 @@ try
         }
         auto states = g_dstates.getLocal();
         for(const auto& state : *states) {
-          string serverName = state->getName().empty() ? (state->remote.toString() + ":" + std::to_string(state->remote.getPort())) : state->getName();
+          string serverName = state->getName().empty() ? state->remote.toStringWithPort() : state->getName();
           boost::replace_all(serverName, ".", "_");
           const string base = namespace_name + "." + hostname + "." + instance_name + ".servers." + serverName + ".";
           str<<base<<"queries" << ' ' << state->queries.load() << " " << now << "\r\n";
@@ -111,7 +110,7 @@ try
           if (front->udpFD == -1 && front->tcpFD == -1)
             continue;
 
-          string frontName = front->local.toString() + ":" + std::to_string(front->local.getPort()) +  (front->udpFD >= 0 ? "_udp" : "_tcp");
+          string frontName = front->local.toStringWithPort() + (front->udpFD >= 0 ? "_udp" : "_tcp");
           boost::replace_all(frontName, ".", "_");
           auto dupPair = frontendDuplicates.insert({frontName, 1});
           if (!dupPair.second) {
index 98841bac685690d531f9142e33b92b7ac11a7fb1..efabac278874a195d90f13af8ac04f2244c01a10 100644 (file)
@@ -25,6 +25,8 @@
 #include <thread>
 
 #if defined (__OpenBSD__) || defined(__NetBSD__)
+// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h
+#undef __STRICT_ANSI__
 #include <readline/readline.h>
 #include <readline/history.h>
 #else
@@ -185,7 +187,6 @@ void doClient(ComboAddress server, const std::string& command)
   }
 
   string histfile = historyFile();
-  set<string> dupper;
   {
     ifstream history(histfile);
     string line;
@@ -228,7 +229,6 @@ void doClient(ComboAddress server, const std::string& command)
 void doConsole()
 {
   string histfile = historyFile(true);
-  set<string> dupper;
   {
     ifstream history(histfile);
     string line;
@@ -348,7 +348,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}", "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 is a table of parameters" },
   { "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" },
   { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
-  { "addDynBlockSMT", true, "names, msessage[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
+  { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
   { "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a cache hit response rule" },
   { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a response rule" },
@@ -360,10 +360,11 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "AndRule", true, "list of DNS rules", "matches if all sub-rules matches" },
   { "benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule" },
   { "carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds" },
-  { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" },
+  { "clearConsoleHistory", true, "", "clear the internal (in-memory) history of console commands" },
   { "clearDynBlocks", true, "", "clear all dynamic blocks" },
   { "clearQueryCounters", true, "", "clears the query counter buffer" },
   { "clearRules", true, "", "remove all current rules" },
+  { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" },
   { "ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action" },
   { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
   { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
@@ -419,7 +420,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "inClientStartup", true, "", "returns true during console client parsing of configuration" },
   { "includeDirectory", true, "path", "include configuration files from `path`" },
   { "KeyValueLookupKeyQName", true, "[wireFormat]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the qname of the query, either in wire format (default) or in plain text if 'wireFormat' is false" },
-  { "KeyValueLookupKeySourceIP", true, "", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the source IP of the client in network byte-order." },
+  { "KeyValueLookupKeySourceIP", true, "[v4Mask [, v6Mask]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the (possibly bitmasked) source IP of the client in network byte-order." },
   { "KeyValueLookupKeySuffix", true, "[minLabels [,wireFormat]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return a vector of keys based on the labels of the qname in DNS wire format or plain text" },
   { "KeyValueLookupKeyTag", true, "tag", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the value of the corresponding tag for this query, if it exists" },
   { "KeyValueStoreLookupAction", true, "kvs, lookupKey, destinationTag", "does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'" },
@@ -452,8 +453,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
   { "newDNSNameSet", true, "", "returns a new DNSNameSet" },
   { "newDynBPFFilter", true, "bpf", "Return a new dynamic eBPF filter associated to a given BPF Filter" },
-  { "newFrameStreamTcpLogger", true, "addr", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
-  { "newFrameStreamUnixLogger", true, "socket", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
+  { "newFrameStreamTcpLogger", true, "addr [, options]", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
+  { "newFrameStreamUnixLogger", true, "socket [, options]", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
 #ifdef HAVE_LMDB
   { "newLMDBKVStore", true, "fname, dbName", "Return a new KeyValueStore object associated to the corresponding LMDB database" },
 #endif
@@ -462,7 +463,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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 [, {uuid=\"UUID\"}]", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
-  { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\", sockets=1}", "instantiate a server" },
+  { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\", sockets=1, reconnectOnUp=false}", "instantiate a server" },
   { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
   { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
   { "NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action" },
@@ -524,10 +525,12 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" },
   { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
   { "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 65535" },
-  { "SetNegativeAndSOAAction", "true", "nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options]", "Turn a query into a NXDomain or NoData answer and sets a SOA record in the additional section" },
+  { "SetNegativeAndSOAAction", true, "nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options]", "Turn a query into a NXDomain or NoData answer and sets a SOA record in the additional section" },
   { "setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses" },
   { "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" },
-  { "setPoolServerPolicyLua", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
+  { "setPoolServerPolicyLua", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
+  { "setPoolServerPolicyLuaFFI", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
+  { "setPoolServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy for this pool to one named 'name' and returned by the Lua FFI code passed in 'code'" },
   { "setPreserveTrailingData", true, "bool", "set whether trailing data should be preserved while adding ECS or XPF records to incoming queries" },
   { "setQueryCount", true, "bool", "set whether queries should be counted" },
   { "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" },
@@ -540,6 +543,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'" },
   { "setServerPolicyLuaFFI", true, "name, function", "set server selection policy to one named 'name' and provided by the Lua FFI 'function'" },
+  { "setServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy to one named 'name' and returned by the Lua FFI code passed in 'code'" },
   { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
   { "setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query" },
   { "setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON" },
@@ -576,6 +580,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" },
   { "showVersion", true, "", "show the current version" },
   { "shutdown", true, "", "shut down `dnsdist`" },
+  { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" },
   { "SkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" },
   { "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" },
   { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
@@ -598,7 +603,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
   { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
   { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
-  { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=ServFail), as grouped when optionally cut down to 'labels' labels" },
+  { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=NXDomain), as grouped when optionally cut down to 'labels' labels" },
   { "topResponseRule", true, "", "move the last response rule to the first position" },
   { "topRule", true, "", "move the last rule to the first position" },
   { "topSelfAnsweredResponseRule", true, "", "move the last self-answered response rule to the first position" },
@@ -613,7 +618,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
 };
 
 extern "C" {
-char* my_generator(const char* text, int state)
+static char* my_generator(const char* text, int state)
 {
   string t(text);
   /* to keep it readable, we try to keep only 4 keywords per line
@@ -819,3 +824,9 @@ catch(const std::exception& e)
   close(fd);
   errlog("Control connection died: %s", e.what());
 }
+
+void clearConsoleHistory()
+{
+  clear_history();
+  g_confDelta.clear();
+}
index 1c3bb6e2f878118b56e884a243dfbee55e966be3..70800644a56098de7508b55d321809c6b91ea41c 100644 (file)
@@ -51,3 +51,4 @@ extern "C" {
 char** my_completion( const char * text , int start,  int end);
 }
 void controlThread(int fd, ComboAddress local);
+void clearConsoleHistory();
index 7cda963d4a1e27d56480f9512e0a1e70d8bd4eec..30659cd7081d849820b795d80faaedca1578f5f6 100644 (file)
@@ -30,7 +30,9 @@
 /* when we add EDNS to a query, we don't want to advertise
    a large buffer size */
 size_t g_EdnsUDPPayloadSize = 512;
-uint16_t g_PayloadSizeSelfGenAnswers{s_udpIncomingBufferSize};
+static const uint16_t defaultPayloadSizeSelfGenAnswers = 1232;
+static_assert(defaultPayloadSizeSelfGenAnswers < s_udpIncomingBufferSize, "The UDP responder's payload size should be smaller or equal to our incoming buffer size");
+uint16_t g_PayloadSizeSelfGenAnswers{defaultPayloadSizeSelfGenAnswers};
 
 /* draft-ietf-dnsop-edns-client-subnet-04 "11.1.  Privacy" */
 uint16_t g_ECSSourcePrefixV4 = 24;
@@ -503,7 +505,12 @@ bool parseEDNSOptions(DNSQuestion& dq)
 
   dq.ednsOptions = std::make_shared<std::map<uint16_t, EDNSOptionView> >();
 
-  if (ntohs(dq.dh->ancount) != 0 || ntohs(dq.dh->nscount) != 0 || (ntohs(dq.dh->arcount) != 0 && ntohs(dq.dh->arcount) != 1)) {
+  if (ntohs(dq.dh->arcount) == 0) {
+    /* nothing in additional so no EDNS */
+    return false;
+  }
+
+  if (ntohs(dq.dh->ancount) != 0 || ntohs(dq.dh->nscount) != 0 || ntohs(dq.dh->arcount) > 1) {
     return slowParseEDNSOptions(reinterpret_cast<const char*>(dq.dh), dq.len, dq.ednsOptions);
   }
 
index ec2cad6c6029d4577274da8ad9782a15c6e62cd7..395deec2b5e9e00534c9c0445559566790818dea 100644 (file)
@@ -27,44 +27,76 @@ struct dnsdist_ffi_dnsquestion_t;
 
 struct DownstreamState;
 
-struct ServerPolicy
+struct PerThreadPoliciesState;
+
+class ServerPolicy
 {
+public:
   template <class T> using NumberedVector = std::vector<std::pair<unsigned int, T> >;
   using NumberedServerVector = NumberedVector<shared_ptr<DownstreamState>>;
   typedef std::function<shared_ptr<DownstreamState>(const NumberedServerVector& servers, const DNSQuestion*)> policyfunc_t;
   typedef std::function<unsigned int(dnsdist_ffi_servers_list_t* servers, dnsdist_ffi_dnsquestion_t* dq)> ffipolicyfunc_t;
 
-  ServerPolicy(const std::string& name_, policyfunc_t policy_, bool isLua_): name(name_), policy(policy_), isLua(isLua_)
+  ServerPolicy(const std::string& name_, policyfunc_t policy_, bool isLua_): d_name(name_), d_policy(policy_), d_isLua(isLua_)
   {
   }
-  ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_): name(name_), ffipolicy(policy_), isLua(true), isFFI(true)
+
+  ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_): d_name(name_), d_ffipolicy(policy_), d_isLua(true), d_isFFI(true)
   {
   }
+
+  /* create a per-thread FFI policy */
+  ServerPolicy(const std::string& name_, const std::string& code);
+
   ServerPolicy()
   {
   }
 
-  string name;
-  policyfunc_t policy;
-  ffipolicyfunc_t ffipolicy;
-  bool isLua{false};
-  bool isFFI{false};
+  std::shared_ptr<DownstreamState> getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq) const;
+
+  const std::string& getName() const
+  {
+    return d_name;
+  }
 
   std::string toString() const {
-    return string("ServerPolicy") + (isLua ? " (Lua)" : "") + " \"" + name + "\"";
+    return string("ServerPolicy") + (d_isLua ? " (Lua)" : "") + " \"" + d_name + "\"";
   }
+
+private:
+  struct PerThreadState
+  {
+    LuaContext d_luaContext;
+    std::unordered_map<std::string, ffipolicyfunc_t> d_policies;
+    bool d_initialized{false};
+  };
+
+  const ffipolicyfunc_t& getPerThreadPolicy() const;
+  static thread_local PerThreadState t_perThreadState;
+
+
+public:
+  std::string d_name;
+  std::string d_perThreadPolicyCode;
+
+  policyfunc_t d_policy;
+  ffipolicyfunc_t d_ffipolicy;
+
+  bool d_isLua{false};
+  bool d_isFFI{false};
+  bool d_isPerThread{false};
 };
 
 struct ServerPool;
 
-using pools_t=map<std::string,std::shared_ptr<ServerPool>>;
+using pools_t = map<std::string, std::shared_ptr<ServerPool>>;
 std::shared_ptr<ServerPool> getPool(const pools_t& pools, const std::string& poolName);
 std::shared_ptr<ServerPool> createPoolIfNotExists(pools_t& pools, const string& poolName);
 void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<ServerPolicy> policy);
 void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server);
 void removeServerFromPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server);
 
-ServerPolicy::NumberedServerVector getDownstreamCandidates(const map<std::string,std::shared_ptr<ServerPool>>& pools, const std::string& poolName);
+const std::shared_ptr<ServerPolicy::NumberedServerVector> getDownstreamCandidates(const map<std::string,std::shared_ptr<ServerPool>>& pools, const std::string& poolName);
 
 std::shared_ptr<DownstreamState> firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
 
@@ -75,7 +107,6 @@ std::shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedSer
 std::shared_ptr<DownstreamState> chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
 std::shared_ptr<DownstreamState> chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
 std::shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> getSelectedBackendFromPolicy(const ServerPolicy& policy, const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq);
 
 extern double g_consistentHashBalancingFactor;
 extern double g_weightedBalancingFactor;
index 82ae55ffe344ad774302e6a65303964e4c1c6b5d..dbbcaadfda8b0c8bda6057647accb46a9db5d53e 100644 (file)
@@ -520,6 +520,8 @@ private:
   func_t d_func;
 };
 
+thread_local std::default_random_engine SpoofAction::t_randomEngine;
+
 DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
 {
   uint16_t qtype = dq->qtype;
@@ -535,7 +537,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   uint16_t numberOfRecords = 0;
   if (!d_cname.empty()) {
     qtype = QType::CNAME;
-    totrdatalen += d_cname.toDNSString().size();
+    totrdatalen += d_cname.getStorage().size();
     numberOfRecords = 1;
   } else if (!d_rawResponse.empty()) {
     totrdatalen += d_rawResponse.size();
@@ -553,8 +555,9 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     }
   }
 
-  if(addrs.size() > 1)
-    random_shuffle(addrs.begin(), addrs.end());
+  if (addrs.size() > 1) {
+    shuffle(addrs.begin(), addrs.end(), t_randomEngine);
+  }
 
   unsigned int consumed=0;
   DNSName ignore((char*)dq->dh, dq->len, sizeof(dnsheader), false, 0, 0, &consumed);
@@ -589,7 +592,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   bool raw = false;
 
   if (qtype == QType::CNAME) {
-    const std::string wireData = d_cname.toDNSString(); // Note! This doesn't do compression!
+    const auto& wireData = d_cname.getStorage(); // Note! This doesn't do compression!
     uint16_t rdataLen = htons(wireData.length());
     qtype = htons(qtype);
     memcpy(&recordstart[2], &qtype, sizeof(qtype));
@@ -729,7 +732,7 @@ public:
     }
     else {
       if (d_binary) {
-        std::string out = dq->qname->toDNSString();
+        const auto& out = dq->qname->getStorage();
         if (d_includeTimestamp) {
           uint64_t tv_sec = static_cast<uint64_t>(dq->queryTime->tv_sec);
           uint32_t tv_nsec = static_cast<uint32_t>(dq->queryTime->tv_nsec);
@@ -1418,6 +1421,37 @@ private:
   bool d_nxd;
 };
 
+class SetProxyProtocolValuesAction : public DNSAction
+{
+public:
+  SetProxyProtocolValuesAction(const std::vector<std::pair<uint8_t, std::string>>& values)
+  {
+    d_values.reserve(values.size());
+    for (const auto& value : values) {
+      d_values.push_back({value.second, value.first});
+    }
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    if (!dq->proxyProtocolValues) {
+      dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+    }
+
+    *(dq->proxyProtocolValues) = d_values;
+
+    return Action::None;
+  }
+
+  std::string toString() const override
+  {
+    return "set Proxy-Protocol values";
+  }
+
+private:
+  std::vector<ProxyProtocolValue> d_values;
+};
+
 template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
   setLuaSideEffect();
@@ -1471,9 +1505,9 @@ void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
   }
 }
 
-void setupLuaActions()
+void setupLuaActions(LuaContext& luaCtx)
 {
-  g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
+  luaCtx.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
       boost::uuids::uuid uuid;
       uint64_t creationOrder;
       parseRuleParams(params, uuid, creationOrder);
@@ -1483,7 +1517,7 @@ void setupLuaActions()
       return std::make_shared<DNSDistRuleAction>(ra);
     });
 
-  g_lua.writeFunction("addAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
+  luaCtx.writeFunction("addAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
       if (era.type() != typeid(std::shared_ptr<DNSAction>)) {
         throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?");
       }
@@ -1491,7 +1525,7 @@ void setupLuaActions()
       addAction(&g_rulactions, var, boost::get<std::shared_ptr<DNSAction> >(era), params);
     });
 
-  g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
+  luaCtx.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era, boost::optional<luaruleparams_t> params) {
       if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
         throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
       }
@@ -1499,7 +1533,7 @@ void setupLuaActions()
       addAction(&g_resprulactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
     });
 
-  g_lua.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+  luaCtx.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
       if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
         throw std::runtime_error("addCacheHitResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
       }
@@ -1507,7 +1541,7 @@ void setupLuaActions()
       addAction(&g_cachehitresprulactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
     });
 
-  g_lua.writeFunction("addSelfAnsweredResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+  luaCtx.writeFunction("addSelfAnsweredResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
       if (era.type() != typeid(std::shared_ptr<DNSResponseAction>)) {
         throw std::runtime_error("addSelfAnsweredResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
       }
@@ -1515,7 +1549,7 @@ void setupLuaActions()
       addAction(&g_selfansweredresprulactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
     });
 
-  g_lua.registerFunction<void(DNSAction::*)()>("printStats", [](const DNSAction& ta) {
+  luaCtx.registerFunction<void(DNSAction::*)()>("printStats", [](const DNSAction& ta) {
       setLuaNoSideEffect();
       auto stats = ta.getStats();
       for(const auto& s : stats) {
@@ -1527,7 +1561,7 @@ void setupLuaActions()
       }
     });
 
-  g_lua.writeFunction("getAction", [](unsigned int num) {
+  luaCtx.writeFunction("getAction", [](unsigned int num) {
       setLuaNoSideEffect();
       boost::optional<std::shared_ptr<DNSAction>> ret;
       auto rulactions = g_rulactions.getCopy();
@@ -1536,39 +1570,39 @@ void setupLuaActions()
       return ret;
     });
 
-  g_lua.registerFunction("getStats", &DNSAction::getStats);
+  luaCtx.registerFunction("getStats", &DNSAction::getStats);
 
-  g_lua.writeFunction("LuaAction", [](LuaAction::func_t func) {
+  luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) {
       setLuaSideEffect();
       return std::shared_ptr<DNSAction>(new LuaAction(func));
     });
 
-  g_lua.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
+  luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
       setLuaSideEffect();
       return std::shared_ptr<DNSAction>(new LuaFFIAction(func));
     });
 
-  g_lua.writeFunction("NoRecurseAction", []() {
+  luaCtx.writeFunction("NoRecurseAction", []() {
       return std::shared_ptr<DNSAction>(new NoRecurseAction);
     });
 
-  g_lua.writeFunction("MacAddrAction", [](int code) {
+  luaCtx.writeFunction("MacAddrAction", [](int code) {
       return std::shared_ptr<DNSAction>(new MacAddrAction(code));
     });
 
-  g_lua.writeFunction("PoolAction", [](const std::string& a) {
+  luaCtx.writeFunction("PoolAction", [](const std::string& a) {
       return std::shared_ptr<DNSAction>(new PoolAction(a));
     });
 
-  g_lua.writeFunction("QPSAction", [](int limit) {
+  luaCtx.writeFunction("QPSAction", [](int limit) {
       return std::shared_ptr<DNSAction>(new QPSAction(limit));
     });
 
-  g_lua.writeFunction("QPSPoolAction", [](int limit, const std::string& a) {
+  luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& a) {
       return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
     });
 
-  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars) {
       vector<ComboAddress> addrs;
       if(auto s = boost::get<std::string>(&inp))
         addrs.push_back(ComboAddress(*s));
@@ -1587,97 +1621,97 @@ void setupLuaActions()
       return ret;
     });
 
-  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new SpoofAction(DNSName(a)));
       auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
       parseResponseConfig(vars, sa->d_responseConfig);
       return ret;
     });
 
-  g_lua.writeFunction("SpoofRawAction", [](const std::string& raw, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("SpoofRawAction", [](const std::string& raw, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raw));
       auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
       parseResponseConfig(vars, sa->d_responseConfig);
       return ret;
     });
 
-  g_lua.writeFunction("DropAction", []() {
+  luaCtx.writeFunction("DropAction", []() {
       return std::shared_ptr<DNSAction>(new DropAction);
     });
 
-  g_lua.writeFunction("AllowAction", []() {
+  luaCtx.writeFunction("AllowAction", []() {
       return std::shared_ptr<DNSAction>(new AllowAction);
     });
 
-  g_lua.writeFunction("NoneAction", []() {
+  luaCtx.writeFunction("NoneAction", []() {
       return std::shared_ptr<DNSAction>(new NoneAction);
     });
 
-  g_lua.writeFunction("DelayAction", [](int msec) {
+  luaCtx.writeFunction("DelayAction", [](int msec) {
       return std::shared_ptr<DNSAction>(new DelayAction(msec));
     });
 
-  g_lua.writeFunction("TCAction", []() {
+  luaCtx.writeFunction("TCAction", []() {
       return std::shared_ptr<DNSAction>(new TCAction);
     });
 
-  g_lua.writeFunction("DisableValidationAction", []() {
+  luaCtx.writeFunction("DisableValidationAction", []() {
       return std::shared_ptr<DNSAction>(new DisableValidationAction);
     });
 
-  g_lua.writeFunction("LogAction", [](boost::optional<std::string> fname, boost::optional<bool> binary, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
+  luaCtx.writeFunction("LogAction", [](boost::optional<std::string> fname, boost::optional<bool> binary, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
       return std::shared_ptr<DNSAction>(new LogAction(fname ? *fname : "", binary ? *binary : true, append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
     });
 
-  g_lua.writeFunction("LogResponseAction", [](boost::optional<std::string> fname, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
+  luaCtx.writeFunction("LogResponseAction", [](boost::optional<std::string> fname, boost::optional<bool> append, boost::optional<bool> buffered, boost::optional<bool> verboseOnly, boost::optional<bool> includeTimestamp) {
       return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
     });
 
-  g_lua.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
       auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
       parseResponseConfig(vars, rca->d_responseConfig);
       return ret;
     });
 
-  g_lua.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
       auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
       parseResponseConfig(vars, erca->d_responseConfig);
       return ret;
     });
 
-  g_lua.writeFunction("SkipCacheAction", []() {
+  luaCtx.writeFunction("SkipCacheAction", []() {
       return std::shared_ptr<DNSAction>(new SkipCacheAction);
     });
 
-  g_lua.writeFunction("TempFailureCacheTTLAction", [](int maxTTL) {
+  luaCtx.writeFunction("TempFailureCacheTTLAction", [](int maxTTL) {
       return std::shared_ptr<DNSAction>(new TempFailureCacheTTLAction(maxTTL));
     });
 
-  g_lua.writeFunction("DropResponseAction", []() {
+  luaCtx.writeFunction("DropResponseAction", []() {
       return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
     });
 
-  g_lua.writeFunction("AllowResponseAction", []() {
+  luaCtx.writeFunction("AllowResponseAction", []() {
       return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
     });
 
-  g_lua.writeFunction("DelayResponseAction", [](int msec) {
+  luaCtx.writeFunction("DelayResponseAction", [](int msec) {
       return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
     });
 
-  g_lua.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
+  luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
       setLuaSideEffect();
       return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(func));
     });
 
-  g_lua.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
+  luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
       setLuaSideEffect();
       return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(func));
     });
 
-  g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<std::unordered_map<std::string, std::string>> vars) {
+  luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<std::unordered_map<std::string, std::string>> vars) {
       if (logger) {
         // avoids potentially-evaluated-expression warning with clang.
         RemoteLoggerInterface& rl = *logger.get();
@@ -1705,7 +1739,7 @@ void setupLuaActions()
 #endif
     });
 
-  g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME, boost::optional<std::unordered_map<std::string, std::string>> vars) {
+  luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME, boost::optional<std::unordered_map<std::string, std::string>> vars) {
       if (logger) {
         // avoids potentially-evaluated-expression warning with clang.
         RemoteLoggerInterface& rl = *logger.get();
@@ -1733,7 +1767,7 @@ void setupLuaActions()
 #endif
     });
 
-  g_lua.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc) {
+  luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc) {
 #ifdef HAVE_PROTOBUF
       return std::shared_ptr<DNSAction>(new DnstapLogAction(identity, logger, alterFunc));
 #else
@@ -1741,7 +1775,7 @@ void setupLuaActions()
 #endif
     });
 
-  g_lua.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc) {
+  luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc) {
 #ifdef HAVE_PROTOBUF
       return std::shared_ptr<DNSResponseAction>(new DnstapLogResponseAction(identity, logger, alterFunc));
 #else
@@ -1749,30 +1783,30 @@ void setupLuaActions()
 #endif
     });
 
-  g_lua.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS) {
+  luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS) {
       return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false));
     });
 
-  g_lua.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
+  luaCtx.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
       return std::shared_ptr<DNSAction>(new ECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
     });
 
-  g_lua.writeFunction("ECSOverrideAction", [](bool ecsOverride) {
+  luaCtx.writeFunction("ECSOverrideAction", [](bool ecsOverride) {
       return std::shared_ptr<DNSAction>(new ECSOverrideAction(ecsOverride));
     });
 
-  g_lua.writeFunction("DisableECSAction", []() {
+  luaCtx.writeFunction("DisableECSAction", []() {
       return std::shared_ptr<DNSAction>(new DisableECSAction());
     });
 
-  g_lua.writeFunction("SetECSAction", [](const std::string v4, boost::optional<std::string> v6) {
+  luaCtx.writeFunction("SetECSAction", [](const std::string v4, boost::optional<std::string> v6) {
       if (v6) {
         return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4), Netmask(*v6)));
       }
       return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4)));
     });
 
-  g_lua.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
+  luaCtx.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
 #ifdef HAVE_NET_SNMP
       return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
 #else
@@ -1780,7 +1814,7 @@ void setupLuaActions()
 #endif /* HAVE_NET_SNMP */
     });
 
-  g_lua.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
+  luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
 #ifdef HAVE_NET_SNMP
       return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
 #else
@@ -1788,20 +1822,20 @@ void setupLuaActions()
 #endif /* HAVE_NET_SNMP */
     });
 
-  g_lua.writeFunction("TagAction", [](std::string tag, std::string value) {
+  luaCtx.writeFunction("TagAction", [](std::string tag, std::string value) {
       return std::shared_ptr<DNSAction>(new TagAction(tag, value));
     });
 
-  g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) {
+  luaCtx.writeFunction("TagResponseAction", [](std::string tag, std::string value) {
       return std::shared_ptr<DNSResponseAction>(new TagResponseAction(tag, value));
     });
 
-  g_lua.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
+  luaCtx.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
       return std::shared_ptr<DNSAction>(new ContinueAction(action));
     });
 
 #ifdef HAVE_DNS_OVER_HTTPS
-  g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
       auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
       parseResponseConfig(vars, hsa->d_responseConfig);
@@ -1809,14 +1843,18 @@ void setupLuaActions()
     });
 #endif /* HAVE_DNS_OVER_HTTPS */
 
-  g_lua.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
+  luaCtx.writeFunction("KeyValueStoreLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
       return std::shared_ptr<DNSAction>(new KeyValueStoreLookupAction(kvs, lookupKey, destinationTag));
     });
 
-  g_lua.writeFunction("SetNegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional<responseParams_t> vars) {
+  luaCtx.writeFunction("SetNegativeAndSOAAction", [](bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new SetNegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum));
       auto action = std::dynamic_pointer_cast<SetNegativeAndSOAAction>(ret);
       parseResponseConfig(vars, action->d_responseConfig);
       return ret;
     });
+
+  luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
+      return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
+    });
 }
index 71840e7f36481f79cc40cd4353102e3304c6d898..eaa1ef31bc607ec6710e288de002f44c579f2426 100644 (file)
 #include "dnsdist-lua.hh"
 #include "dnsparser.hh"
 
-void setupLuaBindingsDNSQuestion()
+void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
 {
   /* DNSQuestion */
   /* PowerDNS DNSQuestion compat */
-  g_lua.registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
-  g_lua.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; });
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
-  g_lua.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.dh->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.dh->rcode = newRCode; });
-  g_lua.registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.remote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; });
+  luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
+  luaCtx.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; });
+  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
+  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
+  luaCtx.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.dh->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.dh->rcode = newRCode; });
+  luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.remote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; });
   /* DNSDist DNSQuestion */
-  g_lua.registerMember("dh", &DNSQuestion::dh);
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; });
-  g_lua.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
-  g_lua.registerMember<size_t (DNSQuestion::*)>("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; });
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; });
-  g_lua.registerMember<boost::optional<uint32_t> (DNSQuestion::*)>("tempFailureTTL",
+  luaCtx.registerMember("dh", &DNSQuestion::dh);
+  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; });
+  luaCtx.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
+  luaCtx.registerMember<size_t (DNSQuestion::*)>("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; });
+  luaCtx.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
+  luaCtx.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
+  luaCtx.registerMember<bool (DNSQuestion::*)>("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; });
+  luaCtx.registerMember<bool (DNSQuestion::*)>("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; });
+  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; });
+  luaCtx.registerMember<boost::optional<uint32_t> (DNSQuestion::*)>("tempFailureTTL",
       [](const DNSQuestion& dq) -> boost::optional<uint32_t> {
         return dq.tempFailureTTL;
       },
@@ -52,29 +52,29 @@ void setupLuaBindingsDNSQuestion()
         dq.tempFailureTTL = newValue;
       }
     );
-  g_lua.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
+  luaCtx.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
       return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO;
     });
 
-  g_lua.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSQuestion::*)()>("getEDNSOptions", [](DNSQuestion& dq) {
+  luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSQuestion::*)()>("getEDNSOptions", [](DNSQuestion& dq) {
       if (dq.ednsOptions == nullptr) {
         parseEDNSOptions(dq);
       }
 
       return *dq.ednsOptions;
     });
-  g_lua.registerFunction<std::string(DNSQuestion::*)(void)>("getTrailingData", [](const DNSQuestion& dq) {
+  luaCtx.registerFunction<std::string(DNSQuestion::*)(void)>("getTrailingData", [](const DNSQuestion& dq) {
       return dq.getTrailingData();
     });
-  g_lua.registerFunction<bool(DNSQuestion::*)(std::string)>("setTrailingData", [](DNSQuestion& dq, const std::string& tail) {
+  luaCtx.registerFunction<bool(DNSQuestion::*)(std::string)>("setTrailingData", [](DNSQuestion& dq, const std::string& tail) {
       return dq.setTrailingData(tail);
     });
 
-  g_lua.registerFunction<std::string(DNSQuestion::*)()>("getServerNameIndication", [](const DNSQuestion& dq) {
+  luaCtx.registerFunction<std::string(DNSQuestion::*)()>("getServerNameIndication", [](const DNSQuestion& dq) {
       return dq.sni;
     });
 
-  g_lua.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
+  luaCtx.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
 #ifdef HAVE_NET_SNMP
       if (g_snmpAgent && g_snmpTrapsEnabled) {
         g_snmpAgent->sendDNSTrap(dq, reason ? *reason : "");
@@ -82,13 +82,13 @@ void setupLuaBindingsDNSQuestion()
 #endif /* HAVE_NET_SNMP */
     });
 
-  g_lua.registerFunction<void(DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) {
+  luaCtx.registerFunction<void(DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) {
       if(dq.qTag == nullptr) {
         dq.qTag = std::make_shared<QTag>();
       }
       dq.qTag->insert({strLabel, strValue});
     });
-  g_lua.registerFunction<void(DNSQuestion::*)(vector<pair<string, string>>)>("setTagArray", [](DNSQuestion& dq, const vector<pair<string, string>>&tags) {
+  luaCtx.registerFunction<void(DNSQuestion::*)(vector<pair<string, string>>)>("setTagArray", [](DNSQuestion& dq, const vector<pair<string, string>>&tags) {
       if (!dq.qTag) {
         dq.qTag = std::make_shared<QTag>();
       }
@@ -97,7 +97,7 @@ void setupLuaBindingsDNSQuestion()
         dq.qTag->insert({tag.first, tag.second});
       }
     });
-  g_lua.registerFunction<string(DNSQuestion::*)(std::string)>("getTag", [](const DNSQuestion& dq, const std::string& strLabel) {
+  luaCtx.registerFunction<string(DNSQuestion::*)(std::string)>("getTag", [](const DNSQuestion& dq, const std::string& strLabel) {
       if (!dq.qTag) {
         return string();
       }
@@ -109,7 +109,7 @@ void setupLuaBindingsDNSQuestion()
       }
       return it->second;
     });
-  g_lua.registerFunction<QTag(DNSQuestion::*)(void)>("getTagArray", [](const DNSQuestion& dq) {
+  luaCtx.registerFunction<QTag(DNSQuestion::*)(void)>("getTagArray", [](const DNSQuestion& dq) {
       if (!dq.qTag) {
         QTag empty;
         return empty;
@@ -118,37 +118,49 @@ void setupLuaBindingsDNSQuestion()
       return *dq.qTag;
     });
 
+  luaCtx.registerFunction<void(DNSQuestion::*)(std::vector<std::pair<uint8_t, std::string>>)>("setProxyProtocolValues", [](DNSQuestion& dq, const std::vector<std::pair<uint8_t, std::string>>& values) {
+      if (!dq.proxyProtocolValues) {
+        dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+      }
+
+      dq.proxyProtocolValues->clear();
+      dq.proxyProtocolValues->reserve(values.size());
+      for (const auto& value : values) {
+        dq.proxyProtocolValues->push_back({value.second, value.first});
+      }
+    });
+
   /* LuaWrapper doesn't support inheritance */
-  g_lua.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
-  g_lua.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; });
-  g_lua.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
-  g_lua.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
-  g_lua.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.dh->rcode; }, [](DNSResponse& dq, int newRCode) { dq.dh->rcode = newRCode; });
-  g_lua.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
-  g_lua.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.dh; }, [](DNSResponse& dr, dnsheader * newdh) { dr.dh = newdh; });
-  g_lua.registerMember<uint16_t (DNSResponse::*)>("len", [](const DNSResponse& dq) -> uint16_t { return dq.len; }, [](DNSResponse& dq, uint16_t newlen) { dq.len = newlen; });
-  g_lua.registerMember<uint8_t (DNSResponse::*)>("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; });
-  g_lua.registerMember<size_t (DNSResponse::*)>("size", [](const DNSResponse& dq) -> size_t { return dq.size; }, [](DNSResponse& dq, size_t newSize) { (void) newSize; });
-  g_lua.registerMember<bool (DNSResponse::*)>("tcp", [](const DNSResponse& dq) -> bool { return dq.tcp; }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; });
-  g_lua.registerMember<bool (DNSResponse::*)>("skipCache", [](const DNSResponse& dq) -> bool { return dq.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
-  g_lua.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](const DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
+  luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
+  luaCtx.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; });
+  luaCtx.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
+  luaCtx.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
+  luaCtx.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.dh->rcode; }, [](DNSResponse& dq, int newRCode) { dq.dh->rcode = newRCode; });
+  luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
+  luaCtx.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.dh; }, [](DNSResponse& dr, dnsheader * newdh) { dr.dh = newdh; });
+  luaCtx.registerMember<uint16_t (DNSResponse::*)>("len", [](const DNSResponse& dq) -> uint16_t { return dq.len; }, [](DNSResponse& dq, uint16_t newlen) { dq.len = newlen; });
+  luaCtx.registerMember<uint8_t (DNSResponse::*)>("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; });
+  luaCtx.registerMember<size_t (DNSResponse::*)>("size", [](const DNSResponse& dq) -> size_t { return dq.size; }, [](DNSResponse& dq, size_t newSize) { (void) newSize; });
+  luaCtx.registerMember<bool (DNSResponse::*)>("tcp", [](const DNSResponse& dq) -> bool { return dq.tcp; }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; });
+  luaCtx.registerMember<bool (DNSResponse::*)>("skipCache", [](const DNSResponse& dq) -> bool { return dq.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
+  luaCtx.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](const DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
         editDNSPacketTTL((char*) dr.dh, dr.len, editFunc);
       });
-  g_lua.registerFunction<std::string(DNSResponse::*)(void)>("getTrailingData", [](const DNSResponse& dq) {
+  luaCtx.registerFunction<std::string(DNSResponse::*)(void)>("getTrailingData", [](const DNSResponse& dq) {
       return dq.getTrailingData();
     });
-  g_lua.registerFunction<bool(DNSResponse::*)(std::string)>("setTrailingData", [](DNSResponse& dq, const std::string& tail) {
+  luaCtx.registerFunction<bool(DNSResponse::*)(std::string)>("setTrailingData", [](DNSResponse& dq, const std::string& tail) {
       return dq.setTrailingData(tail);
     });
 
-  g_lua.registerFunction<void(DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dr, const std::string& strLabel, const std::string& strValue) {
+  luaCtx.registerFunction<void(DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dr, const std::string& strLabel, const std::string& strValue) {
       if(dr.qTag == nullptr) {
         dr.qTag = std::make_shared<QTag>();
       }
       dr.qTag->insert({strLabel, strValue});
     });
 
-  g_lua.registerFunction<void(DNSResponse::*)(vector<pair<string, string>>)>("setTagArray", [](DNSResponse& dr, const vector<pair<string, string>>&tags) {
+  luaCtx.registerFunction<void(DNSResponse::*)(vector<pair<string, string>>)>("setTagArray", [](DNSResponse& dr, const vector<pair<string, string>>&tags) {
       if (!dr.qTag) {
         dr.qTag = std::make_shared<QTag>();
       }
@@ -157,7 +169,7 @@ void setupLuaBindingsDNSQuestion()
         dr.qTag->insert({tag.first, tag.second});
       }
     });
-  g_lua.registerFunction<string(DNSResponse::*)(std::string)>("getTag", [](const DNSResponse& dr, const std::string& strLabel) {
+  luaCtx.registerFunction<string(DNSResponse::*)(std::string)>("getTag", [](const DNSResponse& dr, const std::string& strLabel) {
       if (!dr.qTag) {
         return string();
       }
@@ -169,7 +181,7 @@ void setupLuaBindingsDNSQuestion()
       }
       return it->second;
     });
-  g_lua.registerFunction<QTag(DNSResponse::*)(void)>("getTagArray", [](const DNSResponse& dr) {
+  luaCtx.registerFunction<QTag(DNSResponse::*)(void)>("getTagArray", [](const DNSResponse& dr) {
       if (!dr.qTag) {
         QTag empty;
         return empty;
@@ -178,7 +190,7 @@ void setupLuaBindingsDNSQuestion()
       return *dr.qTag;
     });
 
-  g_lua.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) {
+  luaCtx.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) {
 #ifdef HAVE_NET_SNMP
       if (g_snmpAgent && g_snmpTrapsEnabled) {
         g_snmpAgent->sendDNSTrap(dr, reason ? *reason : "");
@@ -187,42 +199,42 @@ void setupLuaBindingsDNSQuestion()
     });
 
 #ifdef HAVE_DNS_OVER_HTTPS
-    g_lua.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPPath", [](const DNSQuestion& dq) {
+    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPPath", [](const DNSQuestion& dq) {
       if (dq.du == nullptr) {
         return std::string();
       }
       return dq.du->getHTTPPath();
     });
 
-    g_lua.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPQueryString", [](const DNSQuestion& dq) {
+    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPQueryString", [](const DNSQuestion& dq) {
       if (dq.du == nullptr) {
         return std::string();
       }
       return dq.du->getHTTPQueryString();
     });
 
-    g_lua.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPHost", [](const DNSQuestion& dq) {
+    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPHost", [](const DNSQuestion& dq) {
       if (dq.du == nullptr) {
         return std::string();
       }
       return dq.du->getHTTPHost();
     });
 
-    g_lua.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPScheme", [](const DNSQuestion& dq) {
+    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)>("getHTTPScheme", [](const DNSQuestion& dq) {
       if (dq.du == nullptr) {
         return std::string();
       }
       return dq.du->getHTTPScheme();
     });
 
-    g_lua.registerFunction<std::unordered_map<std::string, std::string>(DNSQuestion::*)(void)>("getHTTPHeaders", [](const DNSQuestion& dq) {
+    luaCtx.registerFunction<std::unordered_map<std::string, std::string>(DNSQuestion::*)(void)>("getHTTPHeaders", [](const DNSQuestion& dq) {
       if (dq.du == nullptr) {
         return std::unordered_map<std::string, std::string>();
       }
       return dq.du->getHTTPHeaders();
     });
 
-    g_lua.registerFunction<void(DNSQuestion::*)(uint16_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dq, uint16_t statusCode, const std::string& body, const boost::optional<std::string> contentType) {
+    luaCtx.registerFunction<void(DNSQuestion::*)(uint16_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dq, uint16_t statusCode, const std::string& body, const boost::optional<std::string> contentType) {
       if (dq.du == nullptr) {
         return;
       }
@@ -230,7 +242,7 @@ void setupLuaBindingsDNSQuestion()
     });
 #endif /* HAVE_DNS_OVER_HTTPS */
 
-  g_lua.registerFunction<bool(DNSQuestion::*)(bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dq, bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) {
+  luaCtx.registerFunction<bool(DNSQuestion::*)(bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dq, bool nxd, const std::string& zone, uint32_t ttl, const std::string& mname, const std::string& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) {
       return setNegativeAndAdditionalSOA(dq, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum);
     });
 }
index 4c26647b075d35e22b06467aa72f712f82973d17..2b348ac59e78ce93d4d985151bd20f025ee6510d 100644 (file)
 
 #include "dolog.hh"
 
-void setupLuaBindings(bool client)
+void setupLuaBindings(LuaContext& luaCtx, bool client)
 {
-  g_lua.writeFunction("infolog", [](const string& arg) {
+  luaCtx.writeFunction("infolog", [](const string& arg) {
       infolog("%s", arg);
     });
-  g_lua.writeFunction("errlog", [](const string& arg) {
+  luaCtx.writeFunction("errlog", [](const string& arg) {
       errlog("%s", arg);
     });
-  g_lua.writeFunction("warnlog", [](const string& arg) {
+  luaCtx.writeFunction("warnlog", [](const string& arg) {
       warnlog("%s", arg);
     });
-  g_lua.writeFunction("show", [](const string& arg) {
+  luaCtx.writeFunction("show", [](const string& arg) {
       g_outputBuffer+=arg;
       g_outputBuffer+="\n";
     });
 
   /* Exceptions */
-  g_lua.registerFunction<string(std::exception_ptr::*)()>("__tostring", [](const std::exception_ptr& eptr) {
+  luaCtx.registerFunction<string(std::exception_ptr::*)()>("__tostring", [](const std::exception_ptr& eptr) {
       try {
         if (eptr) {
           std::rethrow_exception(eptr);
@@ -57,124 +57,126 @@ void setupLuaBindings(bool client)
       return string("No exception");
     });
   /* ServerPolicy */
-  g_lua.writeFunction("newServerPolicy", [](string name, ServerPolicy::policyfunc_t policy) { return std::make_shared<ServerPolicy>(name, policy, true);});
-  g_lua.registerMember("name", &ServerPolicy::name);
-  g_lua.registerMember("policy", &ServerPolicy::policy);
-  g_lua.registerMember("ffipolicy", &ServerPolicy::ffipolicy);
-  g_lua.registerMember("isLua", &ServerPolicy::isLua);
-  g_lua.registerMember("isFFI", &ServerPolicy::isFFI);
-  g_lua.registerFunction("toString", &ServerPolicy::toString);
-
-  g_lua.writeVariable("firstAvailable", ServerPolicy{"firstAvailable", firstAvailable, false});
-  g_lua.writeVariable("roundrobin", ServerPolicy{"roundrobin", roundrobin, false});
-  g_lua.writeVariable("wrandom", ServerPolicy{"wrandom", wrandom, false});
-  g_lua.writeVariable("whashed", ServerPolicy{"whashed", whashed, false});
-  g_lua.writeVariable("chashed", ServerPolicy{"chashed", chashed, false});
-  g_lua.writeVariable("leastOutstanding", ServerPolicy{"leastOutstanding", leastOutstanding, false});
+  luaCtx.writeFunction("newServerPolicy", [](string name, ServerPolicy::policyfunc_t policy) { return std::make_shared<ServerPolicy>(name, policy, true);});
+  luaCtx.registerMember("name", &ServerPolicy::d_name);
+  luaCtx.registerMember("policy", &ServerPolicy::d_policy);
+  luaCtx.registerMember("ffipolicy", &ServerPolicy::d_ffipolicy);
+  luaCtx.registerMember("isLua", &ServerPolicy::d_isLua);
+  luaCtx.registerMember("isFFI", &ServerPolicy::d_isFFI);
+  luaCtx.registerMember("isPerThread", &ServerPolicy::d_isPerThread);
+  luaCtx.registerFunction("toString", &ServerPolicy::toString);
+
+  luaCtx.writeVariable("firstAvailable", ServerPolicy{"firstAvailable", firstAvailable, false});
+  luaCtx.writeVariable("roundrobin", ServerPolicy{"roundrobin", roundrobin, false});
+  luaCtx.writeVariable("wrandom", ServerPolicy{"wrandom", wrandom, false});
+  luaCtx.writeVariable("whashed", ServerPolicy{"whashed", whashed, false});
+  luaCtx.writeVariable("chashed", ServerPolicy{"chashed", chashed, false});
+  luaCtx.writeVariable("leastOutstanding", ServerPolicy{"leastOutstanding", leastOutstanding, false});
 
   /* ServerPool */
-  g_lua.registerFunction<void(std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](std::shared_ptr<ServerPool> pool, std::shared_ptr<DNSDistPacketCache> cache) {
+  luaCtx.registerFunction<void(std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](std::shared_ptr<ServerPool> pool, std::shared_ptr<DNSDistPacketCache> cache) {
       if (pool) {
         pool->packetCache = cache;
       }
     });
-  g_lua.registerFunction("getCache", &ServerPool::getCache);
-  g_lua.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) {
+  luaCtx.registerFunction("getCache", &ServerPool::getCache);
+  luaCtx.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) {
       if (pool) {
         pool->packetCache = nullptr;
       }
     });
-  g_lua.registerFunction("getECS", &ServerPool::getECS);
-  g_lua.registerFunction("setECS", &ServerPool::setECS);
+  luaCtx.registerFunction("getECS", &ServerPool::getECS);
+  luaCtx.registerFunction("setECS", &ServerPool::setECS);
 
   /* DownstreamState */
-  g_lua.registerFunction<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); });
-  g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](std::shared_ptr<DownstreamState> s, string pool) {
+  luaCtx.registerFunction<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); });
+  luaCtx.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](std::shared_ptr<DownstreamState> s, string pool) {
       auto localPools = g_pools.getCopy();
       addServerToPool(localPools, pool, s);
       g_pools.setState(localPools);
       s->pools.insert(pool);
     });
-  g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> s, string pool) {
+  luaCtx.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> s, string pool) {
       auto localPools = g_pools.getCopy();
       removeServerFromPool(localPools, pool, s);
       g_pools.setState(localPools);
       s->pools.erase(pool);
     });
-  g_lua.registerFunction<uint64_t(DownstreamState::*)()>("getOutstanding", [](const DownstreamState& s) { return s.outstanding.load(); });
-  g_lua.registerFunction("isUp", &DownstreamState::isUp);
-  g_lua.registerFunction("setDown", &DownstreamState::setDown);
-  g_lua.registerFunction("setUp", &DownstreamState::setUp);
-  g_lua.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& s, boost::optional<bool> newStatus) {
+  luaCtx.registerFunction<uint64_t(DownstreamState::*)()>("getOutstanding", [](const DownstreamState& s) { return s.outstanding.load(); });
+  luaCtx.registerFunction<double(DownstreamState::*)()>("getLatency", [](const DownstreamState& s) { return s.latencyUsec; });
+  luaCtx.registerFunction("isUp", &DownstreamState::isUp);
+  luaCtx.registerFunction("setDown", &DownstreamState::setDown);
+  luaCtx.registerFunction("setUp", &DownstreamState::setUp);
+  luaCtx.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& s, boost::optional<bool> newStatus) {
       if (newStatus) {
         s.upStatus = *newStatus;
       }
       s.setAuto();
     });
-  g_lua.registerFunction<std::string(DownstreamState::*)()>("getName", [](const DownstreamState& s) { return s.getName(); });
-  g_lua.registerFunction<std::string(DownstreamState::*)()>("getNameWithAddr", [](const DownstreamState& s) { return s.getNameWithAddr(); });
-  g_lua.registerMember("upStatus", &DownstreamState::upStatus);
-  g_lua.registerMember<int (DownstreamState::*)>("weight",
+  luaCtx.registerFunction<std::string(DownstreamState::*)()>("getName", [](const DownstreamState& s) { return s.getName(); });
+  luaCtx.registerFunction<std::string(DownstreamState::*)()>("getNameWithAddr", [](const DownstreamState& s) { return s.getNameWithAddr(); });
+  luaCtx.registerMember("upStatus", &DownstreamState::upStatus);
+  luaCtx.registerMember<int (DownstreamState::*)>("weight",
     [](const DownstreamState& s) -> int {return s.weight;},
     [](DownstreamState& s, int newWeight) {s.setWeight(newWeight);}
   );
-  g_lua.registerMember("order", &DownstreamState::order);
-  g_lua.registerMember<const std::string(DownstreamState::*)>("name", [](const DownstreamState& backend) -> const std::string { return backend.getName(); }, [](DownstreamState& backend, const std::string& newName) { backend.setName(newName); });
-  g_lua.registerFunction<std::string(DownstreamState::*)()>("getID", [](const DownstreamState& s) { return boost::uuids::to_string(s.id); });
+  luaCtx.registerMember("order", &DownstreamState::order);
+  luaCtx.registerMember<const std::string(DownstreamState::*)>("name", [](const DownstreamState& backend) -> const std::string { return backend.getName(); }, [](DownstreamState& backend, const std::string& newName) { backend.setName(newName); });
+  luaCtx.registerFunction<std::string(DownstreamState::*)()>("getID", [](const DownstreamState& s) { return boost::uuids::to_string(s.id); });
 
   /* dnsheader */
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
       dh.rd=v;
     });
 
-  g_lua.registerFunction<bool(dnsheader::*)()>("getRD", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()>("getRD", [](dnsheader& dh) {
       return (bool)dh.rd;
     });
 
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
       dh.ra=v;
     });
 
-  g_lua.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
       return (bool)dh.ra;
     });
 
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
       dh.ad=v;
     });
 
-  g_lua.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
       return (bool)dh.ad;
     });
 
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
       dh.aa=v;
     });
 
-  g_lua.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
       return (bool)dh.aa;
     });
 
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
       dh.cd=v;
     });
 
-  g_lua.registerFunction<bool(dnsheader::*)()>("getCD", [](dnsheader& dh) {
+  luaCtx.registerFunction<bool(dnsheader::*)()>("getCD", [](dnsheader& dh) {
       return (bool)dh.cd;
     });
 
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setTC", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setTC", [](dnsheader& dh, bool v) {
       dh.tc=v;
       if(v) dh.ra = dh.rd; // you'll always need this, otherwise TC=1 gets ignored
     });
 
-  g_lua.registerFunction<void(dnsheader::*)(bool)>("setQR", [](dnsheader& dh, bool v) {
+  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setQR", [](dnsheader& dh, bool v) {
       dh.qr=v;
     });
 
   /* ComboAddress */
-  g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); });
-  g_lua.writeFunction("newCAFromRaw", [](const std::string& raw, boost::optional<uint16_t> port) {
+  luaCtx.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); });
+  luaCtx.writeFunction("newCAFromRaw", [](const std::string& raw, boost::optional<uint16_t> port) {
                                         if (raw.size() == 4) {
                                           struct sockaddr_in sin4;
                                           memset(&sin4, 0, sizeof(sin4));
@@ -197,42 +199,43 @@ void setupLuaBindings(bool client)
                                         }
                                         return ComboAddress();
                                       });
-  g_lua.registerFunction<string(ComboAddress::*)()>("tostring", [](const ComboAddress& ca) { return ca.toString(); });
-  g_lua.registerFunction<string(ComboAddress::*)()>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
-  g_lua.registerFunction<string(ComboAddress::*)()>("toString", [](const ComboAddress& ca) { return ca.toString(); });
-  g_lua.registerFunction<string(ComboAddress::*)()>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
-  g_lua.registerFunction<uint16_t(ComboAddress::*)()>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } );
-  g_lua.registerFunction<void(ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); });
-  g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; });
-  g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; });
-  g_lua.registerFunction<bool(ComboAddress::*)()>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); });
-  g_lua.registerFunction<ComboAddress(ComboAddress::*)()>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); });
-  g_lua.registerFunction<bool(nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); });
+  luaCtx.registerFunction<string(ComboAddress::*)()>("tostring", [](const ComboAddress& ca) { return ca.toString(); });
+  luaCtx.registerFunction<string(ComboAddress::*)()>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
+  luaCtx.registerFunction<string(ComboAddress::*)()>("toString", [](const ComboAddress& ca) { return ca.toString(); });
+  luaCtx.registerFunction<string(ComboAddress::*)()>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
+  luaCtx.registerFunction<uint16_t(ComboAddress::*)()>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } );
+  luaCtx.registerFunction<void(ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); });
+  luaCtx.registerFunction<bool(ComboAddress::*)()>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; });
+  luaCtx.registerFunction<bool(ComboAddress::*)()>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; });
+  luaCtx.registerFunction<bool(ComboAddress::*)()>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); });
+  luaCtx.registerFunction<ComboAddress(ComboAddress::*)()>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); });
+  luaCtx.registerFunction<bool(nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); });
 
   /* DNSName */
-  g_lua.registerFunction("isPartOf", &DNSName::isPartOf);
-  g_lua.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
-  g_lua.registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); });
-  g_lua.registerFunction<size_t(DNSName::*)()>("hash", [](const DNSName& name) { return name.hash(); });
-  g_lua.registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); });
-  g_lua.registerFunction<string(DNSName::*)()>("tostring", [](const DNSName&dn ) { return dn.toString(); });
-  g_lua.registerFunction<string(DNSName::*)()>("toString", [](const DNSName&dn ) { return dn.toString(); });
-  g_lua.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); });
-  g_lua.writeFunction("newDNSNameFromRaw", [](const std::string& name) { return DNSName(name.c_str(), name.size(), 0, false); });
-  g_lua.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); });
-  g_lua.writeFunction("newDNSNameSet", []() { return DNSNameSet(); });
+  luaCtx.registerFunction("isPartOf", &DNSName::isPartOf);
+  luaCtx.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
+  luaCtx.registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); });
+  luaCtx.registerFunction<size_t(DNSName::*)()>("hash", [](const DNSName& name) { return name.hash(); });
+  luaCtx.registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); });
+  luaCtx.registerFunction<string(DNSName::*)()>("tostring", [](const DNSName&dn ) { return dn.toString(); });
+  luaCtx.registerFunction<string(DNSName::*)()>("toString", [](const DNSName&dn ) { return dn.toString(); });
+  luaCtx.registerFunction<string(DNSName::*)()>("toDNSString", [](const DNSName&dn ) { return dn.toDNSString(); });
+  luaCtx.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); });
+  luaCtx.writeFunction("newDNSNameFromRaw", [](const std::string& name) { return DNSName(name.c_str(), name.size(), 0, false); });
+  luaCtx.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); });
+  luaCtx.writeFunction("newDNSNameSet", []() { return DNSNameSet(); });
 
   /* DNSNameSet */
-  g_lua.registerFunction<string(DNSNameSet::*)()>("toString", [](const DNSNameSet&dns ) { return dns.toString(); });
-  g_lua.registerFunction<void(DNSNameSet::*)(DNSName&)>("add", [](DNSNameSet& dns, DNSName& dn) { dns.insert(dn); });
-  g_lua.registerFunction<bool(DNSNameSet::*)(DNSName&)>("check", [](DNSNameSet& dns, DNSName& dn) { return dns.find(dn) != dns.end(); });
-  g_lua.registerFunction("delete",(size_t (DNSNameSet::*)(const DNSName&)) &DNSNameSet::erase);
-  g_lua.registerFunction("size",(size_t (DNSNameSet::*)() const) &DNSNameSet::size);
-  g_lua.registerFunction("clear",(void (DNSNameSet::*)()) &DNSNameSet::clear);
-  g_lua.registerFunction("empty",(bool (DNSNameSet::*)()) &DNSNameSet::empty);
+  luaCtx.registerFunction<string(DNSNameSet::*)()>("toString", [](const DNSNameSet&dns ) { return dns.toString(); });
+  luaCtx.registerFunction<void(DNSNameSet::*)(DNSName&)>("add", [](DNSNameSet& dns, DNSName& dn) { dns.insert(dn); });
+  luaCtx.registerFunction<bool(DNSNameSet::*)(DNSName&)>("check", [](DNSNameSet& dns, DNSName& dn) { return dns.find(dn) != dns.end(); });
+  luaCtx.registerFunction("delete",(size_t (DNSNameSet::*)(const DNSName&)) &DNSNameSet::erase);
+  luaCtx.registerFunction("size",(size_t (DNSNameSet::*)() const) &DNSNameSet::size);
+  luaCtx.registerFunction("clear",(void (DNSNameSet::*)()) &DNSNameSet::clear);
+  luaCtx.registerFunction("empty",(bool (DNSNameSet::*)()) &DNSNameSet::empty);
 
   /* SuffixMatchNode */
-  g_lua.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, vector<pair<int, DNSName>>, vector<pair<int, string>>> &name)>("add", [](SuffixMatchNode &smn, const boost::variant<DNSName, string, vector<pair<int, DNSName>>, vector<pair<int, string>>> &name) {
+  luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, vector<pair<int, DNSName>>, vector<pair<int, string>>> &name)>("add", [](SuffixMatchNode &smn, const boost::variant<DNSName, string, vector<pair<int, DNSName>>, vector<pair<int, string>>> &name) {
       if (name.type() == typeid(DNSName)) {
           auto n = boost::get<DNSName>(name);
           smn.add(n);
@@ -245,23 +248,52 @@ void setupLuaBindings(bool client)
       }
       if (name.type() == typeid(vector<pair<int, DNSName>>)) {
           auto names = boost::get<vector<pair<int, DNSName>>>(name);
-          for (auto const n : names) {
+          for (const auto& n : names) {
             smn.add(n.second);
           }
           return;
       }
       if (name.type() == typeid(vector<pair<int, string>>)) {
           auto names = boost::get<vector<pair<int, string>>>(name);
-          for (auto const n : names) {
+          for (const auto& n : names) {
             smn.add(n.second);
           }
           return;
       }
   });
-  g_lua.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
+  luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, vector<pair<int, DNSName>>, vector<pair<int, string>>> &name)>("remove", [](SuffixMatchNode &smn, const boost::variant<DNSName, string, vector<pair<int, DNSName>>, vector<pair<int, string>>> &name) {
+      if (name.type() == typeid(DNSName)) {
+          auto n = boost::get<DNSName>(name);
+          smn.remove(n);
+          return;
+      }
+      if (name.type() == typeid(string)) {
+          auto n = boost::get<string>(name);
+          DNSName d(n);
+          smn.remove(d);
+          return;
+      }
+      if (name.type() == typeid(vector<pair<int, DNSName>>)) {
+          auto names = boost::get<vector<pair<int, DNSName>>>(name);
+          for (const auto& n : names) {
+            smn.remove(n.second);
+          }
+          return;
+      }
+      if (name.type() == typeid(vector<pair<int, string>>)) {
+          auto names = boost::get<vector<pair<int, string>>>(name);
+          for (const auto& n : names) {
+            DNSName d(n.second);
+            smn.remove(d);
+          }
+          return;
+      }
+  });
+
+  luaCtx.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
 
   /* Netmask */
-  g_lua.writeFunction("newNetmask", [](boost::variant<std::string,ComboAddress> s, boost::optional<uint8_t> bits) {
+  luaCtx.writeFunction("newNetmask", [](boost::variant<std::string,ComboAddress> s, boost::optional<uint8_t> bits) {
     if (s.type() == typeid(ComboAddress)) {
       auto ca = boost::get<ComboAddress>(s);
       if (bits) {
@@ -275,92 +307,92 @@ void setupLuaBindings(bool client)
     }
     throw std::runtime_error("Invalid parameter passed to 'newNetmask()'");
   });
-  g_lua.registerFunction("empty", &Netmask::empty);
-  g_lua.registerFunction("getBits", &Netmask::getBits);
-  g_lua.registerFunction<ComboAddress(Netmask::*)()>("getNetwork", [](const Netmask& nm) { return nm.getNetwork(); } ); // const reference makes this necessary
-  g_lua.registerFunction<ComboAddress(Netmask::*)()>("getMaskedNetwork", [](const Netmask& nm) { return nm.getMaskedNetwork(); } );
-  g_lua.registerFunction("isIpv4", &Netmask::isIPv4);
-  g_lua.registerFunction("isIPv4", &Netmask::isIPv4);
-  g_lua.registerFunction("isIpv6", &Netmask::isIPv6);
-  g_lua.registerFunction("isIPv6", &Netmask::isIPv6);
-  g_lua.registerFunction("match", (bool (Netmask::*)(const string&) const)&Netmask::match);
-  g_lua.registerFunction("toString", &Netmask::toString);
-  g_lua.registerEqFunction(&Netmask::operator==);
-  g_lua.registerToStringFunction(&Netmask::toString);
+  luaCtx.registerFunction("empty", &Netmask::empty);
+  luaCtx.registerFunction("getBits", &Netmask::getBits);
+  luaCtx.registerFunction<ComboAddress(Netmask::*)()>("getNetwork", [](const Netmask& nm) { return nm.getNetwork(); } ); // const reference makes this necessary
+  luaCtx.registerFunction<ComboAddress(Netmask::*)()>("getMaskedNetwork", [](const Netmask& nm) { return nm.getMaskedNetwork(); } );
+  luaCtx.registerFunction("isIpv4", &Netmask::isIPv4);
+  luaCtx.registerFunction("isIPv4", &Netmask::isIPv4);
+  luaCtx.registerFunction("isIpv6", &Netmask::isIPv6);
+  luaCtx.registerFunction("isIPv6", &Netmask::isIPv6);
+  luaCtx.registerFunction("match", (bool (Netmask::*)(const string&) const)&Netmask::match);
+  luaCtx.registerFunction("toString", &Netmask::toString);
+  luaCtx.registerEqFunction(&Netmask::operator==);
+  luaCtx.registerToStringFunction(&Netmask::toString);
 
   /* NetmaskGroup */
-  g_lua.writeFunction("newNMG", []() { return NetmaskGroup(); });
-  g_lua.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
+  luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); });
+  luaCtx.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
                          {
                            nmg.addMask(mask);
                          });
-  g_lua.registerFunction<void(NetmaskGroup::*)(const std::map<ComboAddress,int>& map)>("addMasks", [](NetmaskGroup&nmg, const std::map<ComboAddress,int>& map)
+  luaCtx.registerFunction<void(NetmaskGroup::*)(const std::map<ComboAddress,int>& map)>("addMasks", [](NetmaskGroup&nmg, const std::map<ComboAddress,int>& map)
                          {
                            for (const auto& entry : map) {
                              nmg.addMask(Netmask(entry.first));
                            }
                          });
 
-  g_lua.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
-  g_lua.registerFunction("size", &NetmaskGroup::size);
-  g_lua.registerFunction("clear", &NetmaskGroup::clear);
-  g_lua.registerFunction<string(NetmaskGroup::*)()>("toString", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); });
+  luaCtx.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
+  luaCtx.registerFunction("size", &NetmaskGroup::size);
+  luaCtx.registerFunction("clear", &NetmaskGroup::clear);
+  luaCtx.registerFunction<string(NetmaskGroup::*)()>("toString", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); });
 
   /* QPSLimiter */
-  g_lua.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
-  g_lua.registerFunction("check", &QPSLimiter::check);
+  luaCtx.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
+  luaCtx.registerFunction("check", &QPSLimiter::check);
 
   /* ClientState */
-  g_lua.registerFunction<std::string(ClientState::*)()>("toString", [](const ClientState& fe) {
+  luaCtx.registerFunction<std::string(ClientState::*)()>("toString", [](const ClientState& fe) {
       setLuaNoSideEffect();
       return fe.local.toStringWithPort();
     });
-  g_lua.registerMember("muted", &ClientState::muted);
+  luaCtx.registerMember("muted", &ClientState::muted);
 #ifdef HAVE_EBPF
-  g_lua.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
+  luaCtx.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
       if (bpf) {
         frontend.attachFilter(bpf);
       }
     });
-  g_lua.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
+  luaCtx.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
       frontend.detachFilter();
     });
 #endif /* HAVE_EBPF */
 
   /* BPF Filter */
 #ifdef HAVE_EBPF
-  g_lua.writeFunction("newBPFFilter", [client](uint32_t maxV4, uint32_t maxV6, uint32_t maxQNames) {
+  luaCtx.writeFunction("newBPFFilter", [client](uint32_t maxV4, uint32_t maxV6, uint32_t maxQNames) {
       if (client) {
         return std::shared_ptr<BPFFilter>(nullptr);
       }
       return std::make_shared<BPFFilter>(maxV4, maxV6, maxQNames);
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
       if (bpf) {
         return bpf->block(ca);
       }
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
       if (bpf) {
         return bpf->block(qname, qtype ? *qtype : 255);
       }
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
       if (bpf) {
         return bpf->unblock(ca);
       }
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
       if (bpf) {
         return bpf->unblock(qname, qtype ? *qtype : 255);
       }
     });
 
-  g_lua.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()>("getStats", [](const std::shared_ptr<BPFFilter> bpf) {
+  luaCtx.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()>("getStats", [](const std::shared_ptr<BPFFilter> bpf) {
       setLuaNoSideEffect();
       std::string res;
       if (bpf) {
@@ -381,7 +413,7 @@ void setupLuaBindings(bool client)
       return res;
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
+  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
       std::string res;
       if (bpf) {
         for (const auto& frontend : g_frontends) {
@@ -390,14 +422,14 @@ void setupLuaBindings(bool client)
       }
     });
 
-    g_lua.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter> bpf) {
+    luaCtx.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter> bpf) {
         if (client) {
           return std::shared_ptr<DynBPFFilter>(nullptr);
         }
         return std::make_shared<DynBPFFilter>(bpf);
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
+    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
         if (dbpf) {
           struct timespec until;
           clock_gettime(CLOCK_MONOTONIC, &until);
@@ -406,7 +438,7 @@ void setupLuaBindings(bool client)
         }
     });
 
-    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> dbpf) {
+    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> dbpf) {
         if (dbpf) {
           struct timespec now;
           clock_gettime(CLOCK_MONOTONIC, &now);
@@ -414,7 +446,7 @@ void setupLuaBindings(bool client)
         }
     });
 
-    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeRange", [](std::shared_ptr<DynBPFFilter> dbpf, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
+    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeRange", [](std::shared_ptr<DynBPFFilter> dbpf, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
       if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
         for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
           dbpf->excludeRange(Netmask(range.second));
@@ -425,7 +457,7 @@ void setupLuaBindings(bool client)
       }
     });
 
-    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("includeRange", [](std::shared_ptr<DynBPFFilter> dbpf, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
+    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("includeRange", [](std::shared_ptr<DynBPFFilter> dbpf, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
       if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
         for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
           dbpf->includeRange(Netmask(range.second));
@@ -438,10 +470,10 @@ void setupLuaBindings(bool client)
 #endif /* HAVE_EBPF */
 
   /* EDNSOptionView */
-  g_lua.registerFunction<size_t(EDNSOptionView::*)()>("count", [](const EDNSOptionView& option) {
+  luaCtx.registerFunction<size_t(EDNSOptionView::*)()>("count", [](const EDNSOptionView& option) {
       return option.values.size();
     });
-  g_lua.registerFunction<std::vector<string>(EDNSOptionView::*)()>("getValues", [] (const EDNSOptionView& option) {
+  luaCtx.registerFunction<std::vector<string>(EDNSOptionView::*)()>("getValues", [] (const EDNSOptionView& option) {
     std::vector<string> values;
     for (const auto& value : option.values) {
       values.push_back(std::string(value.content, value.size));
@@ -449,7 +481,7 @@ void setupLuaBindings(bool client)
     return values;
   });
 
-  g_lua.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint16_t status, const std::string& content, boost::optional<std::map<std::string, std::string>> customHeaders) {
+  luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint16_t status, const std::string& content, boost::optional<std::map<std::string, std::string>> customHeaders) {
     boost::optional<std::vector<std::pair<std::string, std::string>>> headers{boost::none};
     if (customHeaders) {
       headers = std::vector<std::pair<std::string, std::string>>();
index d01a007b11033247ed72836144c178f36141ebdc..14028e0d47b890f3dab8b55cb93990461b1f5c6c 100644 (file)
@@ -223,9 +223,9 @@ static counts_t exceedRespByterate(unsigned int rate, int seconds)
                   });
 }
 
-void setupLuaInspection()
+void setupLuaInspection(LuaContext& luaCtx)
 {
-  g_lua.writeFunction("topClients", [](boost::optional<unsigned int> top_) {
+  luaCtx.writeFunction("topClients", [](boost::optional<unsigned int> top_) {
       setLuaNoSideEffect();
       auto top = top_.get_value_or(10);
       map<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan > counts;
@@ -259,7 +259,7 @@ void setupLuaInspection()
       g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0*rest/total : 100.0)).str();
     });
 
-  g_lua.writeFunction("getTopQueries", [](unsigned int top, boost::optional<int> labels) {
+  luaCtx.writeFunction("getTopQueries", [](unsigned int top, boost::optional<int> labels) {
       setLuaNoSideEffect();
       map<DNSName, unsigned int> counts;
       unsigned int total=0;
@@ -307,9 +307,9 @@ void setupLuaInspection()
 
     });
 
-  g_lua.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)");
+  luaCtx.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)");
 
-  g_lua.writeFunction("getResponseRing", []() {
+  luaCtx.writeFunction("getResponseRing", []() {
       setLuaNoSideEffect();
       size_t totalEntries = 0;
       std::vector<boost::circular_buffer<Rings::Response>> rings;
@@ -336,28 +336,28 @@ void setupLuaInspection()
       return ret;
     });
 
-  g_lua.writeFunction("getTopResponses", [](unsigned int top, unsigned int kind, boost::optional<int> labels) {
+  luaCtx.writeFunction("getTopResponses", [](unsigned int top, unsigned int kind, boost::optional<int> labels) {
       return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; });
     });
 
-  g_lua.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
+  luaCtx.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
 
 
-  g_lua.writeFunction("getSlowResponses", [](unsigned int top, unsigned int msec, boost::optional<int> labels) {
+  luaCtx.writeFunction("getSlowResponses", [](unsigned int top, unsigned int msec, boost::optional<int> labels) {
       return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; });
     });
 
 
-  g_lua.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
+  luaCtx.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
 
-  g_lua.writeFunction("getTopBandwidth", [](unsigned int top) {
+  luaCtx.writeFunction("getTopBandwidth", [](unsigned int top) {
       setLuaNoSideEffect();
       return g_rings.getTopBandwidth(top);
     });
 
-  g_lua.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
+  luaCtx.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
 
-  g_lua.writeFunction("delta", []() {
+  luaCtx.writeFunction("delta", []() {
       setLuaNoSideEffect();
       // we hold the lua lock already!
       for(const auto& d : g_confDelta) {
@@ -370,7 +370,7 @@ void setupLuaInspection()
       }
     });
 
-  g_lua.writeFunction("grepq", [](boost::variant<string, vector<pair<int,string> > > inp, boost::optional<unsigned int> limit) {
+  luaCtx.writeFunction("grepq", [](boost::variant<string, vector<pair<int,string> > > inp, boost::optional<unsigned int> limit) {
       setLuaNoSideEffect();
       boost::optional<Netmask>  nm;
       boost::optional<DNSName> dn;
@@ -445,11 +445,18 @@ void setupLuaInspection()
       if(msec==-1) {
         for(const auto& c : qr) {
           bool nmmatch=true, dnmatch=true;
-          if(nm)
+          if (nm) {
             nmmatch = nm->match(c.requestor);
-          if(dn)
-            dnmatch = c.name.isPartOf(*dn);
-          if(nmmatch && dnmatch) {
+          }
+          if (dn) {
+            if (c.name.empty()) {
+              dnmatch = false;
+            }
+            else {
+              dnmatch = c.name.isPartOf(*dn);
+            }
+          }
+          if (nmmatch && dnmatch) {
             QType qt(c.qtype);
             std::string extra;
             if (c.dh.opcode != 0) {
@@ -468,26 +475,40 @@ void setupLuaInspection()
       string extra;
       for(const auto& c : rr) {
         bool nmmatch=true, dnmatch=true, msecmatch=true;
-        if(nm)
+        if (nm) {
           nmmatch = nm->match(c.requestor);
-        if(dn)
-          dnmatch = c.name.isPartOf(*dn);
-        if(msec != -1)
+        }
+        if (dn) {
+          if (c.name.empty()) {
+            dnmatch = false;
+          }
+          else {
+            dnmatch = c.name.isPartOf(*dn);
+          }
+        }
+        if (msec != -1) {
           msecmatch=(c.usec/1000 > (unsigned int)msec);
+        }
 
-        if(nmmatch && dnmatch && msecmatch) {
+        if (nmmatch && dnmatch && msecmatch) {
           QType qt(c.qtype);
-         if(!c.dh.rcode)
+         if (!c.dh.rcode) {
            extra=". " +std::to_string(htons(c.dh.ancount))+ " answers";
-         else
+          }
+         else {
            extra.clear();
-          if(c.usec != std::numeric_limits<decltype(c.usec)>::max())
+          }
+
+          if (c.usec != std::numeric_limits<decltype(c.usec)>::max()) {
             out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString()  % qt.getName()  % (c.usec/1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str()  )) ;
-          else
+          }
+          else {
             out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString()  % qt.getName()  % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str()  )) ;
+          }
 
-          if(limit && *limit==++num)
+          if (limit && *limit == ++num) {
             break;
+          }
         }
       }
 
@@ -496,7 +517,7 @@ void setupLuaInspection()
       }
     });
 
-  g_lua.writeFunction("showResponseLatency", []() {
+  luaCtx.writeFunction("showResponseLatency", []() {
       setLuaNoSideEffect();
       map<double, unsigned int> histo;
       double bin=100;
@@ -554,7 +575,7 @@ void setupLuaInspection()
       }
     });
 
-  g_lua.writeFunction("showTCPStats", [] {
+  luaCtx.writeFunction("showTCPStats", [] {
       setLuaNoSideEffect();
       ostringstream ret;
       boost::format fmt("%-12d %-12d %-12d %-12d");
@@ -590,7 +611,7 @@ void setupLuaInspection()
       g_outputBuffer=ret.str();
     });
 
-  g_lua.writeFunction("showTLSErrorCounters", [] {
+  luaCtx.writeFunction("showTLSErrorCounters", [] {
       setLuaNoSideEffect();
       ostringstream ret;
       boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
@@ -621,7 +642,7 @@ void setupLuaInspection()
       g_outputBuffer=ret.str();
     });
 
-  g_lua.writeFunction("dumpStats", [] {
+  luaCtx.writeFunction("dumpStats", [] {
       setLuaNoSideEffect();
       vector<string> leftcolumn, rightcolumn;
 
@@ -665,21 +686,21 @@ void setupLuaInspection()
       }
     });
 
-  g_lua.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
+  luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
       setLuaNoSideEffect();
       return exceedRCode(rate, seconds, RCode::ServFail);
     });
-  g_lua.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
+  luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
       setLuaNoSideEffect();
       return exceedRCode(rate, seconds, RCode::NXDomain);
     });
 
-  g_lua.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
+  luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
       setLuaNoSideEffect();
       return exceedRespByterate(rate, seconds);
     });
 
-  g_lua.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) {
+  luaCtx.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) {
       setLuaNoSideEffect();
       return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& q) {
          if(q.qtype==type)
@@ -687,71 +708,71 @@ void setupLuaInspection()
        });
     });
 
-  g_lua.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
+  luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
       setLuaNoSideEffect();
       return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& q) {
           counts[q.requestor]++;
        });
     });
 
-  g_lua.writeFunction("getRespRing", getRespRing);
+  luaCtx.writeFunction("getRespRing", getRespRing);
 
   /* StatNode */
-  g_lua.registerFunction<StatNode, unsigned int()>("numChildren",
+  luaCtx.registerFunction<StatNode, unsigned int()>("numChildren",
                                                    [](StatNode& sn) -> unsigned int {
                                                      return sn.children.size();
                                                    } );
-  g_lua.registerMember("fullname", &StatNode::fullname);
-  g_lua.registerMember("labelsCount", &StatNode::labelsCount);
-  g_lua.registerMember("servfails", &StatNode::Stat::servfails);
-  g_lua.registerMember("nxdomains", &StatNode::Stat::nxdomains);
-  g_lua.registerMember("queries", &StatNode::Stat::queries);
-  g_lua.registerMember("noerrors", &StatNode::Stat::noerrors);
-  g_lua.registerMember("drops", &StatNode::Stat::drops);
-  g_lua.registerMember("bytes", &StatNode::Stat::bytes);
-
-  g_lua.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<unsigned int> seconds) {
+  luaCtx.registerMember("fullname", &StatNode::fullname);
+  luaCtx.registerMember("labelsCount", &StatNode::labelsCount);
+  luaCtx.registerMember("servfails", &StatNode::Stat::servfails);
+  luaCtx.registerMember("nxdomains", &StatNode::Stat::nxdomains);
+  luaCtx.registerMember("queries", &StatNode::Stat::queries);
+  luaCtx.registerMember("noerrors", &StatNode::Stat::noerrors);
+  luaCtx.registerMember("drops", &StatNode::Stat::drops);
+  luaCtx.registerMember("bytes", &StatNode::Stat::bytes);
+
+  luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<unsigned int> seconds) {
       statNodeRespRing(visitor, seconds ? *seconds : 0);
     });
 
   /* DynBlockRulesGroup */
-  g_lua.writeFunction("dynBlockRulesGroup", []() { return std::make_shared<DynBlockRulesGroup>(); });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQueryRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+  luaCtx.writeFunction("dynBlockRulesGroup", []() { return std::make_shared<DynBlockRulesGroup>(); });
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQueryRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
       if (group) {
         group->setQueryRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setResponseByteRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setResponseByteRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
       if (group) {
         group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, DynBlockRulesGroup::smtVisitor_t)>("setSuffixMatchRule", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, DynBlockRulesGroup::smtVisitor_t visitor) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, DynBlockRulesGroup::smtVisitor_t)>("setSuffixMatchRule", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, DynBlockRulesGroup::smtVisitor_t visitor) {
       if (group) {
         group->setSuffixMatchRule(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, visitor);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, dnsdist_ffi_stat_node_visitor_t)>("setSuffixMatchRuleFFI", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, dnsdist_ffi_stat_node_visitor_t visitor) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, dnsdist_ffi_stat_node_visitor_t)>("setSuffixMatchRuleFFI", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, dnsdist_ffi_stat_node_visitor_t visitor) {
       if (group) {
         group->setSuffixMatchRuleFFI(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, visitor);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setRCodeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setRCodeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
       if (group) {
         group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
       if (group) {
         group->setRCodeRatio(rcode, ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
       if (group) {
         group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
       if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
         for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
           group->excludeRange(Netmask(range.second));
@@ -761,7 +782,7 @@ void setupLuaInspection()
         group->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
       if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
         for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
           group->includeRange(Netmask(range.second));
@@ -771,7 +792,7 @@ void setupLuaInspection()
         group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> domains) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> domains) {
       if (domains.type() == typeid(std::vector<std::pair<int, std::string>>)) {
         for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&domains)) {
           group->excludeDomain(DNSName(range.second));
@@ -781,9 +802,9 @@ void setupLuaInspection()
         group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
       }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
+  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
     group->apply();
   });
-  g_lua.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
-  g_lua.registerFunction("toString", &DynBlockRulesGroup::toString);
+  luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
+  luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString);
 }
index c33af0d7f166f6307de838b5f9b72f223f13d530..4307b0d1315fb56b566b46c21d2ee4a17ba9d453 100644 (file)
@@ -178,80 +178,80 @@ static void mvRule(GlobalStateHolder<vector<T> > *someRespRulActions, unsigned i
   someRespRulActions->setState(std::move(rules));
 }
 
-void setupLuaRules()
+void setupLuaRules(LuaContext& luaCtx)
 {
-  g_lua.writeFunction("makeRule", makeRule);
+  luaCtx.writeFunction("makeRule", makeRule);
 
-  g_lua.registerFunction<string(std::shared_ptr<DNSRule>::*)()>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
+  luaCtx.registerFunction<string(std::shared_ptr<DNSRule>::*)()>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
 
-  g_lua.writeFunction("showResponseRules", [](boost::optional<ruleparams_t> vars) {
+  luaCtx.writeFunction("showResponseRules", [](boost::optional<ruleparams_t> vars) {
       showRules(&g_resprulactions, vars);
     });
 
-  g_lua.writeFunction("rmResponseRule", [](boost::variant<unsigned int, std::string> id) {
+  luaCtx.writeFunction("rmResponseRule", [](boost::variant<unsigned int, std::string> id) {
       rmRule(&g_resprulactions, id);
     });
 
-  g_lua.writeFunction("topResponseRule", []() {
+  luaCtx.writeFunction("topResponseRule", []() {
       topRule(&g_resprulactions);
     });
 
-  g_lua.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) {
+  luaCtx.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) {
       mvRule(&g_resprulactions, from, to);
     });
 
-  g_lua.writeFunction("showCacheHitResponseRules", [](boost::optional<ruleparams_t> vars) {
+  luaCtx.writeFunction("showCacheHitResponseRules", [](boost::optional<ruleparams_t> vars) {
       showRules(&g_cachehitresprulactions, vars);
     });
 
-  g_lua.writeFunction("rmCacheHitResponseRule", [](boost::variant<unsigned int, std::string> id) {
+  luaCtx.writeFunction("rmCacheHitResponseRule", [](boost::variant<unsigned int, std::string> id) {
       rmRule(&g_cachehitresprulactions, id);
     });
 
-  g_lua.writeFunction("topCacheHitResponseRule", []() {
+  luaCtx.writeFunction("topCacheHitResponseRule", []() {
       topRule(&g_cachehitresprulactions);
     });
 
-  g_lua.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) {
+  luaCtx.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) {
       mvRule(&g_cachehitresprulactions, from, to);
     });
 
-  g_lua.writeFunction("showSelfAnsweredResponseRules", [](boost::optional<ruleparams_t> vars) {
+  luaCtx.writeFunction("showSelfAnsweredResponseRules", [](boost::optional<ruleparams_t> vars) {
       showRules(&g_selfansweredresprulactions, vars);
     });
 
-  g_lua.writeFunction("rmSelfAnsweredResponseRule", [](boost::variant<unsigned int, std::string> id) {
+  luaCtx.writeFunction("rmSelfAnsweredResponseRule", [](boost::variant<unsigned int, std::string> id) {
       rmRule(&g_selfansweredresprulactions, id);
     });
 
-  g_lua.writeFunction("topSelfAnsweredResponseRule", []() {
+  luaCtx.writeFunction("topSelfAnsweredResponseRule", []() {
       topRule(&g_selfansweredresprulactions);
     });
 
-  g_lua.writeFunction("mvSelfAnsweredResponseRule", [](unsigned int from, unsigned int to) {
+  luaCtx.writeFunction("mvSelfAnsweredResponseRule", [](unsigned int from, unsigned int to) {
       mvRule(&g_selfansweredresprulactions, from, to);
     });
 
-  g_lua.writeFunction("rmRule", [](boost::variant<unsigned int, std::string> id) {
+  luaCtx.writeFunction("rmRule", [](boost::variant<unsigned int, std::string> id) {
       rmRule(&g_rulactions, id);
     });
 
-  g_lua.writeFunction("topRule", []() {
+  luaCtx.writeFunction("topRule", []() {
       topRule(&g_rulactions);
     });
 
-  g_lua.writeFunction("mvRule", [](unsigned int from, unsigned int to) {
+  luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int to) {
       mvRule(&g_rulactions, from, to);
     });
 
-  g_lua.writeFunction("clearRules", []() {
+  luaCtx.writeFunction("clearRules", []() {
       setLuaSideEffect();
       g_rulactions.modify([](decltype(g_rulactions)::value_type& rulactions) {
           rulactions.clear();
         });
     });
 
-  g_lua.writeFunction("setRules", [](const std::vector<std::pair<int, std::shared_ptr<DNSDistRuleAction>>>& newruleactions) {
+  luaCtx.writeFunction("setRules", [](const std::vector<std::pair<int, std::shared_ptr<DNSDistRuleAction>>>& newruleactions) {
       setLuaSideEffect();
       g_rulactions.modify([newruleactions](decltype(g_rulactions)::value_type& gruleactions) {
           gruleactions.clear();
@@ -265,52 +265,52 @@ void setupLuaRules()
         });
     });
 
-  g_lua.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<int> ipv4trunc, boost::optional<int> ipv6trunc, boost::optional<int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction) {
+  luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<int> ipv4trunc, boost::optional<int> ipv6trunc, boost::optional<int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction) {
       return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, burst.get_value_or(qps), ipv4trunc.get_value_or(32), ipv6trunc.get_value_or(64), expiration.get_value_or(300), cleanupDelay.get_value_or(60), scanFraction.get_value_or(10)));
     });
 
-  g_lua.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<int> burst) {
+  luaCtx.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<int> burst) {
       if(!burst)
         return std::shared_ptr<DNSRule>(new MaxQPSRule(qps));
       else
         return std::shared_ptr<DNSRule>(new MaxQPSRule(qps, *burst));
     });
 
-  g_lua.writeFunction("RegexRule", [](const std::string& str) {
+  luaCtx.writeFunction("RegexRule", [](const std::string& str) {
       return std::shared_ptr<DNSRule>(new RegexRule(str));
     });
 
 #ifdef HAVE_DNS_OVER_HTTPS
-  g_lua.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) {
+  luaCtx.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) {
       return std::shared_ptr<DNSRule>(new HTTPHeaderRule(header, regex));
     });
-  g_lua.writeFunction("HTTPPathRule", [](const std::string& path) {
+  luaCtx.writeFunction("HTTPPathRule", [](const std::string& path) {
       return std::shared_ptr<DNSRule>(new HTTPPathRule(path));
     });
-  g_lua.writeFunction("HTTPPathRegexRule", [](const std::string& regex) {
+  luaCtx.writeFunction("HTTPPathRegexRule", [](const std::string& regex) {
       return std::shared_ptr<DNSRule>(new HTTPPathRegexRule(regex));
     });
 #endif
 
 #ifdef HAVE_RE2
-  g_lua.writeFunction("RE2Rule", [](const std::string& str) {
+  luaCtx.writeFunction("RE2Rule", [](const std::string& str) {
       return std::shared_ptr<DNSRule>(new RE2Rule(str));
     });
 #endif
 
-  g_lua.writeFunction("SNIRule", [](const std::string& name) {
+  luaCtx.writeFunction("SNIRule", [](const std::string& name) {
       return std::shared_ptr<DNSRule>(new SNIRule(name));
   });
 
-  g_lua.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional<bool> quiet) {
+  luaCtx.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional<bool> quiet) {
       return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
     });
 
-  g_lua.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional<bool> src, boost::optional<bool> quiet) {
+  luaCtx.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional<bool> src, boost::optional<bool> quiet) {
       return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
     });
 
-  g_lua.writeFunction("benchRule", [](std::shared_ptr<DNSRule> rule, boost::optional<int> times_, boost::optional<string> suffix_)  {
+  luaCtx.writeFunction("benchRule", [](std::shared_ptr<DNSRule> rule, boost::optional<int> times_, boost::optional<string> suffix_)  {
       setLuaNoSideEffect();
       int times = times_.get_value_or(100000);
       DNSName suffix(suffix_.get_value_or("powerdns.com"));
@@ -349,19 +349,19 @@ void setupLuaRules()
 
     });
 
-  g_lua.writeFunction("AllRule", []() {
+  luaCtx.writeFunction("AllRule", []() {
       return std::shared_ptr<DNSRule>(new AllRule());
     });
 
-  g_lua.writeFunction("ProbaRule", [](double proba) {
+  luaCtx.writeFunction("ProbaRule", [](double proba) {
       return std::shared_ptr<DNSRule>(new ProbaRule(proba));
     });
 
-  g_lua.writeFunction("QNameRule", [](const std::string& qname) {
+  luaCtx.writeFunction("QNameRule", [](const std::string& qname) {
       return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
     });
 
-  g_lua.writeFunction("QTypeRule", [](boost::variant<int, std::string> str) {
+  luaCtx.writeFunction("QTypeRule", [](boost::variant<int, std::string> str) {
       uint16_t qtype;
       if(auto dir = boost::get<int>(&str)) {
         qtype = *dir;
@@ -375,123 +375,123 @@ void setupLuaRules()
       return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
     });
 
-  g_lua.writeFunction("QClassRule", [](int c) {
+  luaCtx.writeFunction("QClassRule", [](int c) {
       return std::shared_ptr<DNSRule>(new QClassRule(c));
     });
 
-  g_lua.writeFunction("OpcodeRule", [](uint8_t code) {
+  luaCtx.writeFunction("OpcodeRule", [](uint8_t code) {
       return std::shared_ptr<DNSRule>(new OpcodeRule(code));
     });
 
-  g_lua.writeFunction("AndRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
+  luaCtx.writeFunction("AndRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
       return std::shared_ptr<DNSRule>(new AndRule(a));
     });
 
-  g_lua.writeFunction("OrRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
+  luaCtx.writeFunction("OrRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
       return std::shared_ptr<DNSRule>(new OrRule(a));
     });
 
-  g_lua.writeFunction("DSTPortRule", [](uint16_t port) {
+  luaCtx.writeFunction("DSTPortRule", [](uint16_t port) {
       return std::shared_ptr<DNSRule>(new DSTPortRule(port));
     });
 
-  g_lua.writeFunction("TCPRule", [](bool tcp) {
+  luaCtx.writeFunction("TCPRule", [](bool tcp) {
       return std::shared_ptr<DNSRule>(new TCPRule(tcp));
     });
 
-  g_lua.writeFunction("DNSSECRule", []() {
+  luaCtx.writeFunction("DNSSECRule", []() {
       return std::shared_ptr<DNSRule>(new DNSSECRule());
     });
 
-  g_lua.writeFunction("NotRule", [](std::shared_ptr<DNSRule>rule) {
+  luaCtx.writeFunction("NotRule", [](std::shared_ptr<DNSRule>rule) {
       return std::shared_ptr<DNSRule>(new NotRule(rule));
     });
 
-  g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) {
+  luaCtx.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) {
       return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
     });
 
-  g_lua.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) {
+  luaCtx.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) {
       return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
     });
 
-  g_lua.writeFunction("TrailingDataRule", []() {
+  luaCtx.writeFunction("TrailingDataRule", []() {
       return std::shared_ptr<DNSRule>(new TrailingDataRule());
     });
 
-  g_lua.writeFunction("QNameLabelsCountRule", [](unsigned int minLabelsCount, unsigned int maxLabelsCount) {
+  luaCtx.writeFunction("QNameLabelsCountRule", [](unsigned int minLabelsCount, unsigned int maxLabelsCount) {
       return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
     });
 
-  g_lua.writeFunction("QNameWireLengthRule", [](size_t min, size_t max) {
+  luaCtx.writeFunction("QNameWireLengthRule", [](size_t min, size_t max) {
       return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
     });
 
-  g_lua.writeFunction("RCodeRule", [](uint8_t rcode) {
+  luaCtx.writeFunction("RCodeRule", [](uint8_t rcode) {
       return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
     });
 
-  g_lua.writeFunction("ERCodeRule", [](uint8_t rcode) {
+  luaCtx.writeFunction("ERCodeRule", [](uint8_t rcode) {
       return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
     });
 
-  g_lua.writeFunction("EDNSVersionRule", [](uint8_t version) {
+  luaCtx.writeFunction("EDNSVersionRule", [](uint8_t version) {
       return std::shared_ptr<DNSRule>(new EDNSVersionRule(version));
     });
 
-  g_lua.writeFunction("EDNSOptionRule", [](uint16_t optcode) {
+  luaCtx.writeFunction("EDNSOptionRule", [](uint16_t optcode) {
       return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
     });
 
-  g_lua.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
+  luaCtx.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
       showRules(&g_rulactions, vars);
     });
 
-  g_lua.writeFunction("RDRule", []() {
+  luaCtx.writeFunction("RDRule", []() {
       return std::shared_ptr<DNSRule>(new RDRule());
     });
 
-  g_lua.writeFunction("TagRule", [](std::string tag, boost::optional<std::string> value) {
+  luaCtx.writeFunction("TagRule", [](std::string tag, boost::optional<std::string> value) {
       return std::shared_ptr<DNSRule>(new TagRule(tag, value));
     });
 
-  g_lua.writeFunction("TimedIPSetRule", []() {
+  luaCtx.writeFunction("TimedIPSetRule", []() {
       return std::shared_ptr<TimedIPSetRule>(new TimedIPSetRule());
     });
 
-  g_lua.writeFunction("PoolAvailableRule", [](std::string poolname) {
+  luaCtx.writeFunction("PoolAvailableRule", [](std::string poolname) {
     return std::shared_ptr<DNSRule>(new PoolAvailableRule(poolname));
   });
 
-  g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](std::shared_ptr<TimedIPSetRule> tisr) {
+  luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](std::shared_ptr<TimedIPSetRule> tisr) {
       tisr->clear();
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](std::shared_ptr<TimedIPSetRule> tisr) {
+  luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](std::shared_ptr<TimedIPSetRule> tisr) {
       tisr->cleanup();
     });
 
-  g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr<TimedIPSetRule> tisr, const ComboAddress& ca, int t) {
+  luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr<TimedIPSetRule> tisr, const ComboAddress& ca, int t) {
       tisr->add(ca, time(0)+t);
     });
 
-  g_lua.registerFunction<std::shared_ptr<DNSRule>(std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](std::shared_ptr<TimedIPSetRule> tisr) {
+  luaCtx.registerFunction<std::shared_ptr<DNSRule>(std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](std::shared_ptr<TimedIPSetRule> tisr) {
       return std::dynamic_pointer_cast<DNSRule>(tisr);
     });
 
-  g_lua.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
+  luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
       return std::shared_ptr<DNSRule>(new QNameSetRule(names));
     });
 
-  g_lua.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
+  luaCtx.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
       return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
     });
 
-  g_lua.writeFunction("LuaRule", [](LuaRule::func_t func) {
+  luaCtx.writeFunction("LuaRule", [](LuaRule::func_t func) {
       return std::shared_ptr<DNSRule>(new LuaRule(func));
     });
 
-  g_lua.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) {
+  luaCtx.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) {
       return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
     });
 }
index b76a05cfee59bb4f8b37788d1cca600416dfbc28..9e263f7d7a7b0ba140b1fa0b0fc4c89f4ad41377 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "dnsdist.hh"
+#include "dnsdist-lua.hh"
 #include "ednsoptions.hh"
 
 #undef BADSIG  // signal.h SIG_ERR
 
-void setupLuaVars()
+void setupLuaVars(LuaContext& luaCtx)
 {
-  g_lua.writeVariable("DNSAction", std::unordered_map<string,int>{
+  luaCtx.writeVariable("DNSAction", std::unordered_map<string,int>{
       {"Drop", (int)DNSAction::Action::Drop},
       {"Nxdomain", (int)DNSAction::Action::Nxdomain},
       {"Refused", (int)DNSAction::Action::Refused},
@@ -43,7 +44,7 @@ void setupLuaVars()
       {"NoRecurse", (int)DNSAction::Action::NoRecurse}
     });
 
-  g_lua.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
+  luaCtx.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
       {"Allow",        (int)DNSResponseAction::Action::Allow        },
       {"Delay",        (int)DNSResponseAction::Action::Delay        },
       {"Drop",         (int)DNSResponseAction::Action::Drop         },
@@ -52,14 +53,14 @@ void setupLuaVars()
       {"None",         (int)DNSResponseAction::Action::None         }
     });
 
-  g_lua.writeVariable("DNSClass", std::unordered_map<string,int>{
+  luaCtx.writeVariable("DNSClass", std::unordered_map<string,int>{
       {"IN",    QClass::IN    },
       {"CHAOS", QClass::CHAOS },
       {"NONE",  QClass::NONE  },
       {"ANY",   QClass::ANY   }
     });
 
-  g_lua.writeVariable("DNSOpcode", std::unordered_map<string,int>{
+  luaCtx.writeVariable("DNSOpcode", std::unordered_map<string,int>{
       {"Query",  Opcode::Query  },
       {"IQuery", Opcode::IQuery },
       {"Status", Opcode::Status },
@@ -67,14 +68,14 @@ void setupLuaVars()
       {"Update", Opcode::Update }
     });
 
-  g_lua.writeVariable("DNSSection", std::unordered_map<string,int>{
+  luaCtx.writeVariable("DNSSection", std::unordered_map<string,int>{
       {"Question",  0 },
       {"Answer",    1 },
       {"Authority", 2 },
       {"Additional",3 }
     });
 
-  g_lua.writeVariable("EDNSOptionCode", std::unordered_map<string,int>{
+  luaCtx.writeVariable("EDNSOptionCode", std::unordered_map<string,int>{
       {"NSID",         EDNSOptionCode::NSID },
       {"DAU",          EDNSOptionCode::DAU },
       {"DHU",          EDNSOptionCode::DHU },
@@ -88,7 +89,7 @@ void setupLuaVars()
       {"KEYTAG",       EDNSOptionCode::KEYTAG }
     });
 
-  g_lua.writeVariable("DNSRCode", std::unordered_map<string, int>{
+  luaCtx.writeVariable("DNSRCode", std::unordered_map<string, int>{
       {"NOERROR",  RCode::NoError  },
       {"FORMERR",  RCode::FormErr  },
       {"SERVFAIL", RCode::ServFail },
@@ -114,9 +115,9 @@ void setupLuaVars()
   vector<pair<string, int> > dd;
   for(const auto& n : QType::names)
     dd.push_back({n.first, n.second});
-  g_lua.writeVariable("DNSQType", dd);
+  luaCtx.writeVariable("DNSQType", dd);
 
-  g_lua.executeCode(R"LUA(
+  luaCtx.executeCode(R"LUA(
     local tables = {
       DNSQType = DNSQType,
       DNSRCode = DNSRCode
@@ -138,7 +139,7 @@ void setupLuaVars()
   );
 
 #ifdef HAVE_DNSCRYPT
-    g_lua.writeVariable("DNSCryptExchangeVersion", std::unordered_map<string,int>{
+    luaCtx.writeVariable("DNSCryptExchangeVersion", std::unordered_map<string,int>{
         { "VERSION1", DNSCryptExchangeVersion::VERSION1 },
         { "VERSION2", DNSCryptExchangeVersion::VERSION2 },
     });
index 3adf375afd2455111c5c51c8bcfba58ee6dead09..b16d466c873e3815260a1406d806f6b19b1a6fc7 100644 (file)
@@ -41,6 +41,7 @@
 #endif /* LUAJIT_VERSION */
 #include "dnsdist-rings.hh"
 #include "dnsdist-secpoll.hh"
+#include "dnsdist-web.hh"
 
 #include "base64.hh"
 #include "dnswriter.hh"
@@ -62,7 +63,7 @@
 
 using std::thread;
 
-static vector<std::function<void(void)>>* g_launchWork = nullptr;
+static boost::optional<std::vector<std::function<void(void)>>> g_launchWork = boost::none;
 
 boost::tribool g_noLuaSideEffect;
 static bool g_included{false};
@@ -97,7 +98,7 @@ void resetLuaSideEffect()
 
 typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> >, std::vector<std::pair<int, std::string> >, std::map<std::string,std::string>  > > localbind_t;
 
-static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus)
+static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus, int& tcpListenQueueSize)
 {
   if (vars) {
     if (vars->count("reusePort")) {
@@ -106,11 +107,14 @@ static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePor
     if (vars->count("tcpFastOpenQueueSize")) {
       tcpFastOpenQueueSize = boost::get<int>((*vars)["tcpFastOpenQueueSize"]);
     }
+    if (vars->count("tcpListenQueueSize")) {
+      tcpListenQueueSize = boost::get<int>((*vars)["tcpListenQueueSize"]);
+    }
     if (vars->count("interface")) {
       interface = boost::get<std::string>((*vars)["interface"]);
     }
     if (vars->count("cpus")) {
-      for (const auto cpu : boost::get<std::vector<std::pair<int,int>>>((*vars)["cpus"])) {
+      for (const auto& cpu : boost::get<std::vector<std::pair<int,int>>>((*vars)["cpus"])) {
         cpus.insert(cpu.second);
       }
     }
@@ -219,18 +223,18 @@ static void parseTLSConfig(TLSConfig& config, const std::string& context, boost:
 
 #endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
 
-static void setupLuaConfig(bool client, bool configCheck)
+static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 {
   typedef std::unordered_map<std::string, boost::variant<bool, std::string, vector<pair<int, std::string> >, DownstreamState::checkfunc_t > > newserver_t;
-  g_lua.writeFunction("inClientStartup", [client]() {
+  luaCtx.writeFunction("inClientStartup", [client]() {
         return client && !g_configurationDone;
   });
 
-  g_lua.writeFunction("inConfigCheck", [configCheck]() {
+  luaCtx.writeFunction("inConfigCheck", [configCheck]() {
         return !configCheck;
   });
 
-  g_lua.writeFunction("newServer",
+  luaCtx.writeFunction("newServer",
                       [client, configCheck](boost::variant<string,newserver_t> pvars, boost::optional<int> qps) {
       setLuaSideEffect();
 
@@ -437,6 +441,10 @@ static void setupLuaConfig(bool client, bool configCheck)
         ret->useECS=boost::get<bool>(vars["useClientSubnet"]);
       }
 
+      if(vars.count("useProxyProtocol")) {
+        ret->useProxyProtocol = boost::get<bool>(vars["useProxyProtocol"]);
+      }
+
       if(vars.count("disableZeroScope")) {
         ret->disableZeroScope=boost::get<bool>(vars["disableZeroScope"]);
       }
@@ -457,8 +465,12 @@ static void setupLuaConfig(bool client, bool configCheck)
         ret->minRiseSuccesses=std::stoi(boost::get<string>(vars["rise"]));
       }
 
+      if(vars.count("reconnectOnUp")) {
+        ret->reconnectOnUp=boost::get<bool>(vars["reconnectOnUp"]);
+      }
+
       if(vars.count("cpus")) {
-        for (const auto cpu : boost::get<vector<pair<int,string>>>(vars["cpus"])) {
+        for (const auto& cpu : boost::get<vector<pair<int,string>>>(vars["cpus"])) {
           cpus.insert(std::stoi(cpu.second));
         }
       }
@@ -488,7 +500,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       if (ret->connected) {
         ret->threadStarted.test_and_set();
 
-        if(g_launchWork) {
+        if (g_launchWork) {
           g_launchWork->push_back([ret,cpus]() {
                                     ret->tid = thread(responderThread, ret);
                                     if (!cpus.empty()) {
@@ -513,7 +525,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       return ret;
       } );
 
-  g_lua.writeFunction("rmServer",
+  luaCtx.writeFunction("rmServer",
                       [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var)
                       {
                         setLuaSideEffect();
@@ -546,22 +558,23 @@ static void setupLuaConfig(bool client, bool configCheck)
                         g_pools.setState(localPools);
                         states.erase(remove(states.begin(), states.end(), server), states.end());
                         g_dstates.setState(states);
+                        server->stop();
                       } );
 
-  g_lua.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; });
-  g_lua.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; });
+  luaCtx.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; });
+  luaCtx.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; });
 
-  g_lua.writeFunction("addACL", [](const std::string& domain) {
+  luaCtx.writeFunction("addACL", [](const std::string& domain) {
       setLuaSideEffect();
       g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); });
     });
 
-  g_lua.writeFunction("rmACL", [](const std::string& netmask) {
+  luaCtx.writeFunction("rmACL", [](const std::string& netmask) {
     setLuaSideEffect();
     g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); });
   });
 
-  g_lua.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
+  luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
       setLuaSideEffect();
       if(client)
        return;
@@ -571,10 +584,11 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
       bool reusePort = false;
       int tcpFastOpenQueueSize = 0;
+      int tcpListenQueueSize = 0;
       std::string interface;
       std::set<int> cpus;
 
-      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus);
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize);
 
       try {
        ComboAddress loc(addr, 53);
@@ -590,14 +604,18 @@ static void setupLuaConfig(bool client, bool configCheck)
 
         // only works pre-startup, so no sync necessary
         g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus)));
-        g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus)));
+        auto tcpCS = std::unique_ptr<ClientState>(new ClientState(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
+        if (tcpListenQueueSize > 0) {
+          tcpCS->tcpListenQueueSize = tcpListenQueueSize;
+        }
+        g_frontends.push_back(std::move(tcpCS));
       }
       catch(const std::exception& e) {
        g_outputBuffer="Error: "+string(e.what())+"\n";
       }
     });
 
-  g_lua.writeFunction("addLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
+  luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
       setLuaSideEffect();
       if(client)
        return;
@@ -607,16 +625,21 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
       bool reusePort = false;
       int tcpFastOpenQueueSize = 0;
+      int tcpListenQueueSize = 0;
       std::string interface;
       std::set<int> cpus;
 
-      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus);
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize);
 
       try {
        ComboAddress loc(addr, 53);
         // only works pre-startup, so no sync necessary
         g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus)));
-        g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus)));
+        auto tcpCS = std::unique_ptr<ClientState>(new ClientState(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
+        if (tcpListenQueueSize > 0) {
+          tcpCS->tcpListenQueueSize = tcpListenQueueSize;
+        }
+        g_frontends.push_back(std::move(tcpCS));
       }
       catch(std::exception& e) {
         g_outputBuffer="Error: "+string(e.what())+"\n";
@@ -624,7 +647,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setACL", [](boost::variant<string,vector<pair<int, string>>> inp) {
+  luaCtx.writeFunction("setACL", [](boost::variant<string,vector<pair<int, string>>> inp) {
       setLuaSideEffect();
       NetmaskGroup nmg;
       if(auto str = boost::get<string>(&inp)) {
@@ -636,7 +659,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_ACL.setState(nmg);
   });
 
-  g_lua.writeFunction("showACL", []() {
+  luaCtx.writeFunction("showACL", []() {
       setLuaNoSideEffect();
       vector<string> vec;
 
@@ -647,7 +670,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 
     });
 
-  g_lua.writeFunction("shutdown", []() {
+  luaCtx.writeFunction("shutdown", []() {
 #ifdef HAVE_SYSTEMD
       sd_notify(0, "STOPPING=1");
 #endif /* HAVE_SYSTEMD */
@@ -667,7 +690,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 
   typedef std::unordered_map<std::string, boost::variant<bool, std::string> > showserversopts_t;
 
-  g_lua.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
+  luaCtx.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
       setLuaNoSideEffect();
       bool showUUIDs = false;
       if (vars) {
@@ -729,7 +752,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("getServers", []() {
+  luaCtx.writeFunction("getServers", []() {
       setLuaNoSideEffect();
       vector<pair<int, std::shared_ptr<DownstreamState> > > ret;
       int count=1;
@@ -739,11 +762,12 @@ static void setupLuaConfig(bool client, bool configCheck)
       return ret;
     });
 
-  g_lua.writeFunction("getPoolServers", [](string pool) {
-      return getDownstreamCandidates(g_pools.getCopy(), pool);
+  luaCtx.writeFunction("getPoolServers", [](string pool) {
+      const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool);
+      return *poolServers;
     });
 
-  g_lua.writeFunction("getServer", [client](boost::variant<unsigned int, std::string> i) {
+  luaCtx.writeFunction("getServer", [client](boost::variant<unsigned int, std::string> i) {
       if (client) {
         return std::make_shared<DownstreamState>(ComboAddress());
       }
@@ -764,7 +788,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       return std::shared_ptr<DownstreamState>(nullptr);
     });
 
-  g_lua.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName,
+  luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName,
                                         boost::optional<unsigned int> interval, boost::optional<string> namespace_name,
                                          boost::optional<string> instance_name) {
       setLuaSideEffect();
@@ -779,7 +803,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_carbon.setState(ours);
   });
 
-  g_lua.writeFunction("webserver", [client,configCheck](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
+  luaCtx.writeFunction("webserver", [client,configCheck](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders, const boost::optional<std::string> acl) {
       setLuaSideEffect();
       ComboAddress local;
       try {
@@ -798,17 +822,22 @@ static void setupLuaConfig(bool client, bool configCheck)
        SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
        SBind(sock, local);
        SListen(sock, 5);
-       auto launch=[sock, local, password, apiKey, customHeaders]() {
+       auto launch=[sock, local, password, apiKey, customHeaders, acl]() {
           setWebserverPassword(password);
           setWebserverAPIKey(apiKey);
           setWebserverCustomHeaders(customHeaders);
+          if (acl) {
+            setWebserverACL(*acl);
+          }
           thread t(dnsdistWebserverThread, sock, local);
          t.detach();
        };
-       if(g_launchWork)
+       if (g_launchWork) {
          g_launchWork->push_back(launch);
-       else
+        }
+       else {
          launch();
+        }
       }
       catch(std::exception& e) {
        g_outputBuffer="Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what();
@@ -819,7 +848,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 
   typedef std::unordered_map<std::string, boost::variant<std::string, std::map<std::string, std::string>> > webserveropts_t;
 
-  g_lua.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
+  luaCtx.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
       setLuaSideEffect();
 
       if (!vars) {
@@ -835,6 +864,11 @@ static void setupLuaConfig(bool client, bool configCheck)
 
         setWebserverAPIKey(apiKey);
       }
+      if (vars->count("acl")) {
+        const std::string acl = boost::get<std::string>(vars->at("acl"));
+
+        setWebserverACL(acl);
+      }
       if(vars->count("customHeaders")) {
         const boost::optional<std::map<std::string, std::string> > headers = boost::get<std::map<std::string, std::string> >(vars->at("customHeaders"));
 
@@ -842,7 +876,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("controlSocket", [client,configCheck](const std::string& str) {
+  luaCtx.writeFunction("controlSocket", [client,configCheck](const std::string& str) {
       setLuaSideEffect();
       ComboAddress local(str, 5199);
 
@@ -867,11 +901,12 @@ static void setupLuaConfig(bool client, bool configCheck)
            thread t(controlThread, sock, local);
            t.detach();
        };
-       if(g_launchWork)
+       if (g_launchWork) {
          g_launchWork->push_back(launch);
-       else
+        }
+       else {
          launch();
-
+        }
       }
       catch(std::exception& e) {
        g_outputBuffer="Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what();
@@ -879,7 +914,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("addConsoleACL", [](const std::string& netmask) {
+  luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) {
       setLuaSideEffect();
 #ifndef HAVE_LIBSODIUM
       warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications");
@@ -888,7 +923,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
     });
 
-  g_lua.writeFunction("setConsoleACL", [](boost::variant<string,vector<pair<int, string>>> inp) {
+  luaCtx.writeFunction("setConsoleACL", [](boost::variant<string,vector<pair<int, string>>> inp) {
       setLuaSideEffect();
 
 #ifndef HAVE_LIBSODIUM
@@ -905,7 +940,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_consoleACL.setState(nmg);
   });
 
-  g_lua.writeFunction("showConsoleACL", []() {
+  luaCtx.writeFunction("showConsoleACL", []() {
       setLuaNoSideEffect();
 
 #ifndef HAVE_LIBSODIUM
@@ -920,7 +955,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("clearQueryCounters", []() {
+  luaCtx.writeFunction("clearQueryCounters", []() {
       unsigned int size{0};
       {
         WriteLock wl(&g_qcount.queryLock);
@@ -932,7 +967,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_outputBuffer = (fmt % size).str();
     });
 
-  g_lua.writeFunction("getQueryCounters", [](boost::optional<unsigned int> optMax) {
+  luaCtx.writeFunction("getQueryCounters", [](boost::optional<unsigned int> optMax) {
       setLuaNoSideEffect();
       ReadLock rl(&g_qcount.queryLock);
       g_outputBuffer = "query counting is currently: ";
@@ -948,18 +983,18 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled=enabled; });
+  luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled=enabled; });
 
-  g_lua.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
+  luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
       g_qcount.filter = func;
     });
 
-  g_lua.writeFunction("makeKey", []() {
+  luaCtx.writeFunction("makeKey", []() {
       setLuaNoSideEffect();
       g_outputBuffer="setKey("+newKey()+")\n";
     });
 
-  g_lua.writeFunction("setKey", [](const std::string& key) {
+  luaCtx.writeFunction("setKey", [](const std::string& key) {
       if(!g_configurationDone && ! g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
         return;                                     // but later setKeys() trump the -k value again
       }
@@ -977,7 +1012,11 @@ static void setupLuaConfig(bool client, bool configCheck)
        g_consoleKey=newkey;
     });
 
-  g_lua.writeFunction("testCrypto", [](boost::optional<string> optTestMsg)
+  luaCtx.writeFunction("clearConsoleHistory", []() {
+      clearConsoleHistory();
+    });
+
+  luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg)
    {
      setLuaNoSideEffect();
 #ifdef HAVE_LIBSODIUM
@@ -1019,13 +1058,13 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
    });
 
-  g_lua.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout=timeout; });
+  luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout=timeout; });
 
-  g_lua.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout=timeout; });
+  luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout=timeout; });
 
-  g_lua.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout=timeout; });
+  luaCtx.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout=timeout; });
 
-  g_lua.writeFunction("setMaxUDPOutstanding", [](uint16_t max) {
+  luaCtx.writeFunction("setMaxUDPOutstanding", [](uint16_t max) {
       if (!g_configurationDone) {
         g_maxOutstanding = max;
       } else {
@@ -1033,7 +1072,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
+  luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
       if (!g_configurationDone) {
         g_maxTCPClientThreads = max;
       } else {
@@ -1041,7 +1080,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
+  luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
       if (!g_configurationDone) {
         g_maxTCPQueuedConnections = max;
       } else {
@@ -1049,7 +1088,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setMaxTCPQueriesPerConnection", [](size_t max) {
+  luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](size_t max) {
       if (!g_configurationDone) {
         g_maxTCPQueriesPerConn = max;
       } else {
@@ -1057,7 +1096,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setMaxTCPConnectionsPerClient", [](size_t max) {
+  luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](size_t max) {
       if (!g_configurationDone) {
         g_maxTCPConnectionsPerClient = max;
       } else {
@@ -1065,7 +1104,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setMaxTCPConnectionDuration", [](size_t max) {
+  luaCtx.writeFunction("setMaxTCPConnectionDuration", [](size_t max) {
       if (!g_configurationDone) {
         g_maxTCPConnectionDuration = max;
       } else {
@@ -1073,19 +1112,19 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setCacheCleaningDelay", [](uint32_t delay) { g_cacheCleaningDelay = delay; });
+  luaCtx.writeFunction("setCacheCleaningDelay", [](uint32_t delay) { g_cacheCleaningDelay = delay; });
 
-  g_lua.writeFunction("setCacheCleaningPercentage", [](uint16_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; });
+  luaCtx.writeFunction("setCacheCleaningPercentage", [](uint16_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; });
 
-  g_lua.writeFunction("setECSSourcePrefixV4", [](uint16_t prefix) { g_ECSSourcePrefixV4=prefix; });
+  luaCtx.writeFunction("setECSSourcePrefixV4", [](uint16_t prefix) { g_ECSSourcePrefixV4=prefix; });
 
-  g_lua.writeFunction("setECSSourcePrefixV6", [](uint16_t prefix) { g_ECSSourcePrefixV6=prefix; });
+  luaCtx.writeFunction("setECSSourcePrefixV6", [](uint16_t prefix) { g_ECSSourcePrefixV6=prefix; });
 
-  g_lua.writeFunction("setECSOverride", [](bool override) { g_ECSOverride=override; });
+  luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride=override; });
 
-  g_lua.writeFunction("setPreserveTrailingData", [](bool preserve) { g_preserveTrailingData = preserve; });
+  luaCtx.writeFunction("setPreserveTrailingData", [](bool preserve) { g_preserveTrailingData = preserve; });
 
-  g_lua.writeFunction("showDynBlocks", []() {
+  luaCtx.writeFunction("showDynBlocks", []() {
       setLuaNoSideEffect();
       auto slow = g_dynblockNMG.getCopy();
       struct timespec now;
@@ -1108,7 +1147,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 
     });
 
-  g_lua.writeFunction("clearDynBlocks", []() {
+  luaCtx.writeFunction("clearDynBlocks", []() {
       setLuaSideEffect();
       nmts_t nmg;
       g_dynblockNMG.setState(nmg);
@@ -1116,7 +1155,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_dynblockSMT.setState(smt);
     });
 
-  g_lua.writeFunction("addDynBlocks",
+  luaCtx.writeFunction("addDynBlocks",
                       [](const std::unordered_map<ComboAddress,unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& m, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
                            if (m.empty()) {
                              return;
@@ -1149,7 +1188,7 @@ static void setupLuaConfig(bool client, bool configCheck)
                           g_dynblockNMG.setState(slow);
                         });
 
-  g_lua.writeFunction("addDynBlockSMT",
+  luaCtx.writeFunction("addDynBlockSMT",
                       [](const vector<pair<unsigned int, string> >&names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
                            if (names.empty()) {
                              return;
@@ -1186,7 +1225,7 @@ static void setupLuaConfig(bool client, bool configCheck)
                           g_dynblockSMT.setState(slow);
                         });
 
-  g_lua.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
+  luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
       if (!g_configurationDone) {
         if (action == DNSAction::Action::Drop || action == DNSAction::Action::NoOp || action == DNSAction::Action::Nxdomain || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate || action == DNSAction::Action::NoRecurse) {
           g_dynBlockAction = action;
@@ -1200,7 +1239,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles, boost::optional<localbind_t> vars) {
+  luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles, boost::optional<localbind_t> vars) {
       if (g_configurationDone) {
         g_outputBuffer="addDNSCryptBind cannot be used at runtime!\n";
         return;
@@ -1208,11 +1247,12 @@ static void setupLuaConfig(bool client, bool configCheck)
 #ifdef HAVE_DNSCRYPT
       bool reusePort = false;
       int tcpFastOpenQueueSize = 0;
+      int tcpListenQueueSize = 0;
       std::string interface;
       std::set<int> cpus;
       std::vector<DNSCryptContext::CertKeyPaths> certKeys;
 
-      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus);
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize);
 
       if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
         auto certFile = boost::get<std::string>(certFiles);
@@ -1251,6 +1291,10 @@ static void setupLuaConfig(bool client, bool configCheck)
         /* TCP */
         cs = std::unique_ptr<ClientState>(new ClientState(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus));
         cs->dnscryptCtx = ctx;
+        if (tcpListenQueueSize > 0) {
+          cs->tcpListenQueueSize = tcpListenQueueSize;
+        }
+
         g_frontends.push_back(std::move(cs));
       }
       catch(std::exception& e) {
@@ -1263,7 +1307,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 
     });
 
-  g_lua.writeFunction("showDNSCryptBinds", []() {
+  luaCtx.writeFunction("showDNSCryptBinds", []() {
       setLuaNoSideEffect();
 #ifdef HAVE_DNSCRYPT
       ostringstream ret;
@@ -1288,7 +1332,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
     });
 
-  g_lua.writeFunction("getDNSCryptBind", [](size_t idx) {
+  luaCtx.writeFunction("getDNSCryptBind", [](size_t idx) {
       setLuaNoSideEffect();
 #ifdef HAVE_DNSCRYPT
       std::shared_ptr<DNSCryptContext> ret = nullptr;
@@ -1301,12 +1345,12 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
     });
 
-  g_lua.writeFunction("getDNSCryptBindCount", []() {
+  luaCtx.writeFunction("getDNSCryptBindCount", []() {
       setLuaNoSideEffect();
       return g_dnsCryptLocals.size();
     });
 
-  g_lua.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string privateKeyFile) {
+  luaCtx.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string privateKeyFile) {
       setLuaNoSideEffect();
 #ifdef HAVE_DNSCRYPT
       if (client) {
@@ -1341,7 +1385,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
     });
 
-  g_lua.writeFunction("printDNSCryptProviderFingerprint", [](const std::string& publicKeyFile) {
+  luaCtx.writeFunction("printDNSCryptProviderFingerprint", [](const std::string& publicKeyFile) {
       setLuaNoSideEffect();
 #ifdef HAVE_DNSCRYPT
       unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
@@ -1366,7 +1410,7 @@ static void setupLuaConfig(bool client, bool configCheck)
     });
 
 #ifdef HAVE_DNSCRYPT
-  g_lua.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
+  luaCtx.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
       setLuaNoSideEffect();
       if (client) {
         return;
@@ -1387,7 +1431,7 @@ static void setupLuaConfig(bool client, bool configCheck)
     });
 #endif
 
-  g_lua.writeFunction("showPools", []() {
+  luaCtx.writeFunction("showPools", []() {
       setLuaNoSideEffect();
       try {
         ostringstream ret;
@@ -1400,13 +1444,14 @@ static void setupLuaConfig(bool client, bool configCheck)
           const string& name = entry.first;
           const std::shared_ptr<ServerPool> pool = entry.second;
           string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : "";
-          string policy = g_policy.getLocal()->name;
+          string policy = g_policy.getLocal()->getName();
           if (pool->policy != nullptr) {
-            policy = pool->policy->name;
+            policy = pool->policy->getName();
           }
           string servers;
 
-          for (const auto& server: pool->getServers()) {
+          const auto poolServers = pool->getServers();
+          for (const auto& server: *poolServers) {
             if (!servers.empty()) {
               servers += ", ";
             }
@@ -1423,7 +1468,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }catch(std::exception& e) { g_outputBuffer=e.what(); throw; }
     });
 
-  g_lua.writeFunction("getPool", [client](const string& poolName) {
+  luaCtx.writeFunction("getPool", [client](const string& poolName) {
       if (client) {
         return std::make_shared<ServerPool>();
       }
@@ -1433,10 +1478,10 @@ static void setupLuaConfig(bool client, bool configCheck)
       return pool;
     });
 
-  g_lua.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks=verbose; });
-  g_lua.writeFunction("setStaleCacheEntriesTTL", [](uint32_t ttl) { g_staleCacheEntriesTTL = ttl; });
+  luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks=verbose; });
+  luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint32_t ttl) { g_staleCacheEntriesTTL = ttl; });
 
-  g_lua.writeFunction("showBinds", []() {
+  luaCtx.writeFunction("showBinds", []() {
       setLuaNoSideEffect();
       try {
         ostringstream ret;
@@ -1453,7 +1498,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }catch(std::exception& e) { g_outputBuffer=e.what(); throw; }
     });
 
-  g_lua.writeFunction("getBind", [](size_t num) {
+  luaCtx.writeFunction("getBind", [](size_t num) {
       setLuaNoSideEffect();
       ClientState* ret = nullptr;
       if(num < g_frontends.size()) {
@@ -1462,12 +1507,12 @@ static void setupLuaConfig(bool client, bool configCheck)
       return ret;
       });
 
-  g_lua.writeFunction("getBindCount", []() {
+  luaCtx.writeFunction("getBindCount", []() {
       setLuaNoSideEffect();
       return g_frontends.size();
     });
 
-  g_lua.writeFunction("help", [](boost::optional<std::string> command) {
+  luaCtx.writeFunction("help", [](boost::optional<std::string> command) {
       setLuaNoSideEffect();
       g_outputBuffer = "";
       for (const auto& keyword : g_consoleKeywords) {
@@ -1484,18 +1529,18 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("showVersion", []() {
+  luaCtx.writeFunction("showVersion", []() {
       setLuaNoSideEffect();
       g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n";
     });
 
-  g_lua.writeFunction("showSecurityStatus", []() {
+  luaCtx.writeFunction("showSecurityStatus", []() {
       setLuaNoSideEffect();
       g_outputBuffer = std::to_string(g_stats.securityStatus) + "\n";
     });
 
 #ifdef HAVE_EBPF
-  g_lua.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
+  luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
       if (g_configurationDone) {
         g_outputBuffer="setDefaultBPFFilter() cannot be used at runtime!\n";
         return;
@@ -1503,13 +1548,13 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_defaultBPFFilter = bpf;
     });
 
-  g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+  luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
       if (dbpf) {
         g_dynBPFFilters.push_back(dbpf);
       }
     });
 
-  g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+  luaCtx.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
       if (dbpf) {
         for (auto it = g_dynBPFFilters.begin(); it != g_dynBPFFilters.end(); it++) {
           if (*it == dbpf) {
@@ -1520,7 +1565,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map<ComboAddress,unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& m, std::shared_ptr<DynBPFFilter> dynbpf, boost::optional<int> seconds, boost::optional<std::string> msg) {
+  luaCtx.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map<ComboAddress,unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& m, std::shared_ptr<DynBPFFilter> dynbpf, boost::optional<int> seconds, boost::optional<std::string> msg) {
       setLuaSideEffect();
       struct timespec until, now;
       clock_gettime(CLOCK_MONOTONIC, &now);
@@ -1536,7 +1581,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 
 #endif /* HAVE_EBPF */
 
-  g_lua.writeFunction<std::unordered_map<string,uint64_t>()>("getStatisticsCounters", []() {
+  luaCtx.writeFunction<std::unordered_map<string,uint64_t>()>("getStatisticsCounters", []() {
       setLuaNoSideEffect();
       std::unordered_map<string,uint64_t> res;
       for(const auto& entry : g_stats.entries) {
@@ -1546,7 +1591,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       return res;
     });
 
-  g_lua.writeFunction("includeDirectory", [](const std::string& dirname) {
+  luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) {
       if (g_configurationDone) {
         errlog("includeDirectory() cannot be used at runtime!");
         g_outputBuffer="includeDirectory() cannot be used at runtime!\n";
@@ -1610,13 +1655,13 @@ static void setupLuaConfig(bool client, bool configCheck)
           vinfolog("Read configuration from '%s'", *file);
         }
 
-        g_lua.executeCode(ifs);
+        luaCtx.executeCode(ifs);
       }
 
       g_included = false;
     });
 
-  g_lua.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> apiConfigDir) {
+  luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> apiConfigDir) {
       setLuaSideEffect();
       g_apiReadWrite = writable;
       if (apiConfigDir) {
@@ -1630,17 +1675,17 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setServFailWhenNoServer", [](bool servfail) {
+  luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) {
       setLuaSideEffect();
       g_servFailOnNoPolicy = servfail;
     });
 
-  g_lua.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) {
+  luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) {
       setLuaSideEffect();
       g_roundrobinFailOnNoServer = fail;
     });
 
-  g_lua.writeFunction("setConsistentHashingBalancingFactor", [](double factor) {
+  luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) {
       setLuaSideEffect();
       if (factor >= 1.0) {
         g_consistentHashBalancingFactor = factor;
@@ -1652,7 +1697,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setWeightedBalancingFactor", [](double factor) {
+  luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) {
       setLuaSideEffect();
       if (factor >= 1.0) {
         g_weightedBalancingFactor = factor;
@@ -1664,7 +1709,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       }
     });
 
-  g_lua.writeFunction("setRingBuffersSize", [](size_t capacity, boost::optional<size_t> numberOfShards) {
+  luaCtx.writeFunction("setRingBuffersSize", [](size_t capacity, boost::optional<size_t> numberOfShards) {
       setLuaSideEffect();
       if (g_configurationDone) {
         errlog("setRingBuffersSize() cannot be used at runtime!");
@@ -1674,17 +1719,17 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 1);
     });
 
-  g_lua.writeFunction("setRingBuffersLockRetries", [](size_t retries) {
+  luaCtx.writeFunction("setRingBuffersLockRetries", [](size_t retries) {
       setLuaSideEffect();
       g_rings.setNumberOfLockRetries(retries);
     });
 
-  g_lua.writeFunction("setWHashedPertubation", [](uint32_t pertub) {
+  luaCtx.writeFunction("setWHashedPertubation", [](uint32_t pertub) {
       setLuaSideEffect();
       g_hashperturb = pertub;
     });
 
-  g_lua.writeFunction("setTCPUseSinglePipe", [](bool flag) {
+  luaCtx.writeFunction("setTCPUseSinglePipe", [](bool flag) {
       if (g_configurationDone) {
         g_outputBuffer="setTCPUseSinglePipe() cannot be used at runtime!\n";
         return;
@@ -1693,7 +1738,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_useTCPSinglePipe = flag;
     });
 
-  g_lua.writeFunction("snmpAgent", [client,configCheck](bool enableTraps, boost::optional<std::string> masterSocket) {
+  luaCtx.writeFunction("snmpAgent", [client,configCheck](bool enableTraps, boost::optional<std::string> masterSocket) {
       if(client || configCheck)
         return;
 #ifdef HAVE_NET_SNMP
@@ -1718,72 +1763,92 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif /* HAVE_NET_SNMP */
     });
 
-  g_lua.writeFunction("sendCustomTrap", [](const std::string& str) {
+  luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) {
       if (g_snmpAgent && g_snmpTrapsEnabled) {
         g_snmpAgent->sendCustomTrap(str);
       }
     });
 
-  g_lua.writeFunction("setServerPolicy", [](ServerPolicy policy) {
+  luaCtx.writeFunction("setServerPolicy", [](ServerPolicy policy) {
       setLuaSideEffect();
       g_policy.setState(policy);
     });
 
-  g_lua.writeFunction("setServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy) {
+  luaCtx.writeFunction("setServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy) {
       setLuaSideEffect();
       g_policy.setState(ServerPolicy{name, policy, true});
     });
 
-  g_lua.writeFunction("setServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy) {
+  luaCtx.writeFunction("setServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy) {
       setLuaSideEffect();
       auto pol = ServerPolicy(name, policy);
       g_policy.setState(std::move(pol));
     });
 
-  g_lua.writeFunction("showServerPolicy", []() {
+  luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](string name, const std::string& policyCode) {
+      setLuaSideEffect();
+      auto pol = ServerPolicy(name, policyCode);
+      g_policy.setState(std::move(pol));
+    });
+
+  luaCtx.writeFunction("showServerPolicy", []() {
       setLuaSideEffect();
-      g_outputBuffer=g_policy.getLocal()->name+"\n";
+      g_outputBuffer=g_policy.getLocal()->getName()+"\n";
     });
 
-  g_lua.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, string pool) {
+  luaCtx.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, string pool) {
       setLuaSideEffect();
       auto localPools = g_pools.getCopy();
       setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(policy));
       g_pools.setState(localPools);
     });
 
-  g_lua.writeFunction("setPoolServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy, string pool) {
+  luaCtx.writeFunction("setPoolServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy, string pool) {
       setLuaSideEffect();
       auto localPools = g_pools.getCopy();
       setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policy, true}));
       g_pools.setState(localPools);
     });
 
-  g_lua.writeFunction("showPoolServerPolicy", [](string pool) {
+  luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy, string pool) {
+      setLuaSideEffect();
+      auto localPools = g_pools.getCopy();
+      setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policy}));
+      g_pools.setState(localPools);
+    });
+
+  luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](string name, const std::string& policyCode, string pool) {
+      setLuaSideEffect();
+      auto localPools = g_pools.getCopy();
+      setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policyCode}));
+      g_pools.setState(localPools);
+    });
+
+  luaCtx.writeFunction("showPoolServerPolicy", [](string pool) {
       setLuaSideEffect();
       auto localPools = g_pools.getCopy();
       auto poolObj = getPool(localPools, pool);
       if (poolObj->policy == nullptr) {
-        g_outputBuffer=g_policy.getLocal()->name+"\n";
+        g_outputBuffer=g_policy.getLocal()->getName()+"\n";
       } else {
-        g_outputBuffer=poolObj->policy->name+"\n";
+        g_outputBuffer=poolObj->policy->getName()+"\n";
       }
     });
 
-  g_lua.writeFunction("setTCPDownstreamCleanupInterval", [](uint16_t interval) {
+  luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint16_t interval) {
       setLuaSideEffect();
       g_downstreamTCPCleanupInterval = interval;
     });
 
-  g_lua.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
+  luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
       g_logConsoleConnections = enabled;
     });
 
-  g_lua.writeFunction("setConsoleOutputMaxMsgSize", [](uint32_t size) {
+  luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint32_t size) {
       g_consoleOutputMsgMaxSize = size;
     });
 
-  g_lua.writeFunction("setUDPMultipleMessagesVectorSize", [](size_t vSize) {
+  luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](size_t vSize) {
       if (g_configurationDone) {
         errlog("setUDPMultipleMessagesVectorSize() cannot be used at runtime!");
         g_outputBuffer="setUDPMultipleMessagesVectorSize() cannot be used at runtime!\n";
@@ -1798,11 +1863,11 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
     });
 
-  g_lua.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) {
+  luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) {
       g_addEDNSToSelfGeneratedResponses = add;
   });
 
-  g_lua.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint16_t payloadSize) {
+  luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint16_t payloadSize) {
       if (payloadSize < 512) {
         warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!");
         g_outputBuffer="setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!";
@@ -1816,7 +1881,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_PayloadSizeSelfGenAnswers = payloadSize;
   });
 
-  g_lua.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
+  luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
       if (g_configurationDone) {
         g_outputBuffer="setSecurityPollSuffix() cannot be used at runtime!\n";
         return;
@@ -1825,7 +1890,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_secPollSuffix = suffix;
   });
 
-  g_lua.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
+  luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
       if (newInterval <= 0) {
         warnlog("setSecurityPollInterval() should be > 0, skipping");
         g_outputBuffer="setSecurityPollInterval() should be > 0, skipping";
@@ -1834,7 +1899,7 @@ static void setupLuaConfig(bool client, bool configCheck)
       g_secPollInterval = newInterval;
   });
 
-  g_lua.writeFunction("setSyslogFacility", [](int facility) {
+  luaCtx.writeFunction("setSyslogFacility", [](int facility) {
     setLuaSideEffect();
     if (g_configurationDone) {
       g_outputBuffer="setSyslogFacility cannot be used at runtime!\n";
@@ -1843,7 +1908,7 @@ static void setupLuaConfig(bool client, bool configCheck)
     setSyslogFacility(facility);
   });
 
-  g_lua.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::vector<std::pair<int,std::string>>>> certFiles, boost::optional<boost::variant<std::string, std::vector<std::pair<int,std::string>>>> keyFiles, boost::optional<boost::variant<std::string, vector<pair<int, std::string> > > > urls, boost::optional<localbind_t> vars) {
+  luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::vector<std::pair<int,std::string>>>> certFiles, boost::optional<boost::variant<std::string, std::vector<std::pair<int,std::string>>>> keyFiles, boost::optional<boost::variant<std::string, vector<pair<int, std::string> > > > urls, boost::optional<localbind_t> vars) {
     if (client) {
       return;
     }
@@ -1884,11 +1949,12 @@ static void setupLuaConfig(bool client, bool configCheck)
 
     bool reusePort = false;
     int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
     std::string interface;
     std::set<int> cpus;
 
-    if(vars) {
-      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus);
+    if (vars) {
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize);
 
       if (vars->count("idleTimeout")) {
         frontend->d_idleTimeout = boost::get<int>((*vars)["idleTimeout"]);
@@ -1909,18 +1975,30 @@ static void setupLuaConfig(bool client, bool configCheck)
         frontend->d_sendCacheControlHeaders = boost::get<bool>((*vars)["sendCacheControlHeaders"]);
       }
 
+      if (vars->count("trustForwardedForHeader")) {
+        frontend->d_trustForwardedForHeader = boost::get<bool>((*vars)["trustForwardedForHeader"]);
+      }
+
+      if (vars->count("internalPipeBufferSize")) {
+        frontend->d_internalPipeBufferSize = boost::get<int>((*vars)["internalPipeBufferSize"]);
+      }
+
       parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
     }
     g_dohlocals.push_back(frontend);
     auto cs = std::unique_ptr<ClientState>(new ClientState(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
     cs->dohFrontend = frontend;
+    if (tcpListenQueueSize > 0) {
+      cs->tcpListenQueueSize = tcpListenQueueSize;
+    }
+
     g_frontends.push_back(std::move(cs));
 #else
     throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!");
 #endif
   });
 
-  g_lua.writeFunction("showDOHFrontends", []() {
+  luaCtx.writeFunction("showDOHFrontends", []() {
 #ifdef HAVE_DNS_OVER_HTTPS
         setLuaNoSideEffect();
         try {
@@ -1929,7 +2007,7 @@ static void setupLuaConfig(bool client, bool configCheck)
           ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
           size_t counter = 0;
           for (const auto& ctx : g_dohlocals) {
-            ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http1Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
+            ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http2Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
             counter++;
           }
           g_outputBuffer = ret.str();
@@ -1943,7 +2021,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
       });
 
-    g_lua.writeFunction("showDOHResponseCodes", []() {
+    luaCtx.writeFunction("showDOHResponseCodes", []() {
 #ifdef HAVE_DNS_OVER_HTTPS
         setLuaNoSideEffect();
         try {
@@ -1977,7 +2055,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
       });
 
-    g_lua.writeFunction("getDOHFrontend", [client](size_t index) {
+    luaCtx.writeFunction("getDOHFrontend", [client](size_t index) {
         std::shared_ptr<DOHFrontend> result = nullptr;
         if (client) {
           return result;
@@ -2003,30 +2081,30 @@ static void setupLuaConfig(bool client, bool configCheck)
         return result;
       });
 
-    g_lua.writeFunction("getDOHFrontendCount", []() {
+    luaCtx.writeFunction("getDOHFrontendCount", []() {
         setLuaNoSideEffect();
         return g_dohlocals.size();
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<DOHFrontend> frontend) {
+    luaCtx.registerFunction<void(std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<DOHFrontend> frontend) {
         if (frontend != nullptr) {
           frontend->reloadCertificates();
         }
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<DOHFrontend> frontend) {
+    luaCtx.registerFunction<void(std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<DOHFrontend> frontend) {
         if (frontend != nullptr) {
           frontend->rotateTicketsKey(time(nullptr));
         }
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<DOHFrontend> frontend, const std::string& file) {
+    luaCtx.registerFunction<void(std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<DOHFrontend> frontend, const std::string& file) {
         if (frontend != nullptr) {
           frontend->loadTicketsKeys(file);
         }
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)(const std::map<int, std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](std::shared_ptr<DOHFrontend> frontend, const std::map<int, std::shared_ptr<DOHResponseMapEntry>>& map) {
+    luaCtx.registerFunction<void(std::shared_ptr<DOHFrontend>::*)(const std::map<int, std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](std::shared_ptr<DOHFrontend> frontend, const std::map<int, std::shared_ptr<DOHResponseMapEntry>>& map) {
         if (frontend != nullptr) {
           std::vector<std::shared_ptr<DOHResponseMapEntry>> newMap;
           newMap.reserve(map.size());
@@ -2039,7 +2117,7 @@ static void setupLuaConfig(bool client, bool configCheck)
         }
       });
 
-  g_lua.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles, boost::optional<localbind_t> vars) {
+  luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles, boost::optional<localbind_t> vars) {
         if (client) {
           return;
         }
@@ -2057,11 +2135,12 @@ static void setupLuaConfig(bool client, bool configCheck)
 
         bool reusePort = false;
         int tcpFastOpenQueueSize = 0;
+        int tcpListenQueueSize = 0;
         std::string interface;
         std::set<int> cpus;
 
         if (vars) {
-          parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus);
+          parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize);
 
           if (vars->count("provider")) {
             frontend->d_provider = boost::get<const string>((*vars)["provider"]);
@@ -2086,6 +2165,9 @@ static void setupLuaConfig(bool client, bool configCheck)
           // only works pre-startup, so no sync necessary
           auto cs = std::unique_ptr<ClientState>(new ClientState(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
           cs->tlsFrontend = frontend;
+          if (tcpListenQueueSize > 0) {
+            cs->tcpListenQueueSize = tcpListenQueueSize;
+          }
           g_tlslocals.push_back(cs->tlsFrontend);
           g_frontends.push_back(std::move(cs));
         }
@@ -2097,7 +2179,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
       });
 
-    g_lua.writeFunction("showTLSContexts", []() {
+    luaCtx.writeFunction("showTLSContexts", []() {
 #ifdef HAVE_DNS_OVER_TLS
         setLuaNoSideEffect();
         try {
@@ -2121,7 +2203,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
       });
 
-    g_lua.writeFunction("getTLSContext", [](size_t index) {
+    luaCtx.writeFunction("getTLSContext", [](size_t index) {
         std::shared_ptr<TLSCtx> result = nullptr;
 #ifdef HAVE_DNS_OVER_TLS
         setLuaNoSideEffect();
@@ -2144,7 +2226,7 @@ static void setupLuaConfig(bool client, bool configCheck)
         return result;
       });
 
-    g_lua.writeFunction("getTLSFrontend", [](size_t index) {
+    luaCtx.writeFunction("getTLSFrontend", [](size_t index) {
         std::shared_ptr<TLSFrontend> result = nullptr;
 #ifdef HAVE_DNS_OVER_TLS
         setLuaNoSideEffect();
@@ -2167,24 +2249,24 @@ static void setupLuaConfig(bool client, bool configCheck)
         return result;
       });
 
-    g_lua.writeFunction("getTLSFrontendCount", []() {
+    luaCtx.writeFunction("getTLSFrontendCount", []() {
         setLuaNoSideEffect();
         return g_tlslocals.size();
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx> ctx) {
+    luaCtx.registerFunction<void(std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx> ctx) {
         if (ctx != nullptr) {
           ctx->rotateTicketsKey(time(nullptr));
         }
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<TLSCtx>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSCtx> ctx, const std::string& file) {
+    luaCtx.registerFunction<void(std::shared_ptr<TLSCtx>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSCtx> ctx, const std::string& file) {
         if (ctx != nullptr) {
           ctx->loadTicketsKeys(file);
         }
       });
 
-    g_lua.registerFunction<void(std::shared_ptr<TLSFrontend>::*)(boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles) {
+    luaCtx.registerFunction<void(std::shared_ptr<TLSFrontend>::*)(boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles) {
 #ifdef HAVE_DNS_OVER_TLS
         if (loadTLSCertificateAndKeys("loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
           frontend->setupTLS();
@@ -2192,7 +2274,7 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif
       });
 
-    g_lua.writeFunction("reloadAllCertificates", []() {
+    luaCtx.writeFunction("reloadAllCertificates", []() {
         for (auto& frontend : g_frontends) {
           if (!frontend) {
             continue;
@@ -2220,10 +2302,10 @@ static void setupLuaConfig(bool client, bool configCheck)
         }
       });
 
-    g_lua.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse=allow; });
+    luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse=allow; });
 
 #if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN)
-    g_lua.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
+    luaCtx.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
       if (client) {
         return;
       }
@@ -2233,24 +2315,26 @@ static void setupLuaConfig(bool client, bool configCheck)
 #endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN*/
 }
 
-vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const std::string& config)
+vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config)
 {
-  g_launchWork= new vector<std::function<void(void)>>();
-
-  setupLuaActions();
-  setupLuaConfig(client, configCheck);
-  setupLuaBindings(client);
-  setupLuaBindingsDNSCrypt();
-  setupLuaBindingsDNSQuestion();
-  setupLuaBindingsKVS(client);
-  setupLuaBindingsPacketCache();
-  setupLuaBindingsProtoBuf(client, configCheck);
-  setupLuaInspection();
-  setupLuaRules();
-  setupLuaVars();
+  // this needs to exist only during the parsing of the configuration
+  // and cannot be captured by lambdas
+  g_launchWork = std::vector<std::function<void(void)>>();
+
+  setupLuaActions(luaCtx);
+  setupLuaConfig(luaCtx, client, configCheck);
+  setupLuaBindings(luaCtx, client);
+  setupLuaBindingsDNSCrypt(luaCtx);
+  setupLuaBindingsDNSQuestion(luaCtx);
+  setupLuaBindingsKVS(luaCtx, client);
+  setupLuaBindingsPacketCache(luaCtx);
+  setupLuaBindingsProtoBuf(luaCtx, client, configCheck);
+  setupLuaInspection(luaCtx);
+  setupLuaRules(luaCtx);
+  setupLuaVars(luaCtx);
 
 #ifdef LUAJIT_VERSION
-  g_lua.executeCode(getLuaFFIWrappers());
+  luaCtx.executeCode(getLuaFFIWrappers());
 #endif
 
   std::ifstream ifs(config);
@@ -2259,10 +2343,9 @@ vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const
   else
     vinfolog("Read configuration from '%s'", config);
 
-  g_lua.executeCode(ifs);
+  luaCtx.executeCode(ifs);
 
   auto ret = *g_launchWork;
-  delete g_launchWork;
-  g_launchWork = nullptr;
+  g_launchWork = boost::none;
   return ret;
 }
index 65bfec3fdc389032185692e53c89ae5783c6fbc9..4818345e650b0ecbb6916e08b3c19d0da420e749 100644 (file)
@@ -21,6 +21,8 @@
  */
 #pragma once
 
+#include <random>
+
 struct ResponseConfig
 {
   boost::optional<bool> setAA{boost::none};
@@ -78,6 +80,7 @@ public:
 
   ResponseConfig d_responseConfig;
 private:
+  static thread_local std::default_random_engine t_randomEngine;
   std::vector<ComboAddress> d_addrs;
   std::set<uint16_t> d_types;
   std::string d_rawResponse;
@@ -91,14 +94,15 @@ void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid
 
 typedef NetmaskTree<DynBlock> nmts_t;
 
-vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const std::string& config);
-void setupLuaActions();
-void setupLuaBindings(bool client);
-void setupLuaBindingsDNSCrypt();
-void setupLuaBindingsDNSQuestion();
-void setupLuaBindingsKVS(bool client);
-void setupLuaBindingsPacketCache();
-void setupLuaBindingsProtoBuf(bool client, bool configCheck);
-void setupLuaRules();
-void setupLuaInspection();
-void setupLuaVars();
+vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config);
+void setupLuaActions(LuaContext& luaCtx);
+void setupLuaBindings(LuaContext& luaCtx, bool client);
+void setupLuaBindingsDNSCrypt(LuaContext& luaCtx);
+void setupLuaBindingsDNSQuestion(LuaContext& luaCtx);
+void setupLuaBindingsKVS(LuaContext& luaCtx, bool client);
+void setupLuaBindingsPacketCache(LuaContext& luaCtx);
+void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck);
+void setupLuaRules(LuaContext& luaCtx);
+void setupLuaInspection(LuaContext& luaCtx);
+void setupLuaVars(LuaContext& luaCtx);
+void setupLuaLoadBalancingContext(LuaContext& luaCtx);
index 40f8ac2dc46b0bc7154a2f8ef7cb74c126560e77..71c669202f6d21ffe237008d9b5020220b4e59e2 100644 (file)
@@ -21,6 +21,7 @@
  */
 #include "dnsdist.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-xpf.hh"
 
@@ -172,6 +173,27 @@ public:
     return d_enableFastOpen;
   }
 
+  bool canBeReused() const
+  {
+    /* we can't reuse a connection where a proxy protocol payload has been sent,
+       since:
+       - it cannot be reused for a different client
+       - we might have different TLV values for each query
+    */
+    if (d_ds && d_ds->useProxyProtocol) {
+      return false;
+    }
+    return true;
+  }
+
+  bool matches(const std::shared_ptr<DownstreamState>& ds) const
+  {
+    if (!ds || !d_ds) {
+      return false;
+    }
+    return ds == d_ds;
+  }
+
 private:
   std::unique_ptr<Socket> d_socket{nullptr};
   std::shared_ptr<DownstreamState> d_ds{nullptr};
@@ -207,6 +229,11 @@ static void releaseDownstreamConnection(std::unique_ptr<TCPConnectionToBackend>&
     return;
   }
 
+  if (!conn->canBeReused()) {
+    conn.reset();
+    return;
+  }
+
   const auto& remote = conn->getRemote();
   const auto& it = t_downstreamConnections.find(remote);
   if (it != t_downstreamConnections.end()) {
@@ -368,7 +395,7 @@ static void cleanupClosedTCPConnections()
    would block.
 */
 // XXX could probably be implemented as a TCPIOHandler
-IOState tryRead(int fd, std::vector<uint8_t>& buffer, size_t& pos, size_t toRead)
+static IOState tryRead(int fd, std::vector<uint8_t>& buffer, size_t& pos, size_t toRead)
 {
   if (buffer.size() < (pos + toRead)) {
     throw std::out_of_range("Calling tryRead() with a too small buffer (" + std::to_string(buffer.size()) + ") for a read of " + std::to_string(toRead) + " bytes starting at " + std::to_string(pos));
@@ -653,6 +680,8 @@ public:
   bool d_isXFR{false};
   bool d_xfrStarted{false};
   bool d_selfGeneratedResponse{false};
+  bool d_proxyProtocolPayloadAdded{false};
+  bool d_proxyProtocolPayloadHasTLV{false};
 };
 
 static void handleIOCallback(int fd, FDMultiplexer::funcparam_t& param);
@@ -790,7 +819,6 @@ static void sendQueryToBackend(std::shared_ptr<IncomingTCPConnectionState>& stat
   state->d_state = IncomingTCPConnectionState::State::sendingQueryToBackend;
   state->d_currentPos = 0;
   state->d_firstResponsePacket = true;
-  state->d_downstreamConnection.reset();
 
   if (state->d_xfrStarted) {
     /* sorry, but we are not going to resume a XFR if we have already sent some packets
@@ -798,20 +826,29 @@ static void sendQueryToBackend(std::shared_ptr<IncomingTCPConnectionState>& stat
     return;
   }
 
-  if (state->d_downstreamFailures < state->d_ds->retries) {
-    try {
-      state->d_downstreamConnection = getConnectionToDownstream(ds, state->d_downstreamFailures, now);
+  if (!state->d_downstreamConnection) {
+    if (state->d_downstreamFailures < state->d_ds->retries) {
+      try {
+        state->d_downstreamConnection = getConnectionToDownstream(ds, state->d_downstreamFailures, now);
+      }
+      catch (const std::runtime_error& e) {
+        state->d_downstreamConnection.reset();
+      }
     }
-    catch (const std::runtime_error& e) {
-      state->d_downstreamConnection.reset();
+
+    if (!state->d_downstreamConnection) {
+      ++ds->tcpGaveUp;
+      ++state->d_ci.cs->tcpGaveUp;
+      vinfolog("Downstream connection to %s failed %d times in a row, giving up.", ds->getName(), state->d_downstreamFailures);
+      return;
     }
-  }
 
-  if (!state->d_downstreamConnection) {
-    ++ds->tcpGaveUp;
-    ++state->d_ci.cs->tcpGaveUp;
-    vinfolog("Downstream connection to %s failed %d times in a row, giving up.", ds->getName(), state->d_downstreamFailures);
-    return;
+    if (ds->useProxyProtocol && !state->d_proxyProtocolPayloadAdded) {
+      /* we know there is no TLV values to add, otherwise we would not have tried
+         to reuse the connection and d_proxyProtocolPayloadAdded would be true already */
+      addProxyProtocol(state->d_buffer, true, state->d_ci.remote, state->d_ids.origDest, std::vector<ProxyProtocolValue>());
+      state->d_proxyProtocolPayloadAdded = true;
+    }
   }
 
   vinfolog("Got query for %s|%s from %s (%s), relayed to %s", state->d_ids.qname.toLogString(), QType(state->d_ids.qtype).getName(), state->d_ci.remote.toStringWithPort(), (state->d_ci.cs->tlsFrontend ? "DoT" : "TCP"), ds->getName());
@@ -828,6 +865,7 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, stru
   }
 
   state->d_readingFirstQuery = false;
+  state->d_proxyProtocolPayloadAdded = false;
   ++state->d_queriesCount;
   ++state->d_ci.cs->queries;
   ++g_stats.queries;
@@ -905,7 +943,6 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, stru
     return;
   }
 
-  state->d_buffer.resize(dq.len);
   setIDStateFromDNSQuestion(state->d_ids, dq, std::move(qname));
 
   const uint8_t sizeBytes[] = { static_cast<uint8_t>(dq.len / 256), static_cast<uint8_t>(dq.len % 256) };
@@ -913,6 +950,30 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, stru
      that could occur if we had to deal with the size during the processing,
      especially alignment issues */
   state->d_buffer.insert(state->d_buffer.begin(), sizeBytes, sizeBytes + 2);
+  dq.len = dq.len + 2;
+  dq.dh = reinterpret_cast<dnsheader*>(&state->d_buffer.at(0));
+  dq.size = state->d_buffer.size();
+  state->d_buffer.resize(dq.len);
+
+  if (state->d_ds->useProxyProtocol) {
+    /* if we ever sent a TLV over a connection, we can never go back */
+    if (!state->d_proxyProtocolPayloadHasTLV) {
+      state->d_proxyProtocolPayloadHasTLV = dq.proxyProtocolValues && !dq.proxyProtocolValues->empty();
+    }
+
+    if (state->d_downstreamConnection && !state->d_proxyProtocolPayloadHasTLV && state->d_downstreamConnection->matches(state->d_ds)) {
+      /* we have an existing connection, on which we already sent a Proxy Protocol header with no values
+         (in the previous query had TLV values we would have reset the connection afterwards),
+         so let's reuse it as long as we still don't have any values */
+      state->d_proxyProtocolPayloadAdded = false;
+    }
+    else {
+      state->d_downstreamConnection.reset();
+      addProxyProtocol(state->d_buffer, true, state->d_ci.remote, state->d_ids.origDest, dq.proxyProtocolValues ? *dq.proxyProtocolValues : std::vector<ProxyProtocolValue>());
+      state->d_proxyProtocolPayloadAdded = true;
+    }
+  }
+
   sendQueryToBackend(state, now);
 }
 
@@ -1023,7 +1084,20 @@ static void handleDownstreamIO(std::shared_ptr<IncomingTCPConnectionState>& stat
           /* but don't reset it either, we will need to read more messages */
         }
         else {
-          releaseDownstreamConnection(std::move(state->d_downstreamConnection));
+          /* if we did not send a Proxy Protocol header, let's pool the connection */
+          if (state->d_ds && state->d_ds->useProxyProtocol == false) {
+            releaseDownstreamConnection(std::move(state->d_downstreamConnection));
+          }
+          else {
+            if (state->d_proxyProtocolPayloadHasTLV) {
+              /* sent a Proxy Protocol header with TLV values, we can't reuse it */
+              state->d_downstreamConnection.reset();
+            }
+            else {
+              /* if we did but there was no TLV values, let's try to reuse it but only
+                 for this incoming connection */
+            }
+          }
         }
         fd = -1;
 
@@ -1082,6 +1156,7 @@ static void handleDownstreamIO(std::shared_ptr<IncomingTCPConnectionState>& stat
   }
 
   if (connectionDied) {
+    state->d_downstreamConnection.reset();
     sendQueryToBackend(state, now);
   }
 }
@@ -1330,10 +1405,10 @@ void tcpClientThread(int pipefd)
 /* spawn as many of these as required, they call Accept on a socket on which they will accept queries, and
    they will hand off to worker threads & spawn more of them if required
 */
-void tcpAcceptorThread(void* p)
+void tcpAcceptorThread(ClientState* cs)
 {
   setThreadName("dnsdist/tcpAcce");
-  ClientState* cs = (ClientState*) p;
+
   bool tcpClientCountIncremented = false;
   ComboAddress remote;
   remote.sin4.sin_family = cs->local.sin4.sin_family;
index 797a5730f432018f9146a0a6abf9c93357fe92ff..00da6abe1f89aff2ad5f48f3fb89cf3760b87d5b 100644 (file)
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
-#include "dnsdist.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-prometheus.hh"
 
-#include "sstuff.hh"
-#include "ext/json11/json11.hpp"
-#include "ext/incbin/incbin.h"
-#include "dolog.hh"
-#include <thread>
-#include "threadname.hh"
+#include <boost/format.hpp>
 #include <sstream>
-#include <yahttp/yahttp.hpp>
-#include "namespaces.hh"
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <thread>
+
 #include "ext/incbin/incbin.h"
-#include "htmlfiles.h"
+#include "ext/json11/json11.hpp"
+#include <yahttp/yahttp.hpp>
+
 #include "base64.hh"
+#include "dnsdist.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-prometheus.hh"
+#include "dnsdist-web.hh"
+#include "dolog.hh"
 #include "gettime.hh"
-#include  <boost/format.hpp>
+#include "htmlfiles.h"
+#include "threadname.hh"
+#include "sstuff.hh"
 
 bool g_apiReadWrite{false};
 WebserverConfig g_webserverConfig;
@@ -88,6 +89,8 @@ const std::map<std::string, MetricDefinition> MetricDefinitionStorage::metrics{
   { "dyn-blocked",            MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a dynamic block")},
   { "dyn-block-nmg-size",     MetricDefinition(PrometheusMetricType::gauge,   "Number of dynamic blocks entries") },
   { "security-status",        MetricDefinition(PrometheusMetricType::gauge,   "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory") },
+  { "doh-query-pipe-full",    MetricDefinition(PrometheusMetricType::counter, "Number of DoH queries dropped because the internal pipe used to distribute queries was full") },
+  { "doh-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of DoH responses dropped because the internal pipe used to distribute responses was full") },
   { "udp-in-errors",          MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InErrors") },
   { "udp-noport-errors",      MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp NoPorts") },
   { "udp-recvbuf-errors",     MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp RcvbufErrors") },
@@ -221,6 +224,12 @@ static bool isMethodAllowed(const YaHTTP::Request& req)
   return false;
 }
 
+static bool isClientAllowedByACL(const ComboAddress& remote)
+{
+  std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
+  return g_webserverConfig.acl.match(remote);
+}
+
 static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
 {
   const auto origin = req.headers.find("Origin");
@@ -372,7 +381,7 @@ static void connectionThread(int sock, ComboAddress remote)
           { "packetcache-misses", 0},
           { "over-capacity-drops", 0 },
           { "too-old-drops", 0 },
-          { "server-policy", g_policy.getLocal()->name}
+          { "server-policy", g_policy.getLocal()->getName()}
         };
 
         for(const auto& e : g_stats.entries) {
@@ -500,7 +509,7 @@ static void connectionThread(int sock, ComboAddress remote)
         }
 
         // Latency histogram buckets
-        output << "# HELP dnsdist_latency Histogram of responses by latency\n";
+        output << "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
         output << "# TYPE dnsdist_latency histogram\n";
         uint64_t latency_amounts = g_stats.latency0_1;
         output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
@@ -627,7 +636,7 @@ static void connectionThread(int sock, ComboAddress remote)
           if (front->udpFD == -1 && front->tcpFD == -1)
             continue;
 
-          const string frontName = front->local.toString() + ":" + std::to_string(front->local.getPort());
+          const string frontName = front->local.toStringWithPort();
           const string proto = front->getType();
           const string fullName = frontName + "_" + proto;
           uint64_t threadNumber = 0;
@@ -795,6 +804,10 @@ static void connectionThread(int sock, ComboAddress remote)
           }
         }
 
+        output << "# HELP dnsdist_info " << "Info from dnsdist, value is always 1" << "\n";
+        output << "# TYPE dnsdist_info " << "gauge" << "\n";
+        output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n";
+
         resp.body = output.str();
         resp.headers["Content-Type"] = "text/plain";
     }
@@ -1070,7 +1083,7 @@ static void connectionThread(int sock, ComboAddress remote)
         { "ecs-source-prefix-v6", (double)  g_ECSSourcePrefixV6 },
         { "fixup-case", g_fixupCase },
         { "max-outstanding", (double) g_maxOutstanding },
-        { "server-policy", g_policy.getLocal()->name },
+        { "server-policy", g_policy.getLocal()->getName() },
         { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL },
         { "tcp-recv-timeout", (double) g_tcpRecvTimeout },
         { "tcp-send-timeout", (double) g_tcpSendTimeout },
@@ -1226,6 +1239,17 @@ void setWebserverPassword(const std::string& password)
   g_webserverConfig.password = password;
 }
 
+void setWebserverACL(const std::string& acl)
+{
+  NetmaskGroup newACL;
+  newACL.toMasks(acl);
+
+  {
+    std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
+    g_webserverConfig.acl = std::move(newACL);
+  }
+}
+
 void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders)
 {
   std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
@@ -1237,15 +1261,21 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local)
 {
   setThreadName("dnsdist/webserv");
   warnlog("Webserver launched on %s", local.toStringWithPort());
+
   for(;;) {
     try {
       ComboAddress remote(local);
       int fd = SAccept(sock, remote);
-      vinfolog("Got connection from %s", remote.toStringWithPort());
+      if (!isClientAllowedByACL(remote)) {
+        vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort());
+        close(fd);
+        continue;
+      }
+      vinfolog("Got a connection to the webserver from %s", remote.toStringWithPort());
       std::thread t(connectionThread, fd, remote);
       t.detach();
     }
-    catch(std::exception& e) {
+    catch (const std::exception& e) {
       errlog("Had an error accepting new webserver connection: %s", e.what());
     }
   }
index 5c5803b1c728cf6a0dacb81d5865202b11b5b7be..ac24be4c58718bc8f7040930161c108aa382f934 100644 (file)
@@ -32,6 +32,8 @@
 #include <unistd.h>
 
 #if defined (__OpenBSD__) || defined(__NetBSD__)
+// If this is not undeffed, __attribute__ wil be redefined by /usr/include/readline/rlstdc.h
+#undef __STRICT_ANSI__
 #include <readline/readline.h>
 #else
 #include <editline/readline.h>
@@ -48,6 +50,7 @@
 #include "dnsdist-ecs.hh"
 #include "dnsdist-healthchecks.hh"
 #include "dnsdist-lua.hh"
+#include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-secpoll.hh"
 #include "dnsdist-xpf.hh"
@@ -239,6 +242,11 @@ bool responseContentMatches(const char* response, const uint16_t responseLen, co
   }
 
   const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(response);
+  if (dh->qr == 0) {
+    ++g_stats.nonCompliantResponses;
+    return false;
+  }
+
   if (dh->qdcount == 0) {
     if ((dh->rcode != RCode::NoError && dh->rcode != RCode::NXDomain) || g_allowEmptyResponse) {
       return true;
@@ -303,8 +311,8 @@ static bool fixUpResponse(char** response, uint16_t* responseLen, size_t* respon
     return true;
   }
 
-  if(g_fixupCase) {
-    string realname = qname.toDNSString();
+  if (g_fixupCase) {
+    const auto& realname = qname.getStorage();
     if (*responseLen >= (sizeof(dnsheader) + realname.length())) {
       memcpy(*response + sizeof(dnsheader), realname.c_str(), realname.length());
     }
@@ -544,7 +552,7 @@ try {
   std::vector<int> sockets;
   sockets.reserve(dss->sockets.size());
 
-  for(;;) {
+  for(; !dss->isStopped(); ) {
     dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet);
     try {
       pickBackendSocketsReadyForReceiving(dss, sockets);
@@ -553,8 +561,13 @@ try {
         char * response = packet;
         size_t responseSize = sizeof(packet);
 
-        if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader))
+        if (got == 0 && dss->isStopped()) {
+          break;
+        }
+
+        if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader)) {
           continue;
+        }
 
         uint16_t responseLen = static_cast<uint16_t>(got);
         queryId = dh->id;
@@ -632,7 +645,17 @@ try {
 #ifdef HAVE_DNS_OVER_HTTPS
             // DoH query
             du->response = std::string(response, responseLen);
-            if (send(du->rsock, &du, sizeof(du), 0) != sizeof(du)) {
+            static_assert(sizeof(du) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+            ssize_t sent = write(du->rsock, &du, sizeof(du));
+            if (sent != sizeof(du)) {
+              if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                ++g_stats.dohResponsePipeFull;
+                vinfolog("Unable to pass a DoH response to the DoH worker thread because the pipe is full");
+              }
+              else {
+                vinfolog("Unable to pass a DoH response to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+              }
+
               /* at this point we have the only remaining pointer on this
                  DOHUnit object since we did set ids->du to nullptr earlier,
                  except if we got the response before the pointer could be
@@ -1175,8 +1198,8 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
     if (serverPool->policy != nullptr) {
       policy = *(serverPool->policy);
     }
-    auto servers = serverPool->getServers();
-    selectedBackend = getSelectedBackendFromPolicy(policy, servers, dq);
+    const auto servers = serverPool->getServers();
+    selectedBackend = policy.getSelectedBackend(*servers, dq);
 
     uint16_t cachedResponseSize = dq.size;
     uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
@@ -1367,6 +1390,10 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
 
     dh->id = idOffset;
 
+    if (ss->useProxyProtocol) {
+      addProxyProtocol(dq);
+    }
+
     int fd = pickBackendSocketForSending(ss);
     ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
 
@@ -1409,7 +1436,7 @@ static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holde
   /* initialize the structures needed to receive our messages */
   for (size_t idx = 0; idx < vectSize; idx++) {
     recvData[idx].remote.sin4.sin_family = cs->local.sin4.sin_family;
-    fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), recvData[idx].packet, s_udpIncomingBufferSize, &recvData[idx].remote);
+    fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), recvData[idx].packet, cs->dnscryptCtx ? sizeof(recvData[idx].packet) : s_udpIncomingBufferSize, &recvData[idx].remote);
   }
 
   /* go now */
@@ -1493,7 +1520,7 @@ try
     ComboAddress remote;
     ComboAddress dest;
     remote.sin4.sin_family = cs->local.sin4.sin_family;
-    fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), packet, s_udpIncomingBufferSize, &remote);
+    fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), packet, cs->dnscryptCtx ? sizeof(packet) : s_udpIncomingBufferSize, &remote);
 
     for(;;) {
       ssize_t got = recvmsg(cs->udpFD, &msgh, 0);
@@ -1533,7 +1560,7 @@ uint64_t g_maxTCPClientThreads{10};
 std::atomic<uint16_t> g_cacheCleaningDelay{60};
 std::atomic<uint16_t> g_cacheCleaningPercentage{100};
 
-void maintThread()
+static void maintThread()
 {
   setThreadName("dnsdist/main");
   int interval = 1;
@@ -1829,24 +1856,23 @@ static void setUpLocalBind(std::unique_ptr<ClientState>& cs)
   }
 
   if (cs->reuseport) {
-#ifdef SO_REUSEPORT
-    SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT, 1);
-#else
-    if (warn) {
-      /* no need to warn again if configured but support is not available, we already did for UDP */
-      warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", cs->local.toStringWithPort());
+    if (!setReusePort(fd)) {
+      if (warn) {
+        /* no need to warn again if configured but support is not available, we already did for UDP */
+        warnlog("SO_REUSEPORT has been configured on local address '%s' but is not supported", cs->local.toStringWithPort());
+      }
     }
-#endif
   }
 
-  if (!cs->tcp) {
-    if (cs->local.isIPv4()) {
-      try {
-        setSocketIgnorePMTU(cs->udpFD);
-      }
-      catch(const std::exception& e) {
-        warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", cs->local.toStringWithPort(), e.what());
-      }
+  /* Only set this on IPv4 UDP sockets.
+     Don't set it for DNSCrypt binds. DNSCrypt pads queries for privacy
+     purposes, so we do receive large, sometimes fragmented datagrams. */
+  if (!cs->tcp && cs->local.isIPv4() && !cs->dnscryptCtx) {
+    try {
+      setSocketIgnorePMTU(cs->udpFD);
+    }
+    catch(const std::exception& e) {
+      warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", cs->local.toStringWithPort(), e.what());
     }
   }
 
@@ -1885,7 +1911,8 @@ static void setUpLocalBind(std::unique_ptr<ClientState>& cs)
   SBind(fd, cs->local);
 
   if (cs->tcp) {
-    SListen(cs->tcpFD, SOMAXCONN);
+    SListen(cs->tcpFD, cs->tcpListenQueueSize);
+
     if (cs->tlsFrontend != nullptr) {
       warnlog("Listening on %s for TLS", cs->local.toStringWithPort());
     }
@@ -2141,7 +2168,7 @@ try
 
   g_policy.setState(leastOutstandingPol);
   if(g_cmdLine.beClient || !g_cmdLine.command.empty()) {
-    setupLua(true, false, g_cmdLine.config);
+    setupLua(g_lua, true, false, g_cmdLine.config);
     if (clientAddress != ComboAddress())
       g_serverControl = clientAddress;
     doClient(g_serverControl, g_cmdLine.command);
@@ -2162,22 +2189,22 @@ try
   g_consoleACL.setState(consoleACL);
 
   if (g_cmdLine.checkConfig) {
-    setupLua(false, true, g_cmdLine.config);
+    setupLua(g_lua, false, true, g_cmdLine.config);
     // No exception was thrown
     infolog("Configuration '%s' OK!", g_cmdLine.config);
     _exit(EXIT_SUCCESS);
   }
 
-  auto todo=setupLua(false, false, g_cmdLine.config);
+  auto todo = setupLua(g_lua, false, false, g_cmdLine.config);
 
   auto localPools = g_pools.getCopy();
   {
     bool precompute = false;
-    if (g_policy.getLocal()->name == "chashed") {
+    if (g_policy.getLocal()->getName() == "chashed") {
       precompute = true;
     } else {
       for (const auto& entry: localPools) {
-        if (entry.second->policy != nullptr && entry.second->policy->name == "chashed") {
+        if (entry.second->policy != nullptr && entry.second->policy->getName() == "chashed") {
           precompute = true;
           break ;
         }
@@ -2188,6 +2215,10 @@ try
       // pre compute hashes
       auto backends = g_dstates.getLocal();
       for (auto& backend: *backends) {
+        if (backend->weight < 100) {
+          vinfolog("Warning, the backend '%s' has a very low weight (%d), which will not yield a good distribution of queries with the 'chashed' policy. Please consider raising it to at least '100'.", backend->getName(), backend->weight);
+        }
+
         backend->hash();
       }
     }
index c5aff84584878a56c5f14f568907c5b7981fd123..55c0868647b1cf02fe4db9c04d3fb584ca2d6f79 100644 (file)
@@ -49,6 +49,7 @@
 #include "sholder.hh"
 #include "tcpiohandler.hh"
 #include "uuid-utils.hh"
+#include "proxy-protocol.hh"
 
 void carbonDumpThread();
 uint64_t uptimeOfProcess(const std::string& str);
@@ -84,6 +85,7 @@ struct DNSQuestion
   const ComboAddress* local{nullptr};
   const ComboAddress* remote{nullptr};
   std::shared_ptr<QTag> qTag{nullptr};
+  std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
   std::shared_ptr<std::map<uint16_t, EDNSOptionView> > ednsOptions;
   std::shared_ptr<DNSCryptQuery> dnsCryptQuery{nullptr};
   std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
@@ -261,6 +263,8 @@ struct DNSDistStats
   stat_t cacheMisses{0};
   stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0}, latencySum{0};
   stat_t securityStatus{0};
+  stat_t dohQueryPipeFull{0};
+  stat_t dohResponsePipeFull{0};
 
   double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
   typedef std::function<uint64_t(const std::string&)> statfunction_t;
@@ -313,6 +317,8 @@ struct DNSDistStats
     {"dyn-blocked", &dynBlocked},
     {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
     {"security-status", &securityStatus},
+    {"doh-query-pipe-full", &dohQueryPipeFull},
+    {"doh-response-pipe-full", &dohResponsePipeFull},
     // Latency histogram
     {"latency-sum", &latencySum},
     {"latency-count", getLatencyCount},
@@ -572,15 +578,13 @@ typedef std::function<std::tuple<bool, string>(const DNSQuestion* dq)> QueryCoun
 struct QueryCount {
   QueryCount()
   {
-    pthread_rwlock_init(&queryLock, nullptr);
   }
   ~QueryCount()
   {
-    pthread_rwlock_destroy(&queryLock);
   }
   QueryCountRecords records;
   QueryCountFilter filter;
-  pthread_rwlock_t queryLock;
+  ReadWriteLock queryLock;
   bool enabled{false};
 };
 
@@ -620,6 +624,7 @@ struct ClientState
   std::atomic<double> tcpAvgConnectionDuration{0.0};
   int udpFD{-1};
   int tcpFD{-1};
+  int tcpListenQueueSize{SOMAXCONN};
   int fastOpenQueueSize{0};
   bool muted{false};
   bool tcp;
@@ -760,19 +765,11 @@ struct DownstreamState
 
   DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf, const std::string& sourceItfName, size_t numberOfSockets, bool connect);
   DownstreamState(const ComboAddress& remote_): DownstreamState(remote_, ComboAddress(), 0, std::string(), 1, true) {}
-  ~DownstreamState()
-  {
-    for (auto& fd : sockets) {
-      if (fd >= 0) {
-        close(fd);
-        fd = -1;
-      }
-    }
-    pthread_rwlock_destroy(&d_lock);
-  }
+  ~DownstreamState();
+
   boost::uuids::uuid id;
   std::vector<unsigned int> hashes;
-  mutable pthread_rwlock_t d_lock;
+  mutable ReadWriteLock d_lock;
   std::vector<int> sockets;
   const std::string sourceItfName;
   std::mutex socketsLock;
@@ -832,12 +829,14 @@ struct DownstreamState
   bool mustResolve{false};
   bool upStatus{false};
   bool useECS{false};
+  bool useProxyProtocol{false};
   bool setCD{false};
   bool disableZeroScope{false};
   std::atomic<bool> connected{false};
   std::atomic_flag threadStarted;
   bool tcpFastOpen{false};
   bool ipBindAddrNoPort{true};
+  bool reconnectOnUp{false};
 
   bool isUp() const
   {
@@ -877,6 +876,11 @@ struct DownstreamState
   void hash();
   void setId(const boost::uuids::uuid& newId);
   void setWeight(int newWeight);
+  void stop();
+  bool isStopped() const
+  {
+    return d_stopped;
+  }
 
   void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
   {
@@ -886,6 +890,7 @@ struct DownstreamState
 private:
   std::string name;
   std::string nameWithAddr;
+  bool d_stopped{false};
 };
 using servers_t =vector<std::shared_ptr<DownstreamState>>;
 
@@ -907,13 +912,12 @@ public:
 
 struct ServerPool
 {
-  ServerPool()
+  ServerPool(): d_servers(std::make_shared<ServerPolicy::NumberedServerVector>())
   {
-    pthread_rwlock_init(&d_lock, nullptr);
   }
+
   ~ServerPool()
   {
-    pthread_rwlock_destroy(&d_lock);
   }
 
   const std::shared_ptr<DNSDistPacketCache> getCache() const { return packetCache; };
@@ -935,7 +939,7 @@ struct ServerPool
   {
     size_t count = 0;
     ReadLock rl(&d_lock);
-    for (const auto& server : d_servers) {
+    for (const auto& server : *d_servers) {
       if (!upOnly || std::get<1>(server)->isUp() ) {
         count++;
       }
@@ -943,9 +947,9 @@ struct ServerPool
     return count;
   }
 
-  ServerPolicy::NumberedServerVector getServers()
+  const std::shared_ptr<ServerPolicy::NumberedServerVector> getServers()
   {
-    ServerPolicy::NumberedServerVector result;
+    std::shared_ptr<ServerPolicy::NumberedServerVector> result;
     {
       ReadLock rl(&d_lock);
       result = d_servers;
@@ -956,25 +960,32 @@ struct ServerPool
   void addServer(shared_ptr<DownstreamState>& server)
   {
     WriteLock wl(&d_lock);
-    unsigned int count = (unsigned int) d_servers.size();
-    d_servers.push_back(make_pair(++count, server));
+    /* we can't update the content of the shared pointer directly even when holding the lock,
+       as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */
+    unsigned int count = static_cast<unsigned int>(d_servers->size());
+    auto newServers = std::make_shared<ServerPolicy::NumberedServerVector>(*d_servers);
+    newServers->push_back(make_pair(++count, server));
     /* we need to reorder based on the server 'order' */
-    std::stable_sort(d_servers.begin(), d_servers.end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& a, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& b) {
+    std::stable_sort(newServers->begin(), newServers->end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& a, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& b) {
       return a.second->order < b.second->order;
     });
     /* and now we need to renumber for Lua (custom policies) */
     size_t idx = 1;
-    for (auto& serv : d_servers) {
+    for (auto& serv : *newServers) {
       serv.first = idx++;
     }
+    d_servers = newServers;
   }
 
   void removeServer(shared_ptr<DownstreamState>& server)
   {
     WriteLock wl(&d_lock);
+    /* we can't update the content of the shared pointer directly even when holding the lock,
+       as other threads might hold a copy. We can however update the pointer as long as we hold the lock. */
+    auto newServers = std::make_shared<ServerPolicy::NumberedServerVector>(*d_servers);
     size_t idx = 1;
     bool found = false;
-    for (auto it = d_servers.begin(); it != d_servers.end();) {
+    for (auto it = newServers->begin(); it != newServers->end();) {
       if (found) {
         /* we need to renumber the servers placed
            after the removed one, for Lua (custom policies) */
@@ -982,18 +993,19 @@ struct ServerPool
         it++;
       }
       else if (it->second == server) {
-        it = d_servers.erase(it);
+        it = newServers->erase(it);
         found = true;
       } else {
         idx++;
         it++;
       }
     }
+    d_servers = newServers;
   }
 
 private:
-  ServerPolicy::NumberedServerVector d_servers;
-  pthread_rwlock_t d_lock;
+  std::shared_ptr<ServerPolicy::NumberedServerVector> d_servers;
+  ReadWriteLock d_lock;
   bool d_useECS{false};
 };
 
@@ -1093,23 +1105,9 @@ struct LocalHolders
 
 struct dnsheader;
 
-void controlThread(int fd, ComboAddress local);
 vector<std::function<void(void)>> setupLua(bool client, const std::string& config);
 
-struct WebserverConfig
-{
-  std::string password;
-  std::string apiKey;
-  boost::optional<std::map<std::string, std::string> > customHeaders;
-  std::mutex lock;
-};
-
-void setWebserverAPIKey(const boost::optional<std::string> apiKey);
-void setWebserverPassword(const std::string& password);
-void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders);
-
-void dnsdistWebserverThread(int sock, const ComboAddress& local);
-void tcpAcceptorThread(void* p);
+void tcpAcceptorThread(ClientState* p);
 #ifdef HAVE_DNS_OVER_HTTPS
 void dohThread(ClientState* cs);
 #endif /* HAVE_DNS_OVER_HTTPS */
index 810de15926b057d14e4fc475a31dff6a98cc6ecf..2b218d5a993487b1cfb5e132892242539a495282 100644 (file)
+-- == Generic Configuration ==
+
+-- only accept queries (Do53, DNSCrypt,  DoT or DoH) from a few subnets
+-- see https://dnsdist.org/advanced/acl.html for more details
+-- please be careful when dnsdist is deployed in front of a server
+-- server granting access based on the source IP, as all queries will
+-- seem to originate from dnsdist, which might be especially relevant for
+-- AXFR, IXFR, NOTIFY and UPDATE
+-- https://dnsdist.org/advanced/axfr.html
+-- setACL({'192.0.2.0/28', '2001:DB8:1::/56'})
+
 -- listen for console connection with the given secret key
--- controlSocket("0.0.0.0")
--- setKey(please generate a fresh private key with makeKey())
+-- https://dnsdist.org/guides/console.html
+-- controlSocket("127.0.0.1:5900")
+-- setKey("please generate a fresh private key with makeKey()")
 
 -- start the web server on port 8083, using password 'set a random password here'
--- webserver("0.0.0.0:8083", "set a random password here")
-
--- accept DNS queries on UDP/5200 and TCP/5200
-addLocal("0.0.0.0:5200")
-
--- send statistics to PowerDNS metronome server
--- carbonServer("2001:888:2000:1d::2")
-
--- fix up possibly badly truncated answers from pdns 2.9.22
-truncateTC(true)
-
-warnlog(string.format("Script starting %s", "up!"))
-
--- define the good servers
-newServer("8.8.8.8", 2)  -- 2 qps
-newServer("8.8.4.4", 2)
-newServer("208.67.222.222", 1)
-newServer("208.67.220.220", 1)
-newServer("2001:4860:4860::8888", 1)
-newServer("2001:4860:4860::8844",1)
-newServer("2620:0:ccc::2", 10)
-newServer("2620:0:ccd::2", 10)
-newServer({address="192.168.1.2", qps=1000, order=2})
-newServer({address="192.168.1.79:5300", order=2})
-newServer({address="127.0.0.1:5300", order=3})
-newServer({address="192.168.1.30:5300", pool="abuse"})
-
--- switch the server balancing policy to round robin,
--- the default being least outstanding queries
--- setServerPolicy(roundrobin)
-
--- send the queries for selected domain suffixes to the server
+-- https://dnsdist.org/guides/webserver.html
+-- webserver("127.0.0.1:8083", "set a random password here")
+
+-- send statistics to PowerDNS metronome server https://metronome1.powerdns.com/
+-- https://dnsdist.org/guides/carbon.html
+-- carbonServer("37.252.122.50", 'unique-name')
+
+-- accept plain DNS (Do53) queries on UDP/5200 and TCP/5200
+-- addLocal("127.0.0.1:5200")
+
+-- accept DNSCrypt queries on UDP/8443 and TCP/8443
+-- https://dnsdist.org/guides/dnscrypt.html
+-- addDNSCryptBind("127.0.0.1:8443", "2.provider.name", "DNSCryptResolver.cert", "DNSCryptResolver.key")
+
+-- accept DNS over TLS (DoT) queries on TCP/9443
+-- https://dnsdist.org/guides/dns-over-tls.html
+-- addTLSLocal("127.0.0.1:9443", {"server.crt"}, {"server.key"}, { provider="openssl" })
+
+-- accept DNS over HTTPS (DoH) queries on TCP/443
+-- https://dnsdist.org/guides/dns-over-https.html
+-- addDOHLocal("127.0.0.1:443", {"server.crt"}, {"server.key"})
+
+-- define downstream servers, aka backends
+-- https://dnsdist.org/guides/downstreams.html
+-- https://dnsdist.org/guides/serverpools.html
+-- https://dnsdist.org/guides/serverselection.html
+-- newServer("192.0.2.1")
+-- newServer({address="192.0.2.1:5300", pool="abuse"})
+
+-- == Tuning ==
+
+-- Increase the in-memory rings size (the default, 10000, is only one second at 10k qps) used by
+-- live-traffic inspection features like grepq, and use 100 shards to improve performance
+-- setRingBuffersSize(1000000, 100)
+
+-- increase the number of TCP workers, each one being capable of handling a large number
+-- of TCP connections since 1.4.0
+-- setMaxTCPClientThreads(20)
+
+-- == Sample Actions ==
+
+-- https://dnsdist.org/rules-actions.html
+
+-- send the queries for selected domain suffixes to the servers
 -- in the 'abuse' pool
-addAction({"ezdns.it.", "xxx."}, PoolAction("abuse"))
+-- addAction({"abuse.example.org.", "xxx."}, PoolAction("abuse"))
+
+-- drop queries for this exact qname
+-- addAction(QNameRule("drop-me.example.org."), DropAction())
 
 -- send the queries from a selected subnet to the
 -- abuse pool
-addAction("192.168.1.0/24", PoolAction("abuse"))
-
--- send the queries for the "com" suffix to the "abuse"
--- pool, but only up to 100 qps
-addAction("com.", QPSPoolAction(100, "abuse"))
-
--- declare a Lua action function, routing NAPTR queries
--- to the abuse pool
-function luarule(dq)
-       if(dq.qtype==DNSQType.NAPTR)
-       then
-               return DNSAction.Pool, "abuse" -- send to abuse pool
-       else
-               return DNSAction.None, ""      -- no action
-       end
-end
--- send only queries from the selected subnet to
--- the luarule function
-addAction("192.168.1.0/24", LuaAction(luarule))
-
--- drop queries exceeding 5 qps, grouped by /24 for IPv4
--- and /64 for IPv6
-addAction(MaxQPSIPRule(5, 24, 64), DropAction())
-
--- move the last rule to the first position
-topRule()
-
--- drop queries for the following suffixes:
-addAction("powerdns.org.", DropAction())
-addAction("spectre.", DropAction())
-
--- called before we distribute a question
-block=newDNSName("powerdns.org.")
-truncateNMG = newNMG()
-truncateNMG:addMask("213.244.0.0/16")
-truncateNMG:addMask("2001:503:ba3e::2:30")
-truncateNMG:addMask("fe80::/16")
-
-print(string.format("Have %d entries in truncate NMG", truncateNMG:size()))
-
--- called to pick a downstream server, ignores 'up' status
-counter=0
-function luaroundrobin(servers, dq)
-        counter=counter+1;
-        return servers[1+(counter % #servers)]
-end
--- setServerPolicyLua("luaroundrobin", luaroundrobin)
-
-newServer({address="2001:888:2000:1d::2", pool={"auth", "dnssec"}})
-newServer({address="2a01:4f8:110:4389::2", pool={"auth", "dnssec"}})
---addAction(DNSSECRule(), PoolAction("dnssec"))
---topRule()
-
--- split queries between the 'auth' pool and the regular one,
--- based on the RD flag
-function splitSetup(servers, dq)
-        if(dq.dh:getRD() == false)
-        then
-               return firstAvailable.policy(getPoolServers("auth"), dq)
-        else
-               return firstAvailable.policy(servers, dq)
-        end
-end
--- setServerPolicyLua("splitSetup", splitSetup)
-
--- the 'maintenance' function is called every second
-function maintenance()
-        -- block all hosts that exceeded 20 qps over the past 10s,
-        -- for 60s
-        addDynBlocks(exceedQRate(20, 10), "Exceeded query rate", 60)
-end
-
--- allow queries for the domain powerdns.com., drop everything else
--- addAction(makeRule("powerdns.com."), AllowAction())
--- addAction(AllRule(), DropAction())
-
--- clear the RD flag in queries for powerdns.com.
--- addAction("powerdns.com.", NoRecurseAction())
-
--- set the CD flag in queries for powerdns.com.
--- addAction("powerdns.com.", DisableValidationAction())
-
--- delay all responses for 1000ms
--- addAction(AllRule(), DelayAction(1000))
-
--- truncate ANY queries over UDP only
--- addAction(AndRule{QTypeRule(DNSQType.ANY), TCPRule(false)}, TCAction())
-
--- truncate ANY queries over TCP only
--- addAction(AndRule({QTypeRule(DNSQType.ANY), TCPRule(true)}), TCAction())
--- can also be written as:
--- addAction(AndRule({QTypeRule("ANY"), TCPRule(true)}), TCAction())
-
--- return 'not implemented' for qtype != A over UDP
--- addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(DNSRCode.NOTIMP))
-
--- return 'not implemented' for qtype == A OR received over UDP
--- addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(DNSRCode.NOTIMP))
-
--- log all queries to a 'dndist.log' file, in text-mode (not binary) appending and unbuffered
--- addAction(AllRule(), LogAction("dnsdist.log", false, true, false))
-
--- drop all queries with the DO flag set
--- addAction(DNSSECRule(), DropAction())
-
--- drop all queries for the CHAOS class
--- addAction(QClassRule(3), DropAction())
--- addAction(QClassRule(DNSClass.CHAOS), DropAction())
-
--- drop all queries with the UPDATE opcode
--- addAction(OpcodeRule(DNSOpcode.Update), DropAction())
-
--- refuse all queries not having exactly one question
--- addAction(NotRule(RecordsCountRule(DNSSection.Question, 1, 1)), RCodeAction(DNSRCode.REFUSED))
-
--- return 'refused' for domains matching the regex evil[0-9]{4,}.powerdns.com$
--- addAction(RegexRule("evil[0-9]{4,}\\.powerdns\\.com$"), RCodeAction(DNSRCode.REFUSED))
-
--- spoof responses for A, AAAA and ANY for spoof.powerdns.com.
--- A queries will get 192.0.2.1, AAAA 2001:DB8::1 and ANY both
--- addAction("spoof.powerdns.com.", SpoofAction({"192.0.2.1", "2001:DB8::1"}))
-
--- spoof responses will multiple records
--- A will get 192.0.2.1 and 192.0.2.2, AAAA 20B8::1 and 2001:DB8::2
--- ANY all of that
--- addAction("spoof.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "20B8::1", "2001:DB8::2"}))
-
--- spoof responses with a CNAME
--- addAction("cnamespoof.powerdns.com.", SpoofCNAMEAction("cname.powerdns.com."))
-
--- spoof responses in Lua
---[[
-    function spoof1rule(dq)
-        if(dq.qtype==1) -- A
-        then
-                return DNSAction.Spoof, "192.0.2.1"
-        elseif(dq.qtype == 28) -- AAAA
-        then
-                return DNSAction.Spoof, "2001:DB8::1"
-        else
-                return DNSAction.None, ""
-        end
-    end
-    function spoof2rule(dq)
-        return DNSAction.Spoof, "spoofed.powerdns.com."
-    end
-    addAction("luaspoof1.powerdns.com.", LuaAction(spoof1rule))
-    addAction("luaspoof2.powerdns.com.", LuaAction(spoof2rule))
-
---]]
-
--- alter a protobuf response for anonymization purposes
---[[
-function alterProtobuf(dq, protobuf)
-    requestor = newCA(dq.remoteaddr:toString())
-    if requestor:isIPv4() then
-        requestor:truncate(24)
-    else
-        requestor:truncate(56)
-    end
-    protobuf:setRequestor(requestor)
-end
-
-rl = newRemoteLogger("127.0.0.1:4242")
-addAction(AllRule(), RemoteLogAction(rl, alterProtobuf))
---]]
+-- addAction("192.0.2.0/24", PoolAction("abuse"))
+
+-- Refuse incoming AXFR, IXFR, NOTIFY and UPDATE
+-- Add trusted sources (slaves, masters) explicitely in front of this rule
+-- addAction(OrRule({OpcodeRule(DNSOpcode.Notify), OpcodeRule(DNSOpcode.Update), QTypeRule(DNSQType.AXFR), QTypeRule(DNSQType.IXFR)}), RCodeAction(DNSRCode.REFUSED))
+
+-- == Dynamic Blocks ==
+
+-- define a dynamic block rules group object, set a few limits and apply it
+-- see https://dnsdist.org/guides/dynblocks.html for more details
+
+-- local dbr = dynBlockRulesGroup()
+-- dbr:setQueryRate(30, 10, "Exceeded query rate", 60)
+-- dbr:setRCodeRate(dnsdist.NXDOMAIN, 20, 10, "Exceeded NXD rate", 60)
+-- dbr:setRCodeRate(dnsdist.SERVFAIL, 20, 10, "Exceeded ServFail rate", 60)
+-- dbr:setQTypeRate(dnsdist.ANY, 5, 10, "Exceeded ANY rate", 60)
+-- dbr:setResponseByteRate(10000, 10, "Exceeded resp BW rate", 60)
+-- function maintenance()
+--  dbr:apply()
+-- end
+
+-- == Logging ==
+
+-- connect to a remote protobuf logger and export queries and responses
+-- https://dnsdist.org/reference/protobuf.html
+-- rl = newRemoteLogger('127.0.0.1:4242')
+-- addAction(AllRule(), RemoteLogAction(rl))
+-- addResponseAction(AllRule(), RemoteLogResponseAction(rl))
+
+-- DNSTAP is also supported
+-- https://dnsdist.org/reference/dnstap.html
+-- fstr = newFrameStreamUnixLogger(/path/to/unix/socket)
+-- or
+-- fstr = newFrameStreamTcpLogger('192.0.2.1:4242')
+-- addAction(AllRule(), DnstapLogAction(fstr))
+-- addResponseAction(AllRule(), DnstapLogResponseAction(fstr))
+
+-- == Caching ==
+
+-- https://dnsdist.org/guides/cache.html
+-- create a packet cache of at most 100k entries,
+-- and apply it to the default pool
+-- pc = newPacketCache(100000)
+-- getPool(""):setCache(pc)
index 54bbd0ae3f49398540abbf935c44fda3475edba5..f70a58ba625ef121e0d8ea1731b665f105a9bf06 100644 (file)
@@ -152,13 +152,14 @@ dnsdist_SOURCES = \
        dnsdist-lua-vars.cc \
        dnsdist-prometheus.hh \
        dnsdist-protobuf.cc dnsdist-protobuf.hh \
+       dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
        dnsdist-rings.cc dnsdist-rings.hh \
        dnsdist-rules.hh \
        dnsdist-secpoll.cc dnsdist-secpoll.hh \
        dnsdist-snmp.cc dnsdist-snmp.hh \
        dnsdist-systemd.cc dnsdist-systemd.hh \
        dnsdist-tcp.cc \
-       dnsdist-web.cc \
+       dnsdist-web.cc dnsdist-web.hh \
        dnsdist-xpf.cc dnsdist-xpf.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
@@ -178,8 +179,10 @@ dnsdist_SOURCES = \
        misc.cc misc.hh \
        mplexer.hh \
        namespaces.hh \
+       packetcache.hh \
        pdnsexception.hh \
        protobuf.cc protobuf.hh \
+       proxy-protocol.cc proxy-protocol.hh \
        dnstap.cc dnstap.hh \
        qtype.cc qtype.hh \
        remote_logger.cc remote_logger.hh \
@@ -192,6 +195,7 @@ dnsdist_SOURCES = \
        tcpiohandler.cc tcpiohandler.hh \
        threadname.hh threadname.cc \
        uuid-utils.hh uuid-utils.cc \
+       views.hh \
        xpf.cc xpf.hh \
        ext/luawrapper/include/LuaContext.hpp \
        ext/json11/json11.cpp \
@@ -215,17 +219,24 @@ testrunner_SOURCES = \
        test-dnsparser_cc.cc \
        test-iputils_hh.cc \
        test-mplexer.cc \
+       test-proxy_protocol_cc.cc \
+       bpf-filter.cc bpf-filter.hh \
        cachecleaner.hh \
        circular_buffer.hh \
        dnsdist.hh \
        dnsdist-backend.cc \
        dnsdist-cache.cc dnsdist-cache.hh \
        dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
+       dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
        dnsdist-kvs.cc dnsdist-kvs.hh \
        dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
+       dnsdist-lua-bindings.cc \
+       dnsdist-lua-bindings-dnsquestion.cc \
+       dnsdist-lua-bindings-kvs.cc \
        dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \
        dnsdist-lua-ffi-interface.h dnsdist-lua-ffi-interface.inc \
+       dnsdist-lua-vars.cc \
        dnsdist-rings.hh \
        dnsdist-xpf.cc dnsdist-xpf.hh \
        dnscrypt.cc dnscrypt.hh \
@@ -243,6 +254,7 @@ testrunner_SOURCES = \
        namespaces.hh \
        pdnsexception.hh \
        pollmplexer.cc \
+       proxy-protocol.cc proxy-protocol.hh \
        qtype.cc qtype.hh \
        sholder.hh \
        sodcrypto.cc \
@@ -431,12 +443,24 @@ endif
 if !HAVE_SYSTEMD_PRIVATE_TMP
        $(AM_V_GEN)perl -ni -e 'print unless /^PrivateTmp/' $@
 endif
+if !HAVE_SYSTEMD_PRIVATE_USERS
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateUsers/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_CLOCK
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectClock/' $@
+endif
 if !HAVE_SYSTEMD_PROTECT_CONTROL_GROUPS
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectControlGroups/' $@
 endif
 if !HAVE_SYSTEMD_PROTECT_HOME
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHome/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_HOSTNAME
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHostname/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_KERNEL_LOGS
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelLogs/' $@
+endif
 if !HAVE_SYSTEMD_PROTECT_KERNEL_MODULES
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelModules/' $@
 endif
@@ -455,6 +479,9 @@ endif
 if !HAVE_SYSTEMD_RESTRICT_REALTIME
        $(AM_V_GEN)perl -ni -e 'print unless /^RestrictRealtime/' $@
 endif
+if !HAVE_SYSTEMD_RESTRICT_SUIDSGID
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictSUIDSGID/' $@
+endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_ARCHITECTURES
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallArchitectures/' $@
 endif
@@ -463,7 +490,9 @@ if !HAVE_SYSTEMD_SYSTEM_CALL_FILTER
 endif
 
 dnsdist@.service: dnsdist.service
-       $(AM_V_GEN)sed -e 's!/dnsdist !&--config $(sysconfdir)/dnsdist-%i.conf !' < $< >$@
+       $(AM_V_GEN)sed -e 's!/dnsdist !&--config $(sysconfdir)/dnsdist-%i.conf !' \
+         -e 's!RuntimeDirectory=.*!&-%i!' \
+         < $< >$@
 
 systemdsystemunitdir = $(SYSTEMD_DIR)
 
index 89bb865e19c3ad0126eb4d1a4addd1b7275fae98..b09a7e02b15b400db7a480a937220c22193cdc5f 100644 (file)
@@ -18,7 +18,7 @@ LT_PREREQ([2.2.2])
 LT_INIT([disable-static])
 
 CFLAGS="-g -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter $CFLAGS"
-CXXFLAGS="-g -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter $CXXFLAGS"
+CXXFLAGS="-std=c++11 -g -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls $CXXFLAGS"
 
 PDNS_WITH_LIBSODIUM
 PDNS_CHECK_DNSTAP([auto])
@@ -101,7 +101,7 @@ AS_IF([test "x$enable_dns_over_https" != "xno"], [
 PDNS_CHECK_CDB
 PDNS_CHECK_LMDB
 
-AX_CXX_COMPILE_STDCXX_11([ext], [mandatory])
+AX_CXX_COMPILE_STDCXX_11([noext], [mandatory])
 
 AC_MSG_CHECKING([whether we will enable compiler security checks])
 AC_ARG_ENABLE([hardening],
index 606977c098fe4c8c3b93e242165fd6c234b46ca8..38398e28eb11406b3128e5d2d57481d994300943 100644 (file)
@@ -79,8 +79,14 @@ bool DownstreamState::reconnect()
     for (auto& fd : sockets) {
       if (fd != -1) {
         if (sockets.size() > 1) {
-          std::lock_guard<std::mutex> lock(socketsLock);
-          mplexer->removeReadFD(fd);
+          try {
+            std::lock_guard<std::mutex> lock(socketsLock);
+            mplexer->removeReadFD(fd);
+          }
+          catch (const FDMultiplexerException& e) {
+            /* some sockets might not have been added to the multiplexer
+               yet, that's fine */
+          }
         }
         /* shutdown() is needed to wake up recv() in the responderThread */
         shutdown(fd, SHUT_RDWR);
@@ -92,6 +98,21 @@ bool DownstreamState::reconnect()
 
   return connected;
 }
+
+void DownstreamState::stop()
+{
+  std::unique_lock<std::mutex> tl(connectLock);
+  std::lock_guard<std::mutex> slock(socketsLock);
+  d_stopped = true;
+
+  for (auto& fd : sockets) {
+    if (fd != -1) {
+      /* shutdown() is needed to wake up recv() in the responderThread */
+      shutdown(fd, SHUT_RDWR);
+    }
+  }
+}
+
 void DownstreamState::hash()
 {
   vinfolog("Computing hashes for id=%s and weight=%d", id, weight);
@@ -131,7 +152,6 @@ void DownstreamState::setWeight(int newWeight)
 
 DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf_, const std::string& sourceItfName_, size_t numberOfSockets, bool connect=true): sourceItfName(sourceItfName_), remote(remote_), sourceAddr(sourceAddr_), sourceItf(sourceItf_), name(remote_.toStringWithPort()), nameWithAddr(remote_.toStringWithPort())
 {
-  pthread_rwlock_init(&d_lock, nullptr);
   id = getUniqueID();
   threadStarted.clear();
 
@@ -147,5 +167,20 @@ DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress
     idStates.resize(g_maxOutstanding);
     sw.start();
   }
+}
 
+DownstreamState::~DownstreamState()
+{
+  for (auto& fd : sockets) {
+    if (fd >= 0) {
+      close(fd);
+      fd = -1;
+    }
+  }
+
+  // we need to either detach or join the thread before it
+  // is destroyed
+  if (threadStarted.test_and_set()) {
+    tid.detach();
+  }
 }
index 3dc7e6a371dc8661fbbab7c6d5ed68123c9fde16..6b78c3d3c4d53159528aa2f66d43c073202082ba 100644 (file)
@@ -60,7 +60,7 @@ void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool n
   if(newState != dss->upStatus) {
     warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
 
-    if (newState && !dss->connected) {
+    if (newState && (!dss->connected || dss->reconnectOnUp)) {
       newState = dss->reconnect();
 
       if (dss->connected && !dss->threadStarted.test_and_set()) {
@@ -207,6 +207,11 @@ bool queueHealthCheck(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared
     dnsheader * requestHeader = dpw.getHeader();
     *requestHeader = checkHeader;
 
+    if (ds->useProxyProtocol) {
+      auto payload = makeLocalProxyHeader();
+      packet.insert(packet.begin(), payload.begin(), payload.end());
+    }
+
     Socket sock(ds->remote.sin4.sin_family, SOCK_DGRAM);
     sock.setNonBlocking();
     if (!IsAnyAddress(ds->sourceAddr)) {
index 169ba64f3a12319e36efd1efaac7fd4f4899f24d..05fa48f7ff37ceaa307e7c2de959ecf4a5b66692 100644 (file)
@@ -3,7 +3,6 @@
 
 DNSResponse makeDNSResponseFromIDState(IDState& ids, struct dnsheader* dh, size_t bufferSize, uint16_t responseLen, bool isTCP)
 {
-  
   DNSResponse dr(&ids.qname, ids.qtype, ids.qclass, ids.qname.wirelength(), &ids.origDest, &ids.origRemote, dh, bufferSize, responseLen, isTCP, &ids.sentTime.d_start);
   dr.origFlags = ids.origFlags;
   dr.ecsAdded = ids.ecsAdded;
@@ -25,7 +24,7 @@ DNSResponse makeDNSResponseFromIDState(IDState& ids, struct dnsheader* dh, size_
     dr.dnsCryptQuery = std::move(ids.dnsCryptQuery);
   }
 
-  return dr;  
+  return dr;
 }
 
 void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname)
@@ -49,9 +48,9 @@ void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname)
   ids.useZeroScope = dq.useZeroScope;
   ids.qTag = dq.qTag;
   ids.dnssecOK = dq.dnssecOK;
-  
+
   ids.dnsCryptQuery = std::move(dq.dnsCryptQuery);
-  
+
 #ifdef HAVE_PROTOBUF
   ids.uniqueId = std::move(dq.uniqueId);
 #endif
index e4cc36b506087f457e72ce4c7be53473313600e2..3b60dfc2acd0bda2636c4acaaf46b895c16a5588 100644 (file)
 std::vector<std::string> KeyValueLookupKeySourceIP::getKeys(const ComboAddress& addr)
 {
   std::vector<std::string> result;
+  ComboAddress truncated(addr);
 
-  if (addr.sin4.sin_family == AF_INET) {
-    result.emplace_back(reinterpret_cast<const char*>(&addr.sin4.sin_addr.s_addr), sizeof(addr.sin4.sin_addr.s_addr));
+  if (truncated.isIPv4()) {
+    truncated.truncate(d_v4Mask);
+    result.emplace_back(reinterpret_cast<const char*>(&truncated.sin4.sin_addr.s_addr), sizeof(truncated.sin4.sin_addr.s_addr));
   }
-  else if (addr.sin4.sin_family == AF_INET6) {
-    result.emplace_back(reinterpret_cast<const char*>(&addr.sin6.sin6_addr.s6_addr), sizeof(addr.sin6.sin6_addr.s6_addr));
+  else if (truncated.isIPv6()) {
+    truncated.truncate(d_v6Mask);
+    result.emplace_back(reinterpret_cast<const char*>(&truncated.sin6.sin6_addr.s6_addr), sizeof(truncated.sin6.sin6_addr.s6_addr));
   }
 
   return result;
@@ -117,7 +120,6 @@ bool LMDBKVStore::keyExists(const std::string& key)
 
 CDBKVStore::CDBKVStore(const std::string& fname, time_t refreshDelay): d_fname(fname), d_refreshDelay(refreshDelay)
 {
-  pthread_rwlock_init(&d_lock, nullptr);
   d_refreshing.clear();
 
   time_t now = time(nullptr);
@@ -129,7 +131,6 @@ CDBKVStore::CDBKVStore(const std::string& fname, time_t refreshDelay): d_fname(f
 }
 
 CDBKVStore::~CDBKVStore() {
-  pthread_rwlock_destroy(&d_lock);
 }
 
 bool CDBKVStore::reload(const struct stat& st)
index 63e18fdf5e507e94be14b45bfd79db8911fc190d..f0968b08ab14b971e69d0acaf9097785f9be6fe7 100644 (file)
@@ -36,6 +36,10 @@ public:
 class KeyValueLookupKeySourceIP: public KeyValueLookupKey
 {
 public:
+  KeyValueLookupKeySourceIP(uint8_t v4Mask, uint8_t v6Mask): d_v4Mask(v4Mask), d_v6Mask(v6Mask)
+  {
+  }
+
   std::vector<std::string> getKeys(const ComboAddress& addr);
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
@@ -45,8 +49,11 @@ public:
 
   std::string toString() const override
   {
-    return "source IP";
+    return "source IP (masked to " + std::to_string(d_v4Mask) + " (v4) / " + std::to_string(d_v6Mask) + " (v6) bits)";
   }
+private:
+  uint8_t d_v4Mask;
+  uint8_t d_v6Mask;
 };
 
 class KeyValueLookupKeyQName: public KeyValueLookupKey
@@ -193,7 +200,7 @@ private:
 
   std::unique_ptr<CDB> d_cdb{nullptr};
   std::string d_fname;
-  pthread_rwlock_t d_lock;
+  ReadWriteLock d_lock;
   time_t d_mtime{0};
   time_t d_nextCheck{0};
   time_t d_refreshDelay{0};
index a36cc16afd783c3d556becd68e465e8563857921..1d75ca0600a88d88d5921c9253e4b47dac212970 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "dnsdist.hh"
 #include "dnsdist-lbpolicies.hh"
+#include "dnsdist-lua.hh"
 #include "dnsdist-lua-ffi.hh"
 #include "dolog.hh"
 
@@ -199,26 +200,33 @@ shared_ptr<DownstreamState> chashed(const ServerPolicy::NumberedServerVector& se
 
 shared_ptr<DownstreamState> roundrobin(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
 {
-  ServerPolicy::NumberedServerVector poss;
+  if (servers.empty()) {
+    return shared_ptr<DownstreamState>();
+  }
 
-  for(auto& d : servers) {
-    if(d.second->isUp()) {
-      poss.push_back(d);
+  vector<size_t> candidates;
+  candidates.reserve(servers.size());
+
+  for (auto& d : servers) {
+    if (d.second->isUp()) {
+      candidates.push_back(d.first);
     }
   }
 
-  const auto *res=&poss;
-  if(poss.empty() && !g_roundrobinFailOnNoServer)
-    res = &servers;
-
-  if(res->empty())
-    return shared_ptr<DownstreamState>();
+  if (candidates.empty()) {
+    if (g_roundrobinFailOnNoServer) {
+      return shared_ptr<DownstreamState>();
+    }
+    for (auto& d : servers) {
+      candidates.push_back(d.first);
+    }
+  }
 
   static unsigned int counter;
-  return (*res)[(counter++) % res->size()].second;
+  return servers.at(candidates.at((counter++) % candidates.size()) - 1).second;
 }
 
-ServerPolicy::NumberedServerVector getDownstreamCandidates(const pools_t& pools, const std::string& poolName)
+const std::shared_ptr<ServerPolicy::NumberedServerVector> getDownstreamCandidates(const pools_t& pools, const std::string& poolName)
 {
   std::shared_ptr<ServerPool> pool = getPool(pools, poolName);
   return pool->getServers();
@@ -244,9 +252,9 @@ void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<Serve
 {
   std::shared_ptr<ServerPool> pool = createPoolIfNotExists(pools, poolName);
   if (!poolName.empty()) {
-    vinfolog("Setting pool %s server selection policy to %s", poolName, policy->name);
+    vinfolog("Setting pool %s server selection policy to %s", poolName, policy->getName());
   } else {
-    vinfolog("Setting default pool server selection policy to %s", policy->name);
+    vinfolog("Setting default pool server selection policy to %s", policy->getName());
   }
   pool->policy = policy;
 }
@@ -287,28 +295,61 @@ std::shared_ptr<ServerPool> getPool(const pools_t& pools, const std::string& poo
   return it->second;
 }
 
-std::shared_ptr<DownstreamState> getSelectedBackendFromPolicy(const ServerPolicy& policy, const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq)
+ServerPolicy::ServerPolicy(const std::string& name_, const std::string& code): d_name(name_), d_perThreadPolicyCode(code), d_isLua(true), d_isFFI(true), d_isPerThread(true)
+{
+  LuaContext tmpContext;
+  setupLuaLoadBalancingContext(tmpContext);
+  auto ret = tmpContext.executeCode<ServerPolicy::ffipolicyfunc_t>(code);
+}
+
+thread_local ServerPolicy::PerThreadState ServerPolicy::t_perThreadState;
+
+const ServerPolicy::ffipolicyfunc_t& ServerPolicy::getPerThreadPolicy() const
+{
+  auto& state = t_perThreadState;
+  if (!state.d_initialized) {
+    setupLuaLoadBalancingContext(state.d_luaContext);
+    state.d_initialized = true;
+  }
+
+  const auto& it = state.d_policies.find(d_name);
+  if (it != state.d_policies.end()) {
+    return it->second;
+  }
+
+  auto newPolicy = state.d_luaContext.executeCode<ServerPolicy::ffipolicyfunc_t>(d_perThreadPolicyCode);
+  state.d_policies[d_name] = std::move(newPolicy);
+  return state.d_policies.at(d_name);
+}
+
+std::shared_ptr<DownstreamState> ServerPolicy::getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dq) const
 {
   std::shared_ptr<DownstreamState> selectedBackend{nullptr};
 
-  if (policy.isLua) {
-    if (!policy.isFFI) {
+  if (d_isLua) {
+    if (!d_isFFI) {
       std::lock_guard<std::mutex> lock(g_luamutex);
-      selectedBackend = policy.policy(servers, &dq);
+      selectedBackend = d_policy(servers, &dq);
     }
     else {
       dnsdist_ffi_dnsquestion_t dnsq(&dq);
       dnsdist_ffi_servers_list_t serversList(servers);
       unsigned int selected = 0;
-      {
+
+      if (!d_isPerThread) {
         std::lock_guard<std::mutex> lock(g_luamutex);
-        selected = policy.ffipolicy(&serversList, &dnsq);
+        selected = d_ffipolicy(&serversList, &dnsq);
+      }
+      else {
+        const auto& policy = getPerThreadPolicy();
+        selected = policy(&serversList, &dnsq);
       }
+
       selectedBackend = servers.at(selected).second;
     }
   }
   else {
-    selectedBackend = policy.policy(servers, &dq);
+    selectedBackend = d_policy(servers, &dq);
   }
 
   return selectedBackend;
index 6a25dcf852994690e9ae5beff18b8922b81ac912..ff2a13ee1562f834eced2bd1ad61c8400826e0df 100644 (file)
 
 #include "dolog.hh"
 
-void setupLuaBindingsDNSCrypt()
+void setupLuaBindingsDNSCrypt(LuaContext& luaCtx)
 {
 #ifdef HAVE_DNSCRYPT
     /* DNSCryptContext bindings */
-    g_lua.registerFunction<std::string(DNSCryptContext::*)()>("getProviderName", [](const DNSCryptContext& ctx) { return ctx.getProviderName().toStringNoDot(); });
-    g_lua.registerFunction("markActive", &DNSCryptContext::markActive);
-    g_lua.registerFunction("markInactive", &DNSCryptContext::markInactive);
-    g_lua.registerFunction("removeInactiveCertificate", &DNSCryptContext::removeInactiveCertificate);
-    g_lua.registerFunction<void(std::shared_ptr<DNSCryptContext>::*)(const std::string& certFile, const std::string& keyFile, boost::optional<bool> active)>("loadNewCertificate", [](std::shared_ptr<DNSCryptContext> ctx, const std::string& certFile, const std::string& keyFile, boost::optional<bool> active) {
+    luaCtx.registerFunction<std::string(DNSCryptContext::*)()>("getProviderName", [](const DNSCryptContext& ctx) { return ctx.getProviderName().toStringNoDot(); });
+    luaCtx.registerFunction("markActive", &DNSCryptContext::markActive);
+    luaCtx.registerFunction("markInactive", &DNSCryptContext::markInactive);
+    luaCtx.registerFunction("removeInactiveCertificate", &DNSCryptContext::removeInactiveCertificate);
+    luaCtx.registerFunction<void(std::shared_ptr<DNSCryptContext>::*)(const std::string& certFile, const std::string& keyFile, boost::optional<bool> active)>("loadNewCertificate", [](std::shared_ptr<DNSCryptContext> ctx, const std::string& certFile, const std::string& keyFile, boost::optional<bool> active) {
 
       if (ctx == nullptr) {
         throw std::runtime_error("DNSCryptContext::loadNewCertificate() called on a nil value");
@@ -42,7 +42,7 @@ void setupLuaBindingsDNSCrypt()
 
       ctx->loadNewCertificate(certFile, keyFile, active ? *active : true);
     });
-    g_lua.registerFunction<void(std::shared_ptr<DNSCryptContext>::*)(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, boost::optional<bool> active)>("addNewCertificate", [](std::shared_ptr<DNSCryptContext> ctx, const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, boost::optional<bool> active) {
+    luaCtx.registerFunction<void(std::shared_ptr<DNSCryptContext>::*)(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, boost::optional<bool> active)>("addNewCertificate", [](std::shared_ptr<DNSCryptContext> ctx, const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, boost::optional<bool> active) {
 
       if (ctx == nullptr) {
         throw std::runtime_error("DNSCryptContext::addNewCertificate() called on a nil value");
@@ -50,7 +50,7 @@ void setupLuaBindingsDNSCrypt()
 
       ctx->addNewCertificate(newCert, newKey, active ? *active : true);
     });
-    g_lua.registerFunction<std::map<int, std::shared_ptr<DNSCryptCertificatePair>>(std::shared_ptr<DNSCryptContext>::*)()>("getCertificatePairs", [](std::shared_ptr<DNSCryptContext> ctx) {
+    luaCtx.registerFunction<std::map<int, std::shared_ptr<DNSCryptCertificatePair>>(std::shared_ptr<DNSCryptContext>::*)()>("getCertificatePairs", [](std::shared_ptr<DNSCryptContext> ctx) {
       std::map<int, std::shared_ptr<DNSCryptCertificatePair>> result;
 
       if (ctx != nullptr) {
@@ -63,7 +63,7 @@ void setupLuaBindingsDNSCrypt()
       return result;
     });
 
-    g_lua.registerFunction<std::shared_ptr<DNSCryptCertificatePair>(std::shared_ptr<DNSCryptContext>::*)(size_t idx)>("getCertificatePair", [](std::shared_ptr<DNSCryptContext> ctx, size_t idx) {
+    luaCtx.registerFunction<std::shared_ptr<DNSCryptCertificatePair>(std::shared_ptr<DNSCryptContext>::*)(size_t idx)>("getCertificatePair", [](std::shared_ptr<DNSCryptContext> ctx, size_t idx) {
 
       if (ctx == nullptr) {
         throw std::runtime_error("DNSCryptContext::getCertificatePair() called on a nil value");
@@ -78,7 +78,7 @@ void setupLuaBindingsDNSCrypt()
       return result;
     });
 
-    g_lua.registerFunction<const DNSCryptCert(std::shared_ptr<DNSCryptContext>::*)(size_t idx)>("getCertificate", [](std::shared_ptr<DNSCryptContext> ctx, size_t idx) {
+    luaCtx.registerFunction<const DNSCryptCert(std::shared_ptr<DNSCryptContext>::*)(size_t idx)>("getCertificate", [](std::shared_ptr<DNSCryptContext> ctx, size_t idx) {
 
       if (ctx == nullptr) {
         throw std::runtime_error("DNSCryptContext::getCertificate() called on a nil value");
@@ -92,7 +92,7 @@ void setupLuaBindingsDNSCrypt()
       throw std::runtime_error("This DNSCrypt context has no certificate at index " + std::to_string(idx));
     });
 
-    g_lua.registerFunction<std::string(std::shared_ptr<DNSCryptContext>::*)()>("printCertificates", [](const std::shared_ptr<DNSCryptContext> ctx) {
+    luaCtx.registerFunction<std::string(std::shared_ptr<DNSCryptContext>::*)()>("printCertificates", [](const std::shared_ptr<DNSCryptContext> ctx) {
       ostringstream ret;
 
       if (ctx != nullptr) {
@@ -111,7 +111,7 @@ void setupLuaBindingsDNSCrypt()
       return ret.str();
     });
 
-    g_lua.registerFunction<void(DNSCryptContext::*)(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version)>("generateAndLoadInMemoryCertificate", [](DNSCryptContext& ctx, const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
+    luaCtx.registerFunction<void(DNSCryptContext::*)(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version)>("generateAndLoadInMemoryCertificate", [](DNSCryptContext& ctx, const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
         DNSCryptPrivateKey privateKey;
         DNSCryptCert cert;
 
@@ -127,13 +127,13 @@ void setupLuaBindingsDNSCrypt()
     });
 
     /* DNSCryptCertificatePair */
-    g_lua.registerFunction<const DNSCryptCert(std::shared_ptr<DNSCryptCertificatePair>::*)()>("getCertificate", [](const std::shared_ptr<DNSCryptCertificatePair> pair) {
+    luaCtx.registerFunction<const DNSCryptCert(std::shared_ptr<DNSCryptCertificatePair>::*)()>("getCertificate", [](const std::shared_ptr<DNSCryptCertificatePair> pair) {
       if (pair == nullptr) {
         throw std::runtime_error("DNSCryptCertificatePair::getCertificate() called on a nil value");
       }
       return pair->cert;
     });
-    g_lua.registerFunction<bool(std::shared_ptr<DNSCryptCertificatePair>::*)()>("isActive", [](const std::shared_ptr<DNSCryptCertificatePair> pair) {
+    luaCtx.registerFunction<bool(std::shared_ptr<DNSCryptCertificatePair>::*)()>("isActive", [](const std::shared_ptr<DNSCryptCertificatePair> pair) {
       if (pair == nullptr) {
         throw std::runtime_error("DNSCryptCertificatePair::isActive() called on a nil value");
       }
@@ -141,14 +141,14 @@ void setupLuaBindingsDNSCrypt()
     });
 
     /* DNSCryptCert */
-    g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getMagic", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.magic), sizeof(cert.magic)); });
-    g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getEsVersion", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.esVersion), sizeof(cert.esVersion)); });
-    g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getProtocolMinorVersion", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); });
-    g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getSignature", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signature), sizeof(cert.signature)); });
-    g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getResolverPublicKey", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); });
-    g_lua.registerFunction<std::string(DNSCryptCert::*)()>("getClientMagic", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); });
-    g_lua.registerFunction<uint32_t(DNSCryptCert::*)()>("getSerial", [](const DNSCryptCert& cert) { return cert.getSerial(); });
-    g_lua.registerFunction<uint32_t(DNSCryptCert::*)()>("getTSStart", [](const DNSCryptCert& cert) { return ntohl(cert.getTSStart()); });
-    g_lua.registerFunction<uint32_t(DNSCryptCert::*)()>("getTSEnd", [](const DNSCryptCert& cert) { return ntohl(cert.getTSEnd()); });
+    luaCtx.registerFunction<std::string(DNSCryptCert::*)()>("getMagic", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.magic), sizeof(cert.magic)); });
+    luaCtx.registerFunction<std::string(DNSCryptCert::*)()>("getEsVersion", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.esVersion), sizeof(cert.esVersion)); });
+    luaCtx.registerFunction<std::string(DNSCryptCert::*)()>("getProtocolMinorVersion", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); });
+    luaCtx.registerFunction<std::string(DNSCryptCert::*)()>("getSignature", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signature), sizeof(cert.signature)); });
+    luaCtx.registerFunction<std::string(DNSCryptCert::*)()>("getResolverPublicKey", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); });
+    luaCtx.registerFunction<std::string(DNSCryptCert::*)()>("getClientMagic", [](const DNSCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); });
+    luaCtx.registerFunction<uint32_t(DNSCryptCert::*)()>("getSerial", [](const DNSCryptCert& cert) { return cert.getSerial(); });
+    luaCtx.registerFunction<uint32_t(DNSCryptCert::*)()>("getTSStart", [](const DNSCryptCert& cert) { return ntohl(cert.getTSStart()); });
+    luaCtx.registerFunction<uint32_t(DNSCryptCert::*)()>("getTSEnd", [](const DNSCryptCert& cert) { return ntohl(cert.getTSEnd()); });
 #endif
 }
index a026cfb11b4d30a318c9f880423e6a1342ee1c1b..bf26914bc7e4f3a83a75ab595152447c49573bd2 100644 (file)
 #include "dnsdist-kvs.hh"
 #include "dnsdist-lua.hh"
 
-void setupLuaBindingsKVS(bool client)
+void setupLuaBindingsKVS(LuaContext& luaCtx, bool client)
 {
   /* Key Value Store objects */
-  g_lua.writeFunction("KeyValueLookupKeySourceIP", []() {
-    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySourceIP());
+  luaCtx.writeFunction("KeyValueLookupKeySourceIP", [](boost::optional<uint8_t> v4Mask, boost::optional<uint8_t> v6Mask) {
+    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySourceIP(v4Mask.get_value_or(32), v6Mask.get_value_or(128)));
   });
-  g_lua.writeFunction("KeyValueLookupKeyQName", [](boost::optional<bool> wireFormat) {
+  luaCtx.writeFunction("KeyValueLookupKeyQName", [](boost::optional<bool> wireFormat) {
     return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeyQName(wireFormat ? *wireFormat : true));
   });
-  g_lua.writeFunction("KeyValueLookupKeySuffix", [](boost::optional<size_t> minLabels, boost::optional<bool> wireFormat) {
+  luaCtx.writeFunction("KeyValueLookupKeySuffix", [](boost::optional<size_t> minLabels, boost::optional<bool> wireFormat) {
     return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySuffix(minLabels ? *minLabels : 0, wireFormat ? *wireFormat : true));
   });
-  g_lua.writeFunction("KeyValueLookupKeyTag", [](const std::string& tag) {
+  luaCtx.writeFunction("KeyValueLookupKeyTag", [](const std::string& tag) {
     return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeyTag(tag));
   });
 
 #ifdef HAVE_LMDB
-  g_lua.writeFunction("newLMDBKVStore", [client](const std::string& fname, const std::string& dbName) {
+  luaCtx.writeFunction("newLMDBKVStore", [client](const std::string& fname, const std::string& dbName) {
     if (client) {
       return std::shared_ptr<KeyValueStore>(nullptr);
     }
@@ -49,7 +49,7 @@ void setupLuaBindingsKVS(bool client)
 #endif /* HAVE_LMDB */
 
 #ifdef HAVE_CDB
-  g_lua.writeFunction("newCDBKVStore", [client](const std::string& fname, time_t refreshDelay) {
+  luaCtx.writeFunction("newCDBKVStore", [client](const std::string& fname, time_t refreshDelay) {
     if (client) {
       return std::shared_ptr<KeyValueStore>(nullptr);
     }
@@ -57,7 +57,7 @@ void setupLuaBindingsKVS(bool client)
   });
 #endif /* HAVE_CDB */
 
-  g_lua.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const boost::variant<ComboAddress, DNSName, std::string>, boost::optional<bool> wireFormat)>("lookup", [](std::shared_ptr<KeyValueStore>& kvs, const boost::variant<ComboAddress, DNSName, std::string> keyVar, boost::optional<bool> wireFormat) {
+  luaCtx.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const boost::variant<ComboAddress, DNSName, std::string>, boost::optional<bool> wireFormat)>("lookup", [](std::shared_ptr<KeyValueStore>& kvs, const boost::variant<ComboAddress, DNSName, std::string> keyVar, boost::optional<bool> wireFormat) {
     std::string result;
     if (!kvs) {
       return result;
@@ -65,7 +65,7 @@ void setupLuaBindingsKVS(bool client)
 
     if (keyVar.type() == typeid(ComboAddress)) {
       const auto ca = boost::get<ComboAddress>(&keyVar);
-      KeyValueLookupKeySourceIP lookup;
+      KeyValueLookupKeySourceIP lookup(32, 128);
       for (const auto& key : lookup.getKeys(*ca)) {
         if (kvs->getValue(key, result)) {
           return result;
@@ -89,7 +89,7 @@ void setupLuaBindingsKVS(bool client)
     return result;
   });
 
-  g_lua.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const DNSName&, boost::optional<size_t> minLabels, boost::optional<bool> wireFormat)>("lookupSuffix", [](std::shared_ptr<KeyValueStore>& kvs, const DNSName& dn, boost::optional<size_t> minLabels, boost::optional<bool> wireFormat) {
+  luaCtx.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const DNSName&, boost::optional<size_t> minLabels, boost::optional<bool> wireFormat)>("lookupSuffix", [](std::shared_ptr<KeyValueStore>& kvs, const DNSName& dn, boost::optional<size_t> minLabels, boost::optional<bool> wireFormat) {
     std::string result;
     if (!kvs) {
       return result;
@@ -105,7 +105,7 @@ void setupLuaBindingsKVS(bool client)
     return result;
   });
 
-  g_lua.registerFunction<bool(std::shared_ptr<KeyValueStore>::*)()>("reload", [](std::shared_ptr<KeyValueStore>& kvs) {
+  luaCtx.registerFunction<bool(std::shared_ptr<KeyValueStore>::*)()>("reload", [](std::shared_ptr<KeyValueStore>& kvs) {
     if (!kvs) {
       return false;
     }
index b534651ac12627ad508026e9ce0568603b5b28cf..2183231b9004de8a115e9594220d2b54cd996bb8 100644 (file)
 #include "dnsdist.hh"
 #include "dnsdist-lua.hh"
 
-void setupLuaBindingsPacketCache()
+void setupLuaBindingsPacketCache(LuaContext& luaCtx)
 {
   /* PacketCache */
-  g_lua.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional<std::unordered_map<std::string, boost::variant<bool, size_t>>> vars) {
+  luaCtx.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional<std::unordered_map<std::string, boost::variant<bool, size_t>>> vars) {
 
       bool keepStaleData = false;
       size_t maxTTL = 86400;
@@ -42,6 +42,7 @@ void setupLuaBindingsPacketCache()
       bool dontAge = false;
       bool deferrableInsertLock = true;
       bool ecsParsing = false;
+      bool cookieHashing = false;
 
       if (vars) {
 
@@ -84,28 +85,60 @@ void setupLuaBindingsPacketCache()
         if (vars->count("temporaryFailureTTL")) {
           tempFailTTL = boost::get<size_t>((*vars)["temporaryFailureTTL"]);
         }
+
+        if (vars->count("cookieHashing")) {
+          cookieHashing = boost::get<bool>((*vars)["cookieHashing"]);
+        }
       }
 
       auto res = std::make_shared<DNSDistPacketCache>(maxEntries, maxTTL, minTTL, tempFailTTL, maxNegativeTTL, staleTTL, dontAge, numberOfShards, deferrableInsertLock, ecsParsing);
 
       res->setKeepStaleData(keepStaleData);
+      res->setCookieHashing(cookieHashing);
 
       return res;
     });
-  g_lua.registerFunction("toString", &DNSDistPacketCache::toString);
-  g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull);
-  g_lua.registerFunction("purgeExpired", &DNSDistPacketCache::purgeExpired);
-  g_lua.registerFunction("expunge", &DNSDistPacketCache::expunge);
-  g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const DNSName& dname, boost::optional<uint16_t> qtype, boost::optional<bool> suffixMatch)>("expungeByName", [](
-              std::shared_ptr<DNSDistPacketCache> cache,
-              const DNSName& dname,
+  luaCtx.registerFunction<std::string(std::shared_ptr<DNSDistPacketCache>::*)()>("toString", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
+      if (cache) {
+        return cache->toString();
+      }
+      return std::string();
+    });
+  luaCtx.registerFunction<bool(std::shared_ptr<DNSDistPacketCache>::*)()>("isFull", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
+      if (cache) {
+        return cache->isFull();
+      }
+      return false;
+    });
+  luaCtx.registerFunction<size_t(std::shared_ptr<DNSDistPacketCache>::*)(size_t)>("purgeExpired", [](std::shared_ptr<DNSDistPacketCache>& cache, size_t upTo) {
+      if (cache) {
+        return cache->purgeExpired(upTo);
+      }
+      return static_cast<size_t>(0);
+    });
+  luaCtx.registerFunction<size_t(std::shared_ptr<DNSDistPacketCache>::*)(size_t)>("expunge", [](std::shared_ptr<DNSDistPacketCache>& cache, size_t upTo) {
+      if (cache) {
+        return cache->expunge(upTo);
+      }
+      return static_cast<size_t>(0);
+    });
+  luaCtx.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const boost::variant<DNSName, string>& dname, boost::optional<uint16_t> qtype, boost::optional<bool> suffixMatch)>("expungeByName", [](
+              std::shared_ptr<DNSDistPacketCache>& cache,
+              const boost::variant<DNSName, string>& dname,
               boost::optional<uint16_t> qtype,
               boost::optional<bool> suffixMatch) {
+                DNSName qname;
+                if (dname.type() == typeid(DNSName)) {
+                  qname = boost::get<DNSName>(dname);
+                }
+                if (dname.type() == typeid(string)) {
+                  qname = DNSName(boost::get<string>(dname));
+                }
                 if (cache) {
-                  g_outputBuffer="Expunged " + std::to_string(cache->expungeByName(dname, qtype ? *qtype : QType(QType::ANY).getCode(), suffixMatch ? *suffixMatch : false)) + " records\n";
+                  g_outputBuffer="Expunged " + std::to_string(cache->expungeByName(qname, qtype ? *qtype : QType(QType::ANY).getCode(), suffixMatch ? *suffixMatch : false)) + " records\n";
                 }
     });
-  g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)()>("printStats", [](const std::shared_ptr<DNSDistPacketCache> cache) {
+  luaCtx.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)()>("printStats", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
       if (cache) {
         g_outputBuffer="Entries: " + std::to_string(cache->getEntriesCount()) + "/" + std::to_string(cache->getMaxEntries()) + "\n";
         g_outputBuffer+="Hits: " + std::to_string(cache->getHits()) + "\n";
@@ -117,7 +150,7 @@ void setupLuaBindingsPacketCache()
         g_outputBuffer+="TTL Too Shorts: " + std::to_string(cache->getTTLTooShorts()) + "\n";
       }
     });
-  g_lua.registerFunction<std::unordered_map<std::string, uint64_t>(std::shared_ptr<DNSDistPacketCache>::*)()>("getStats", [](const std::shared_ptr<DNSDistPacketCache> cache) {
+  luaCtx.registerFunction<std::unordered_map<std::string, uint64_t>(std::shared_ptr<DNSDistPacketCache>::*)()>("getStats", [](const std::shared_ptr<DNSDistPacketCache>& cache) {
       std::unordered_map<std::string, uint64_t> stats;
       if (cache) {
         stats["entries"] = cache->getEntriesCount();
@@ -132,7 +165,7 @@ void setupLuaBindingsPacketCache()
       }
       return stats;
     });
-  g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const std::string& fname)>("dump", [](const std::shared_ptr<DNSDistPacketCache> cache, const std::string& fname) {
+  luaCtx.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)(const std::string& fname)>("dump", [](const std::shared_ptr<DNSDistPacketCache>& cache, const std::string& fname) {
       if (cache) {
 
         int fd = open(fname.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0660);
index e8d6350eb9629aad3f68b23b6b311aecea73e4cd..c8aade0affe053fa268074b5b2939215fb78b18c 100644 (file)
 #include "ipcipher.hh"
 #endif /* HAVE_LIBCRYPTO */
 
-void setupLuaBindingsProtoBuf(bool client, bool configCheck)
+#ifdef HAVE_FSTRM
+static void parseFSTRMOptions(const boost::optional<std::unordered_map<std::string, unsigned int>>& params, std::unordered_map<string, unsigned int>& options)
+{
+  if (!params) {
+    return;
+  }
+
+  static std::vector<std::string> const potentialOptions = { "bufferHint", "flushTimeout", "inputQueueSize", "outputQueueSize", "queueNotifyThreshold", "reopenInterval" };
+
+  for (const auto& potentialOption : potentialOptions) {
+    if (params->count(potentialOption)) {
+      options[potentialOption] = boost::get<unsigned int>(params->at(potentialOption));
+    }
+  }
+}
+#endif /* HAVE_FSTRM */
+
+void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck)
 {
 #ifdef HAVE_LIBCRYPTO
-  g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipencrypt", [](const ComboAddress& ca, const std::string& key) {
+  luaCtx.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipencrypt", [](const ComboAddress& ca, const std::string& key) {
       return encryptCA(ca, key);
     });
-  g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipdecrypt", [](const ComboAddress& ca, const std::string& key) {
+  luaCtx.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipdecrypt", [](const ComboAddress& ca, const std::string& key) {
       return decryptCA(ca, key);
     });
 
-  g_lua.writeFunction("makeIPCipherKey", [](const std::string& password) {
+  luaCtx.writeFunction("makeIPCipherKey", [](const std::string& password) {
       return makeIPCipherKey(password);
     });
 #endif /* HAVE_LIBCRYPTO */
 
   /* ProtobufMessage */
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(std::string)>("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(std::string)>("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) {
       message.addTag(strValue);
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(vector<pair<int, string>>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector<pair<int, string>>&tags) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(vector<pair<int, string>>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector<pair<int, string>>&tags) {
       for (const auto& tag : tags) {
         message.addTag(tag.second);
       }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(boost::optional <time_t> sec, boost::optional <uint32_t> uSec)>("setProtobufResponseType",
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(boost::optional <time_t> sec, boost::optional <uint32_t> uSec)>("setProtobufResponseType",
                                         [](DNSDistProtoBufMessage& message, boost::optional <time_t> sec, boost::optional <uint32_t> uSec) {
       message.setType(DNSProtoBufMessage::Response);
       message.setQueryTime(sec?*sec:0, uSec?*uSec:0);
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)>("addResponseRR", [](DNSDistProtoBufMessage& message,
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)>("addResponseRR", [](DNSDistProtoBufMessage& message,
                                                             const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob) {
       message.addRR(DNSName(strQueryName), uType, uClass, uTTL, strBlob);
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const Netmask&)>("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const DNSName&, uint16_t, uint16_t)>("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(size_t)>("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); });
-  g_lua.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const Netmask&)>("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); });
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const DNSName&, uint16_t, uint16_t)>("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); });
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(size_t)>("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); });
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); });
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); });
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); });
+  luaCtx.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); });
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
       message.setRequestor(addr);
       if (port) {
         message.setRequestorPort(*port);
       }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
       message.setRequestor(str);
       if (port) {
         message.setRequestorPort(*port);
       }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
       message.setResponder(addr);
       if (port) {
         message.setResponderPort(*port);
       }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
       message.setResponder(str);
       if (port) {
         message.setResponderPort(*port);
       }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setServerIdentity", [](DNSDistProtoBufMessage& message, const std::string& str) {
+  luaCtx.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setServerIdentity", [](DNSDistProtoBufMessage& message, const std::string& str) {
       message.setServerIdentity(str);
     });
 
-  g_lua.registerFunction<std::string(DnstapMessage::*)()>("toDebugString", [](const DnstapMessage& message) { return message.toDebugString(); });
-  g_lua.registerFunction<void(DnstapMessage::*)(const std::string&)>("setExtra", [](DnstapMessage& message, const std::string& str) {
+  luaCtx.registerFunction<std::string(DnstapMessage::*)()>("toDebugString", [](const DnstapMessage& message) { return message.toDebugString(); });
+  luaCtx.registerFunction<void(DnstapMessage::*)(const std::string&)>("setExtra", [](DnstapMessage& message, const std::string& str) {
       message.setExtra(str);
     });
 
   /* RemoteLogger */
-  g_lua.writeFunction("newRemoteLogger", [client,configCheck](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
+  luaCtx.writeFunction("newRemoteLogger", [client,configCheck](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
       if (client || configCheck) {
         return std::shared_ptr<RemoteLoggerInterface>(nullptr);
       }
       return std::shared_ptr<RemoteLoggerInterface>(new RemoteLogger(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? (*maxQueuedEntries*100) : 10000, reconnectWaitTime ? *reconnectWaitTime : 1, client));
     });
 
-  g_lua.writeFunction("newFrameStreamUnixLogger", [client,configCheck](const std::string& address) {
+  luaCtx.writeFunction("newFrameStreamUnixLogger", [client,configCheck](const std::string& address, boost::optional<std::unordered_map<std::string, unsigned int>> params) {
 #ifdef HAVE_FSTRM
       if (client || configCheck) {
         return std::shared_ptr<RemoteLoggerInterface>(nullptr);
       }
-      return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_UNIX, address, !client));
+
+      std::unordered_map<string, unsigned int> options;
+      parseFSTRMOptions(params, options);
+      return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_UNIX, address, !client, options));
 #else
       throw std::runtime_error("fstrm support is required to build an AF_UNIX FrameStreamLogger");
 #endif /* HAVE_FSTRM */
     });
 
-  g_lua.writeFunction("newFrameStreamTcpLogger", [client,configCheck](const std::string& address) {
+  luaCtx.writeFunction("newFrameStreamTcpLogger", [client,configCheck](const std::string& address, boost::optional<std::unordered_map<std::string, unsigned int>> params) {
 #if defined(HAVE_FSTRM) && defined(HAVE_FSTRM_TCP_WRITER_INIT)
       if (client || configCheck) {
         return std::shared_ptr<RemoteLoggerInterface>(nullptr);
       }
-      return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_INET, address, !client));
+
+      std::unordered_map<string, unsigned int> options;
+      parseFSTRMOptions(params, options);
+      return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_INET, address, !client, options));
 #else
       throw std::runtime_error("fstrm with TCP support is required to build an AF_INET FrameStreamLogger");
 #endif /* HAVE_FSTRM */
     });
 
-  g_lua.registerFunction<std::string(std::shared_ptr<RemoteLoggerInterface>::*)()>("toString", [](const std::shared_ptr<RemoteLoggerInterface>& logger) {
+  luaCtx.registerFunction<std::string(std::shared_ptr<RemoteLoggerInterface>::*)()>("toString", [](const std::shared_ptr<RemoteLoggerInterface>& logger) {
       if (logger) {
         return logger->toString();
       }
index d56b73510ad7a0864645417ef7a2adf44fa2e400..efa1c40dd0a1ef7e2f7561eef182b20b0b552798 100644 (file)
@@ -112,3 +112,4 @@ const char* dnsdist_ffi_server_get_name(const dnsdist_ffi_server_t* server) __at
 const char* dnsdist_ffi_server_get_name_with_addr(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
 int dnsdist_ffi_server_get_weight(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
 int dnsdist_ffi_server_get_order(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
+double dnsdist_ffi_server_get_latency(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
index f709feaa5d101adb248c10c6f89ca93d19503c25..1cc6cf2cbcfd83b86e08e722a889c47679ffbf48 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 #include "dnsdist-lua-ffi.hh"
+#include "dnsdist-lua.hh"
 #include "dnsdist-ecs.hh"
 
 uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq)
@@ -468,6 +469,11 @@ int dnsdist_ffi_server_get_order(const dnsdist_ffi_server_t* server)
   return server->server->order;
 }
 
+double dnsdist_ffi_server_get_latency(const dnsdist_ffi_server_t* server)
+{
+  return server->server->latencyUsec;
+}
+
 bool dnsdist_ffi_server_is_up(const dnsdist_ffi_server_t* server)
 {
   return server->server->isUp();
@@ -499,3 +505,15 @@ const std::string& getLuaFFIWrappers()
 )FFICodeContent";
   return code;
 }
+
+void setupLuaLoadBalancingContext(LuaContext& luaCtx)
+{
+  setupLuaBindings(luaCtx, true);
+  setupLuaBindingsDNSQuestion(luaCtx);
+  setupLuaBindingsKVS(luaCtx, true);
+  setupLuaVars(luaCtx);
+
+#ifdef LUAJIT_VERSION
+  luaCtx.executeCode(getLuaFFIWrappers());
+#endif
+}
index 2b81c60b20913db775fdd20d2d87f3d6c1d8bdeb..6988d451b87ed8a7e3cf3d480239e8c13c3681f5 100644 (file)
@@ -39,5 +39,5 @@ extern "C" {
   uint64_t dnsdist_ffi_stat_node_get_children_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
   uint64_t dnsdist_ffi_stat_node_get_children_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
   uint64_t dnsdist_ffi_stat_node_get_children_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_children_bytes(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
+  uint64_t dnsdist_ffi_stat_node_get_children_bytes_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
 }
diff --git a/pdns/dnsdistdist/dnsdist-proxy-protocol.cc b/pdns/dnsdistdist/dnsdist-proxy-protocol.cc
new file mode 100644 (file)
index 0000000..083b0d3
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 "dnsdist-proxy-protocol.hh"
+
+bool addProxyProtocol(DNSQuestion& dq)
+{
+  auto payload = makeProxyHeader(dq.tcp, *dq.remote, *dq.local, dq.proxyProtocolValues ? *dq.proxyProtocolValues : std::vector<ProxyProtocolValue>());
+  if ((dq.size - dq.len) < payload.size()) {
+    return false;
+  }
+
+  memmove(reinterpret_cast<char*>(dq.dh) + payload.size(), dq.dh, dq.len);
+  memcpy(dq.dh, payload.c_str(), payload.size());
+  dq.len += payload.size();
+
+  return true;
+}
+
+bool addProxyProtocol(std::vector<uint8_t>& buffer, bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values)
+{
+  auto payload = makeProxyHeader(tcp, source, destination, values);
+
+  auto previousSize = buffer.size();
+  if (payload.size() > (std::numeric_limits<size_t>::max() - previousSize)) {
+    return false;
+  }
+
+  buffer.resize(previousSize + payload.size());
+  std::copy_backward(buffer.begin(), buffer.begin() + previousSize, buffer.end());
+  std::copy(payload.begin(), payload.end(), buffer.begin());
+
+  return true;
+}
diff --git a/pdns/dnsdistdist/dnsdist-proxy-protocol.hh b/pdns/dnsdistdist/dnsdist-proxy-protocol.hh
new file mode 100644 (file)
index 0000000..433a7d2
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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 "dnsdist.hh"
+
+bool addProxyProtocol(DNSQuestion& dq);
+bool addProxyProtocol(std::vector<uint8_t>& buffer, bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values);
index 0474a1e1504b8d3c9acee069d7ee8426b40141ee..202e0b055364ca3f589fa2a2c5eeb5cf9a4f0e4f 100644 (file)
@@ -234,13 +234,9 @@ private:
 public:
   TimedIPSetRule()
   {
-    pthread_rwlock_init(&d_lock4, 0);
-    pthread_rwlock_init(&d_lock6, 0);
   }
   ~TimedIPSetRule()
   {
-    pthread_rwlock_destroy(&d_lock4);
-    pthread_rwlock_destroy(&d_lock6);
   }
   bool matches(const DNSQuestion* dq) const override
   {
@@ -302,7 +298,7 @@ public:
 
   void cleanup()
   {
-    time_t now=time(0);
+    time_t now = time(nullptr);
     {
       WriteLock rl(&d_lock4);
 
@@ -360,8 +356,8 @@ private:
   };
   std::unordered_map<IPv6, time_t, IPv6Hash> d_ip6s;
   std::unordered_map<uint32_t, time_t> d_ip4s;
-  mutable pthread_rwlock_t d_lock4;
-  mutable pthread_rwlock_t d_lock6;
+  mutable ReadWriteLock d_lock4;
+  mutable ReadWriteLock d_lock6;
 };
 
 
index 179779f15d3cdeeac279f7c8e3f55448acb91879..358f943b18bae084f47ead8461577d6565829b53 100644 (file)
@@ -234,7 +234,7 @@ void doSecPoll(const std::string& suffix)
   }
 
   if (releaseVersion) {
-    warnlog("Could not retrieve security status update for '%s' on %s", pkgv, queriedName);
+    warnlog("Failed to retrieve security status update for '%s' on %s", pkgv, queriedName);
   }
   else if (!g_secPollDone) {
     infolog("Not validating response for security status update, this is a non-release version.");
diff --git a/pdns/dnsdistdist/dnsdist-web.hh b/pdns/dnsdistdist/dnsdist-web.hh
new file mode 100644 (file)
index 0000000..1b5d001
--- /dev/null
@@ -0,0 +1,22 @@
+#pragma once
+
+struct WebserverConfig
+{
+  WebserverConfig()
+  {
+    acl.toMasks("127.0.0.1, ::1");
+  }
+
+  NetmaskGroup acl;
+  std::string password;
+  std::string apiKey;
+  boost::optional<std::map<std::string, std::string> > customHeaders;
+  std::mutex lock;
+};
+
+void setWebserverAPIKey(const boost::optional<std::string> apiKey);
+void setWebserverPassword(const std::string& password);
+void setWebserverACL(const std::string& acl);
+void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders);
+
+void dnsdistWebserverThread(int sock, const ComboAddress& local);
index c96c37c168ee21283dccb3f34af0dec2fcff33bf..12aadb9c72c949c2b8c2ef9585c7c798a6d79efd 100644 (file)
@@ -28,14 +28,19 @@ LockPersonality=true
 NoNewPrivileges=true
 PrivateDevices=true
 PrivateTmp=true
+# Setting PrivateUsers=true prevents us from opening our sockets
+ProtectClock=true
 ProtectControlGroups=true
 ProtectHome=true
+ProtectHostname=true
+ProtectKernelLogs=true
 ProtectKernelModules=true
 ProtectKernelTunables=true
 ProtectSystem=full
 RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
 RestrictNamespaces=true
 RestrictRealtime=true
+RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
 
index 9bb82b7f369f828f7b420ead2747c592c96169d1..806243d018f58a44cb2dcc80118cd88c8ca47adc 100644 (file)
@@ -42,7 +42,7 @@ So feel free to listen on the magic ``0.0.0.0`` or ``::`` addresses, dnsdist doe
 Modifying the ACL
 -----------------
 
-ACLs can be modfied at runtime from the :ref:`Console`.
+ACLs can be modified at runtime from the :ref:`Console`.
 To inspect the currently active :term:`ACL`, run :func:`showACL`.
 
 To add a new network range to the existing ACL, use :func:`addACL`:
index 643f467a02c281a196ed6ec268e9ae070c58ae40..004ebba58f4229da434c9a5da82c775ebb515320 100644 (file)
@@ -3,7 +3,7 @@ Using EDNS Client Subnet
 
 In order to provide the downstream server with the address of the real client, or at least the one talking to dnsdist, the ``useClientSubnet`` parameter can be used when creating a :func:`new server <newServer>`.
 This parameter indicates whether an EDNS Client Subnet option should be added to the request.
-If the incoming request already contains an EDNS Client Subnet value, it will not be overriden unless :func:`setECSOverride` is set to ``true``.
+If the incoming request already contains an EDNS Client Subnet value, it will not be overridden unless :func:`setECSOverride` is set to ``true``.
 The default source prefix-length is 24 for IPv4 and 56 for IPv6, meaning that for a query received from 192.0.2.42, the EDNS Client Subnet value sent to the backend will be 192.0.2.0.
 This can be changed with :func:`setECSSourcePrefixV4` and :func:`setECSSourcePrefixV6`.
 
index f9cc4ace693c5f8eefc084a427fad14cd8d200dc..966cab90f47365c2479c39c4d445b6531b6fee78 100644 (file)
@@ -11,6 +11,8 @@ These chapters contain information on the advanced features of dnsdist
    luaaction
    timedipsetrule
    ecs
+   xpf
+   proxyprotocol
    qpslimits
    ebpf
    tuning
diff --git a/pdns/dnsdistdist/docs/advanced/proxyprotocol.rst b/pdns/dnsdistdist/docs/advanced/proxyprotocol.rst
new file mode 100644 (file)
index 0000000..90db387
--- /dev/null
@@ -0,0 +1,9 @@
+Using the Proxy Protocol
+------------------------
+
+In order to provide the downstream server with the address of the real client, or at least the one talking to dnsdist, the ``useProxyProtocol`` parameter can be used when creating a :func:`new server <newServer>`.
+This parameter indicates whether a Proxy Protocol version 2 (binary) header should be prepended to the query before forwarding it to the backend, over UDP or TCP. This header contains the initial source and destination addresses and ports, and can also contain several custom values in a Type-Length-Value format. More information about the Proxy Protocol can be found at https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt
+
+Custom values can be added to the header via :meth:`DNSQuestion:setProxyProtocolValues` and :func:`SetProxyProtocolValuesAction`.
+
+As of 1.5.0 only outgoing Proxy Protocol support has been implemented, although support for parsing incoming Proxy Protocol headers will likely be implemented in the future.
index 46f846c68ca31f044b9d0df1e6470314d98a64b4..d50becb7f7320649906489248b47050040fd5023 100644 (file)
@@ -31,7 +31,7 @@ Large installations are advised to increase the default value at the cost of a s
 Most of the query processing is done in C++ for maximum performance, but some operations are executed in Lua for maximum flexibility:
 
  * Rules added by :func:`addLuaAction`
- * Server selection policies defined via :func:`setServerPolicyLua`, :func:`setServerPolicyLuaFFI` or :func:`newServerPolicy`
+ * Server selection policies defined via :func:`setServerPolicyLua`, :func:`setServerPolicyLuaFFI`, :func:`setServerPolicyLuaFFIPerThread` or :func:`newServerPolicy`
 
 While Lua is fast, its use should be restricted to the strict necessary in order to achieve maximum performance, it might be worth considering using LuaJIT instead of Lua.
 When Lua inspection is needed, the best course of action is to restrict the queries sent to Lua inspection by using :func:`addLuaAction` with a selector.
diff --git a/pdns/dnsdistdist/docs/advanced/xpf.rst b/pdns/dnsdistdist/docs/advanced/xpf.rst
new file mode 100644 (file)
index 0000000..abe30ce
--- /dev/null
@@ -0,0 +1,13 @@
+Using XPF
+---------
+
+In order to provide the downstream server with the address of the real client, or at least the one talking to dnsdist, the ``addXPF`` parameter can be used when creating a :func:`new server <newServer>`.
+This parameter indicates whether an experimental XPF record (from `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_) shall be added to the query. Since that record is experimental, there is currently no option code assigned to it, and therefore one needs to be specified as an argument to the ``addXPF`` parameter.
+
+The XPF record is an alternative to the use of EDNS Client Subnet which has the advantages of preserving any existing EDNS Client Subnet value sent by the client, and of passing along the original destination address, as well as the initial source and destination ports.
+
+If the incoming request already contains a XPF record, it will not be overwritten. Instead a new one will be added to the query and the existing one will be preserved.
+That might be an issue by allowing clients to spoof their source address by adding a forged XPF record to their query. That can be prevented by using a rule to drop incoming queries containing a XPF record (in that example the 65280 option code has been assigned to XPF):
+
+  addAction(RecordsTypeCountRule(DNSSection.Additional, 65280, 1, 65535), DropAction())
+
index 209123d3c86124c825de5f5599af89ca7d87f9a5..7dc7a90e9241367e1dc92d70d146248771dd1cb0 100644 (file)
@@ -1,6 +1,545 @@
 Changelog
 =========
 
+.. changelog::
+  :version: 1.5.0
+  :released: 30th of July 2020
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9231
+
+    Use explicit flag for the specific version of c++ we are targeting.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9320
+
+    Prevent a possible overflow via large Proxy Protocol values. (Valentei Sergey)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9348
+    :tickets: 9279
+
+    Avoid name clashes on Solaris derived systems.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9343
+
+    Resize hostname to final size in getCarbonHostname(). (Aki Tuomi)
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 9344
+
+    Fix compilation with h2o_socket_get_ssl_server_name().
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9346
+
+    Fix compilation on OpenBSD/amd64.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9356
+
+    Handle calling PacketCache methods on a nil object.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9360
+
+    Prevent a copy of a pool's backends when selecting a server.
+
+.. changelog::
+  :version: 1.5.0-rc4
+  :released: 7th of July 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9278
+
+    Prevent a race between the DoH handling threads
+
+.. changelog::
+  :version: 1.5.0-rc3
+  :released: 18th of June 2020
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9100
+
+    Less negatives in secpoll error messages improves readability.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9127
+    :tickets: 9125
+
+    Fix compilation on systems that do not define HOST_NAME_MAX
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9207
+
+    Use std::string_view when available (Rosen Penev)
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 9211
+    :tickets: 9206
+
+    Use non-blocking pipes to pass DoH queries/responses around
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9213
+
+    Do not use `using namespace std;`
+
+  .. change::
+    :tags: New Features
+    :pullreq: 9229
+
+    Implement an ACL in the internal web server
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9238
+    :tickets: 8038
+
+    Clean up dnsdistconf.lua as a default configuration file
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9244
+
+    Add optional masks to KeyValueLookupKeySourceIP
+
+.. changelog::
+  :version: 1.5.0-rc2
+  :released: 13th of May 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9031
+    :tickets: 9025
+
+    Fix compilation of the ports event multiplexer
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9042
+
+    Avoid copies in for loops
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9054
+
+    Build with -Wmissing-declarations -Wredundant-decls
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9016
+    :tickets: 9004
+
+    Use std::shuffle instead of std::random_shuffle
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9053
+
+    Get rid of a naked pointer in the /dev/poll event multiplexer
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9059
+
+    A few warnings fixed, reported by clang on OpenBSD
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 9068
+
+    Fix duplicated HTTP/1 counter in 'showDOHFrontends()'
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9057
+
+    Gracefully handle a failure to remove FD on (re)-connection
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9067
+
+    Wrap pthread objects
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 9084
+
+    Add the unit to the help for latency buckets
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9078
+
+    NetmaskTree: do not test node for null, the loop guarantees node is not null.
+
+.. changelog::
+  :version: 1.5.0-rc1
+  :released: 16th of April 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8955
+
+    On OpenBSD string_view is both in boost and std
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8956
+
+    Expose SuffixMatchNode::remove in Lua
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8962
+
+    Remove a std::move() preventing Return-Value Optimization in lmdb-safe.cc
+
+  .. change::
+    :tags: Bug Fixes, DNSCrypt
+    :pullreq: 8974
+
+    Keep accepting fragmented UDP datagrams on DNSCrypt binds
+
+  .. change::
+    :tags: Bug Fixes, DNSCrypt
+    :pullreq: 8976
+    :tickets: 8974
+
+    Accept UDP datagrams larger than 1500 bytes for DNSCrypt
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8996
+
+    Drop responses with the QR bit set to 0
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8994
+    :tickets: 8986
+
+    Add an option to control the size of the TCP listen queue
+
+.. changelog::
+  :version: 1.5.0-alpha1
+  :released: 20th of March 2020
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 7820
+
+    Don't start as root within a systemd environment
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8115
+    :tickets: 8098
+
+    Fix ECS addition when the OPT record is not the last one
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8171
+    :tickets: 4747
+
+    Add SetNegativeAndSOAAction() and its Lua binding
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8274
+
+    Implement dynamic blocking on ratio of rcode/total responses
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 8355
+
+    Rework NetmaskTree for better CPU and memory efficiency. (Stephan Bosch)
+
+  .. change::
+    :tags: Improvements, DNS over TLS
+    :pullreq: 8380
+
+    Switch the default DoT provider from GnuTLS to OpenSSL
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8456
+
+    Separate the check-config and client modes
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 8491
+
+    Implement parallel health checks
+
+  .. change::
+    :tags: New Features, Performance
+    :pullreq: 8505
+    :tickets: 7617
+
+    Implement LuaFFIRule, LuaFFIAction and LuaFFIResponseAction
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8529
+
+    Add the number of received bytes to StatNode entries
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 8538
+
+    Use move semantics when updating the content of the StateHolder
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8556
+    :tickets: 8534
+
+    Support setting the value of AA, AD and RA when self-generating answers
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8567
+    :tickets: 7387
+
+    Add bounded loads to the consistent hashing policy
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8580
+
+    pthread_rwlock_init() should be matched by pthread_rwlock_destroy()
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8591
+
+    Wait longer for the TLS ticket to arrive in our tests
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8604
+
+    Add missing exception message in KVS error
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8631
+
+    Replace include guard ifdef/define with pragma once (Chris Hofstaedtler)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8654
+
+    Dnsdist: LogResponseAction (phonedph1)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8657
+
+    Allow retrieving and deleting a backend via its UUID
+
+  .. change::
+    :tags: Bug Fixes, DNS over TLS
+    :pullreq: 8662
+
+    Display the correct DoT provider
+
+  .. change::
+    :tags: Improvements, Protobuf
+    :pullreq: 8702
+
+    Add the source and destination ports to the protobuf msg
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8722
+
+    Add spoofRawAction() to craft answers from raw bytes
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8733
+
+    Load an openssl configuration file, if any, during startup
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 8760
+    :tickets: 8573
+
+    Don't accept sub-paths of configured DoH URLs
+
+  .. change::
+    :tags: Bug Fixes, DNS over TLS
+    :pullreq: 8761
+
+    Use ref counting for the DoT TLS context
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 8762
+    :tickets: 8586
+
+    Implement Cache-Control headers in DoH
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 8772
+    :tickets: 8746
+
+    Add backend status to prometheus metrics
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8782
+
+    Add getTag()/setTag() Lua bindings for a DNSResponse
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 8783
+
+    Add 'IO wait' and 'steal' metrics on Linux
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8787
+    :tickets: 8442
+
+    Fix key logging for DNS over TLS
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 8812
+
+    Keep a masked network in the Netmask class
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8874
+
+    Add support for Proxy Protocol between dnsdist and the recursor
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8848
+
+    Add get*BindCount() functions
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8855
+
+    Fix a typo in the help/completion for getDNSCryptBindCount
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8856
+
+    Implement rmACL() (swoga)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8879
+
+    Remove unused lambda capture reported by clang++
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8882
+
+    Add sessionTimeout setting for TLS session lifetime (Matti Hiljanen)
+
+  .. change::
+    :tags: Bug Fixes, Protobuf
+    :pullreq: 8883
+    :tickets: 8629
+
+    Add 'queue full' metrics for our remote logger, log at debug only
+
+  .. change::
+    :tags: Improvements, Protobuf
+    :pullreq: 8887
+
+    Better handling of reconnections in Remote Logger
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS, DNS over TLS
+    :pullreq: 8899
+    :tickets: 8806
+
+    Document that the 'keyLogFile' option requires OpenSSL >= 1.1.1
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8900
+    :tickets: 8739
+
+    Detect {Libre,Open}SSL functions availability during configure
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 8905
+    :tickets: 8819
+
+    Change the default DoH path from / to /dns-query
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8909
+
+    Implement bounded loads for the whashed and wrandom policies
+
+  .. change::
+    :tags: Improvements, DNSTAP, Performance
+    :pullreq: 8937
+
+    Make FrameStream IO parameters configurable
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 8945
+    :tickets: 8661
+
+    Add support for the processing of X-Forwarded-For headers
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 8949
+
+    Set the DoH ticket rotation delay before loading tickets
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8950
+    :tickets: 8669
+
+    Warn on startup about low weight values with chashed
+
 .. changelog::
   :version: 1.4.0
   :released: 20th of November 2019
@@ -33,7 +572,7 @@ Changelog
     :tags: Improvements
     :pullreq: 8440
 
-    Fix -WShadow warnings (Aki Tuomi)
+    Fix -Wshadow warnings (Aki Tuomi)
 
   .. change::
     :tags: Improvements
@@ -337,7 +876,7 @@ Changelog
     :tags: Bug Fixes
     :pullreq: 7886
 
-     SuffixMatchTree: fix root removal, partial match of non-leaf nodes
+    SuffixMatchTree: fix root removal, partial match of non-leaf nodes
 
   .. change::
     :tags: Improvements
@@ -852,7 +1391,7 @@ Changelog
     :pullreq: 7585
     :tickets: 7534
 
-     Prevent 0-ttl cache hits
+    Prevent 0-ttl cache hits
 
   .. change::
     :tags: Improvements
@@ -1030,7 +1569,7 @@ Changelog
     :pullreq: 7015
     :tickets: 7004, 6990
 
-     Add support for exporting a server id in protobuf
+    Add support for exporting a server id in protobuf
 
   .. change::
     :tags: Improvements
@@ -1055,7 +1594,7 @@ Changelog
     :pullreq: 7064
     :tickets: 7060
 
-     Wrap GnuTLS and OpenSSL pointers in smart pointers
+    Wrap GnuTLS and OpenSSL pointers in smart pointers
 
   .. change::
     :tags: New Features
@@ -1213,7 +1752,7 @@ Changelog
     :pullreq: 6523
     :tickets: 6430
 
-     Tests: avoid failure on not-so-optimal distribution
+    Tests: avoid failure on not-so-optimal distribution
 
   .. change::
     :tags: New Features
@@ -1275,7 +1814,7 @@ Changelog
     :tags: Bug Fixes
     :pullreq: 6672
 
-     Fix reconnection handling
+    Fix reconnection handling
 
   .. change::
     :tags: Improvements
@@ -2047,7 +2586,7 @@ Changelog
     :tags: Improvements, Performance
     :pullreq: 5185
 
-    Add the possiblity to fill a :class:`NetmaskGroup` (using :meth:`NetmaskGroup:addMask`) from `exceeds*` results.
+    Add the possibility to fill a :class:`NetmaskGroup` (using :meth:`NetmaskGroup:addMask`) from `exceeds*` results.
 
   .. change::
     :tags: Improvements
index c6c6142041a0781a17e172b5f0fbea751a881ad5..35be131d86c4351719a17e2195953ea0aef22fa8 100644 (file)
@@ -85,7 +85,7 @@ changelog_render_pullreq = "https://github.com/PowerDNS/pdns/pull/%s"
 changelog_render_changeset = "https://github.com/PowerDNS/pdns/commit/%s"
 
 changelog_sections = ['New Features', 'Improvements', 'Bug Fixes', 'Removals']
-changelog_inner_tag_sort = ['Security', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
+changelog_inner_tag_sort = ['Security', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'DNSTAP', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
 
 changelog_render_tags = False
 
index 367b1fb25d3cf3c0c3ceac6991743b27633bbd3c..43f8f1af15d0979dd1bdfa36bb0ec1317730f991 100644 (file)
@@ -8,7 +8,7 @@ Glossary
 
   Open Resolver
     A recursive DNS server available for many hosts on the internet.
-    Ususally without adequate rate-limiting, allowing it to be used in reflection attacks.
+    Usually without adequate rate-limiting, allowing it to be used in reflection attacks.
 
   QPS
     Queries Per Second
index 167c1503e13c42267ee8762c18740e50d3980fc2..0217ca6057537f7cee62b6b07032962740ff25a2 100644 (file)
@@ -8,7 +8,7 @@ The first step is to define a cache with :func:`newPacketCache`, then to assign
   pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=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 parameters are optional and in seconds, except the last one which is a boolean.
++ The first parameter (10000) is the maximum number of entries stored in the cache, and is the only one required. All the other parameters are optional and in seconds, except the last one which is a boolean.
 
 + The second one (86400) is the maximum lifetime of an entry in the cache.
 
index 353ba944bf3d45813faeb4970546f64eb92d9630..ab0c23fdacd294f0c0c85f0b9dc95073d175bfdc 100644 (file)
@@ -17,13 +17,10 @@ In order to support multiple certificates and keys, for example an ECDSA and an
 
 The certificate chain presented by the server to an incoming client will then be selected based on the algorithms this client advertised support for.
 
-A fourth parameter may be added to specify the URL path(s) used by
-DoH. If you want your DoH server to handle
-``https://example.com/dns-query``, you have to add ``"/dns-query"`` to
-the call to :func:`addDOHLocal`. It is optional and defaults to ``/``, the root of your HTTP site.
+A fourth parameter may be added to specify the URL path(s) used by DoH. If you want your DoH server to handle ``https://example.com/dns-query-endpoint``, you have to add ``"/dns-query-endpoint"`` to
+the call to :func:`addDOHLocal`. It is optional and defaults to ``/`` in 1.4.0, and ``/dns-query`` since 1.5.0.
 
-The fifth parameter, if present, indicates various options. For
-instance, you use it to indicate custom HTTP headers. An example is::
+The fifth parameter, if present, indicates various options. For instance, you use it to indicate custom HTTP headers. An example is::
 
   addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["x-foo"]="bar"}}
 
@@ -31,8 +28,17 @@ A more complicated (and more realistic) example is when you want to indicate met
 
   addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/", {customResponseHeaders={["link"]="<https://example.com/policy.html> rel=\\"service-meta\\"; type=\\"text/html\\""}})
 
+It is also possible to set HTTP response rules to intercept HTTP queries early, before the DNS payload, if any, has been processed, to send custom responses including error pages, redirects or even serve static content. First a rule needs to be defined using :func:`newDOHResponseMapEntry`, then a set of rules can be applied to a DoH frontend via :meth:`DOHFrontend.setResponsesMap`.
+For example, to send an HTTP redirect to queries asking for ``/rfc``, the following configuration can be used:
+
+  map = { newDOHResponseMapEntry("^/rfc$", 307, "https://www.rfc-editor.org/info/rfc8484") }
+  dohFE = getDOHFrontend(0)
+  dohFE:setResponsesMap(map)
+
 In case you want to run DNS-over-HTTPS behind a reverse proxy you probably don't want to encrypt your traffic between reverse proxy and dnsdist.
 To let dnsdist listen for DoH queries over HTTP on localhost at port 8053 add one of the following to your config::
 
   addDOHLocal("127.0.0.1:8053")
-  addDOHLocal("127.0.0.1:8053", nil, nil, "/", { reusePort=true })
\ No newline at end of file
+  addDOHLocal("127.0.0.1:8053", nil, nil, "/", { reusePort=true })
+
+A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal.
index bb201aa309031a5dbb188e3ecb2ba79241c089c4..27b681dd1ae0fd1a2904e6c94d974f05be43a966 100644 (file)
@@ -16,3 +16,5 @@ In order to support multiple certificates and keys, for example an ECDSA and an
   addTLSLocal('192.0.2.55', {'/etc/ssl/certs/example.com.rsa.pem', '/etc/ssl/certs/example.com.ecdsa.pem'}, {'/etc/ssl/private/example.com.rsa.key', '/etc/ssl/private/example.com.ecdsa.key'})
 
 The certificate chain presented by the server to an incoming client will then be selected based on the algorithms this client advertised support for.
+
+A particular attention should be taken to the permissions of the certificate and key files. Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root, which is no longer true for dnsdist as of 1.5.0. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal.
index ff0ad6fdc3885a93300e2826bdef0e5519d57629..8b3ed9186f5360a07e24ab23bc38990290dbff6e 100644 (file)
@@ -19,7 +19,7 @@ They return a table whose key is a :class:`ComboAddress` object, representing th
 All exceed-functions are documented in the :ref:`Configuration Reference <exceedfuncs>`.
 
 Dynamic blocks drop matched queries by default, but this behavior can be changed with :func:`setDynBlocksAction`.
-For example, to send a REFUSED code instead of droppping the query::
+For example, to send a REFUSED code instead of dropping the query::
 
   setDynBlocksAction(DNSAction.Refused)
 
index 7e8ad323c244826a291ac6b217a4a530674a47f5..66aa68ac0375fef55d92d8ad88f34b4829f1a702 100644 (file)
@@ -112,6 +112,24 @@ Or::
 
   setServerPolicyLua("splitsetup", splitSetup)
 
+For performance reasons, 1.6.0 introduced per-thread Lua FFI policies that are run in a lock-free per-thread Lua context instead of the global one.
+This reduces contention between threads at the cost of preventing sharing data between threads for these policies. Since the policy needs to be recompiled
+in the context of each thread instead of the global one, Lua code that returns a function should be passed to the function as a string instead of directly
+passing the name of a function:
+
+.. code-block:: lua
+
+  setServerPolicyLuaFFIPerThread("luaffiroundrobin", [[
+    local ffi = require("ffi")
+    local C = ffi.C
+
+    local counter = 0
+    return function(servers_list, dq)
+      counter = counter + 1
+      return (counter % tonumber(C.dnsdist_ffi_servers_list_get_count(servers_list)))
+    end
+  ]])
+
 ServerPolicy Objects
 --------------------
 
@@ -143,6 +161,12 @@ ServerPolicy Objects
 
     Whether this policy is a native (C++) policy or a Lua-based one.
 
+  .. attribute:: ServerPolicy.isPerThread
+
+    .. versionadded: 1.6.0
+
+    Whether a FFI Lua-based policy is executed in a lock-free per-thread context instead of running in the global Lua context.
+
   .. attribute:: ServerPolicy.name
 
     The name of the policy.
@@ -197,6 +221,16 @@ Functions
   :param string name: name for this policy
   :param string function: name of the FFI function
 
+.. function:: setServerPolicyLuaFFIPerThread(name, code)
+
+  .. versionadded:: 1.6.0
+
+  Set server selection policy to one named ``name`` and the Lua FFI function returned by the Lua code passed in ``code``.
+  The resulting policy will be executed in a lock-free per-thread context, instead of running in the global Lua context.
+
+  :param string name: name for this policy
+  :param string code: Lua FFI code returning the function to execute as a server selection policy
+
 .. function:: setServFailWhenNoServer(value)
 
   If set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query.
old mode 100644 (file)
new mode 100755 (executable)
index 5bf4043..01e6992
@@ -9,10 +9,17 @@ To visually interact with dnsdist, try add :func:`webserver` to the configuratio
 
 Now point your browser at http://127.0.0.1:8083 and log in with any username, and that password. Enjoy!
 
+Since 1.5.0, only connections from 127.0.0.1 and ::1 are allowed by default. To allow connections from 192.0.2.0/24 but not from 192.0.2.1, instead:
+
+.. code-block:: lua
+
+  webserver("127.0.0.1:8083", "supersecretpassword", "supersecretAPIkey", {}, "192.0.2.0/24, !192.0.2.1")
+
+
 Security of the Webserver
 -------------------------
 
-The built-in webserver serves its content from inside the binary, this means it will not and connot read from disk.
+The built-in webserver serves its content from inside the binary, this means it will not and cannot read from disk.
 
 By default, our web server sends some security-related headers::
 
@@ -27,7 +34,7 @@ For example, to remove the X-Frame-Options header and add a X-Custom one:
 
 .. code-block:: lua
 
-  webserver("127.0.0.1:8080", "supersecret", "apikey", {["X-Frame-Options"]= "", ["X-Custom"]="custom"}
+  webserver("127.0.0.1:8080", "supersecret", "apikey", {["X-Frame-Options"]= "", ["X-Custom"]="custom"})
 
 Credentials can be changed over time using the :func:`setWebserverConfig` function.
 
@@ -38,7 +45,7 @@ To access the API, the `apikey` must be set in the :func:`webserver` function.
 Use the API, this key will need to be sent to dnsdist in the ``X-API-Key`` request header.
 An HTTP 401 response is returned when a wrong or no API key is received.
 A 404 response is generated is the requested endpoint does not exist.
-And a 405 response is returned when the HTTP methos is not allowed.
+And a 405 response is returned when the HTTP method is not allowed.
 
 URL Endpoints
 ~~~~~~~~~~~~~
@@ -340,6 +347,7 @@ URL Endpoints
   Allows you to update the ``allow-from`` :ref:`ACL <ACL>` with a list of netmasks.
 
   Make sure you made the API writable using :func:`setAPIWritable`.
+  Changes to the ACL are directly applied, no restart is required.
 
   **Example request**:
 
index 5722fd458bb16eca489bcbadcc9f2e472594f81d..85344c6df6c89380db094b3c4b83b4c7a7f20ddd 100644 (file)
@@ -2,7 +2,7 @@ Installing dnsdist
 ==================
 
 dnsdist only runs on UNIX-like systems and there are several ways to install dnsdist.
-The fastest way is using packages, either from your own operating system vendor or suppied by the PowerDNS project.
+The fastest way is using packages, either from your own operating system vendor or supplied by the PowerDNS project.
 Building from source is also supported.
 
 
@@ -12,7 +12,7 @@ Installing from Packages
 If dnsdist is available in your operating system's software repositories, install it from there.
 However, the version of dnsdist in the repositories might be an older version that might not have a feature that was added in a later version.
 Or you might want to be brave and try a development snapshot from the master branch.
-PowerDNS provides software respositories for the most popular distributions.
+PowerDNS provides software repositories for the most popular distributions.
 Visit https://repo.powerdns.com for more information and installation instructions.
 
 Debian
index cb51d14bc6557c9f5ce8e169780398b23857d31b..359c50877ec746ec3cbd6d9f8bed2aa1692b35d2 100644 (file)
@@ -70,6 +70,9 @@ Listen Sockets
   .. versionchanged:: 1.4.0
     Removed ``doTCP`` from the options. A listen socket on TCP is always created.
 
+  .. versionchanged:: 1.5.0
+    Added ``tcpListenQueueSize`` parameter.
+
   Add to the list of listen addresses.
 
   :param str address: The IP Address with an optional port to listen on.
@@ -83,6 +86,7 @@ Listen Sockets
   * ``tcpFastOpenQueueSize=0``: int - Set the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0.
   * ``interface=""``: str - Set the network interface to use.
   * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function.
+  * ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
 
   .. code-block:: lua
 
@@ -109,8 +113,8 @@ Listen Sockets
   .. versionadded:: 1.4.0
 
   .. versionchanged:: 1.5.0
-    ``sendCacheControlHeaders``, ``sessionTimeout`` options added.
-    ``url`` now defaults to ``/dns-query`` instead of ``/``
+    ``internalPipeBufferSize``, ``sendCacheControlHeaders``, ``sessionTimeout``, ``trustForwardedForHeader`` options added.
+    ``url`` now defaults to ``/dns-query`` instead of ``/``. Added ``tcpListenQueueSize`` parameter.
 
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
@@ -119,7 +123,7 @@ Listen Sockets
                       The default port is 443.
   :param str certFile(s): The path to a X.509 certificate file in PEM format, or a list of paths to such files.
   :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones.
-  :param str-or-list urls: A base URL, or a list of base URLs, to accept queries on. Any query with a path under one of these will be treated as a DoH query. The default is /dns-query.
+  :param str-or-list urls: The path part of a URL, or a list of paths, to accept queries on. Any query with a path under one of these will be treated as a DoH query. The default is /dns-query.
   :param table options: A table with key: value pairs with listen options.
 
   Options:
@@ -141,9 +145,12 @@ Listen Sockets
   * ``sessionTimeout``: int - Set the TLS session lifetime in seconds, this is used both for TLS ticket lifetime and for sessions kept in memory.
   * ``sessionTickets``: bool - Whether session resumption via session tickets is enabled. Default is true, meaning tickets are enabled.
   * ``numberOfStoredSessions``: int - The maximum number of sessions kept in memory at the same time. Default is 20480. Setting this value to 0 disables stored session entirely.
-  * ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used.
+  * ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used. For OpenSSL >= 1.1.1, setting this option also enables the temporary re-prioritization of the ChaCha20-Poly1305 cipher if the client prioritizes it.
   * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. Note that this feature requires OpenSSL >= 1.1.1.
   * ``sendCacheControlHeaders``: bool - Whether to parse the response to find the lowest TTL and set a HTTP Cache-Control header accordingly. Default is true.
+  * ``trustForwardedForHeader``: bool - Whether to parse any existing X-Forwarded-For header in the HTTP query and use the right-most value as the client source address and port, for ACL checks, rules, logging and so on. Default is false.
+  * ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
+  * ``internalPipeBufferSize=0``: int - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used.
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
@@ -156,7 +163,7 @@ Listen Sockets
   .. versionchanged:: 1.4.0
     ``ciphersTLS13``, ``minTLSVersion``, ``ocspResponses``, ``preferServerCiphers``, ``keyLogFile`` options added.
   .. versionchanged:: 1.5.0
-    ``sessionTimeout`` option added.
+    ``sessionTimeout`` and ``tcpListenQueueSize`` options added.
 
   Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
 
@@ -173,7 +180,7 @@ Listen Sockets
   * ``interface=""``: str - Set the network interface to use.
   * ``cpus={}``: table - Set the CPU affinity for this listener thread, asking the scheduler to run it on a single CPU id, or a set of CPU ids. This parameter is only available if the OS provides the pthread_setaffinity_np() function.
   * ``provider``: str - The TLS library to use between GnuTLS and OpenSSL, if they were available and enabled at compilation time. Default is to use OpenSSL when available.
-  * ``ciphers``: str - The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provder is used, ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
+  * ``ciphers``: str - The TLS ciphers to use. The exact format depends on the provider used. When the OpenSSL provider is used, ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
   * ``ciphersTLS13``: str - The ciphers to use for TLS 1.3, when the OpenSSL provider is used. When the GnuTLS provider is used, ``ciphers`` applies regardless of the TLS protocol and this setting is not used.
   * ``numberOfTicketsKeys``: int - The maximum number of tickets keys to keep in memory at the same time, if the provider supports it (GnuTLS doesn't, OpenSSL does). Only one key is marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. Default to 5.
   * ``ticketKeyFile``: str - The path to a file from where TLS tickets keys should be loaded, to support RFC 5077. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. The OpenSSL provider supports several tickets keys to be able to decrypt existing sessions after the rotation, while the GnuTLS provider only supports one key.
@@ -183,8 +190,9 @@ Listen Sockets
   * ``numberOfStoredSessions``: int - The maximum number of sessions kept in memory at the same time. At this time this is only supported by the OpenSSL provider, as stored sessions are not supported with the GnuTLS one. Default is 20480. Setting this value to 0 disables stored session entirely.
   * ``ocspResponses``: list - List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses.
   * ``minTLSVersion``: str - Minimum version of the TLS protocol to support. Possible values are 'tls1.0', 'tls1.1', 'tls1.2' and 'tls1.3'. Default is to require at least TLS 1.0. Note that this value is ignored when the GnuTLS provider is in use, and the ``ciphers`` option should be set accordingly instead. For example, 'NORMAL:!VERS-TLS1.0:!VERS-TLS1.1' will disable TLS 1.0 and 1.1.
-  * ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used.
+  * ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is true, meaning that the order of the server is used. For OpenSSL >= 1.1.1, setting this option also enables the temporary re-prioritization of the ChaCha20-Poly1305 cipher if the client prioritizes it.
   * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. Note that this feature requires OpenSSL >= 1.1.1.
+  * ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
 
 .. function:: setLocal(address[, options])
 
@@ -224,6 +232,14 @@ Control Socket, Console and Webserver
 
   :param str netmask: A CIDR netmask, e.g. ``"192.0.2.0/24"``. Without a subnetmask, only the specific address is allowed.
 
+.. function:: clearConsoleHistory()
+
+  .. versionadded:: 1.6.0
+
+  Clear the internal (in-memory) buffers of console commands. These buffers are used to provide the :func:`delta` command and
+  console completion and history, and can end up being quite large when a lot of commands are issued via the console, consuming
+  a noticeable amount of memory.
+
 .. function:: controlSocket(address)
 
   Bind to ``addr`` and listen for a connection for the console. Since 1.3.0 only connections from local users are allowed
@@ -233,6 +249,10 @@ Control Socket, Console and Webserver
 
   :param str address: An IP address with optional port. By default, the port is 5199.
 
+.. function:: delta()
+
+  Issuing `delta` on the console will print the changes to the configuration that have been made since startup.
+
 .. function:: inClientStartup()
 
   Returns true while the console client is parsing the configuration.
@@ -289,7 +309,10 @@ Control Socket, Console and Webserver
 Webserver configuration
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-.. function:: webserver(listen_address, password[, apikey[, custom_headers]])
+.. function:: webserver(listen_address, password[, apikey[, custom_headers[, acl]]])
+
+  .. versionchanged:: 1.5.0
+    ``acl`` optional parameter added.
 
   Launch the :doc:`../guides/webserver` with statistics and the API.
 
@@ -297,6 +320,7 @@ Webserver configuration
   :param str password: The password required to access the webserver
   :param str apikey: The key required to access the API
   :param {[str]=str,...} custom_headers: Allows setting custom headers and removing the defaults
+  :param str acl: List of netmasks, as a string, that are allowed to open a connection to the web server. Defaults to "127.0.0.1, ::1". It accepts the same syntax that :func:`NetmaskGroup:addMask` does
 
 .. function:: setAPIWritable(allow [,dir])
 
@@ -311,6 +335,9 @@ Webserver configuration
 
   .. versionadded:: 1.3.3
 
+  .. versionchanged:: 1.5.0
+    ``acl`` optional parameter added.
+
   Setup webserver configuration. See :func:`webserver`.
 
   :param table options: A table with key: value pairs with webserver options.
@@ -320,7 +347,8 @@ Webserver configuration
   * ``password=newPassword``: string - Changes the API password
   * ``apiKey=newKey``: string - Changes the API Key (set to an empty string do disable it)
   * ``custom_headers={[str]=str,...}``: map of string - Allows setting custom headers and removing the defaults.
-                 
+  * ``acl=newACL``: string - List of IP addresses, as a string, that are allowed to open a connection to the web server. Defaults to "127.0.0.1, ::1".
+
 Access Control Lists
 ~~~~~~~~~~~~~~~~~~~~
 
@@ -407,6 +435,9 @@ Servers
   .. versionchanged:: 1.4.0
     Added ``checkInterval``, ``checkTimeout`` and ``rise`` to server_table.
 
+  .. versionchanged:: 1.5.0
+    Added ``useProxyProtocol`` to server_table.
+
   Add a new backend server. Call this function with either a string::
 
     newServer(
@@ -449,7 +480,9 @@ Servers
                              -- using the experimental XPF record from `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ and the specified option code. Default is disabled (0)
       sockets=NUM,           -- Number of sockets (and thus source ports) used toward the backend server, defaults to a single one
       disableZeroScope=BOOL, -- Disable the EDNS Client Subnet 'zero scope' feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup. This requires the ``parseECS`` option of the corresponding cache to be set to true
-      rise=NUM               -- Require NUM consecutive successful checks before declaring the backend up, default: 1
+      rise=NUM,              -- Require NUM consecutive successful checks before declaring the backend up, default: 1
+      useProxyProtocol=BOOL, -- Add a proxy protocol header to the query, passing along the client's IP address and port along with the original destination address and port. Default is disabled.
+      reconnectOnUp=BOOL     -- Close and reopen the sockets when a server transits from Down to Up. This helps when an interface is missing when dnsdist is started. Default is disabled.
     })
 
   :param str server_string: A simple IP:PORT string.
@@ -495,6 +528,14 @@ A server object returned by :func:`getServer` can be manipulated with these func
 
     :param str pool: The pool to add the server to
 
+  .. method:: Server:getLatency() -> double
+
+    .. versionadded:: 1.6.0
+
+    Return the average latency of this server over the last 128 UDP queries, in microseconds.
+
+    :returns: The number of outstanding queries
+
   .. method:: Server:getName() -> string
 
     Get the name of this server.
@@ -665,6 +706,9 @@ See :doc:`../guides/cache` for a how to.
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.6.0
+    ``cookieHashing`` parameter added.
+
   Creates a new :class:`PacketCache` with the settings specified.
 
   :param int maxEntries: The maximum number of entries in this cache
@@ -681,6 +725,7 @@ See :doc:`../guides/cache` for a how to.
   * ``parseECS=false``: bool - Whether any EDNS Client Subnet option present in the query should be extracted and stored to be able to detect hash collisions involving queries with the same qname, qtype and qclass but a different incoming ECS value. Enabling this option adds a parsing cost and only makes sense if at least one backend might send different responses based on the ECS value, so it's disabled by default. Enabling this option is required for the 'zero scope' option to work
   * ``staleTTL=60``: int - When the backend servers are not reachable, and global configuration ``setStaleCacheEntriesTTL`` is set appropriately, TTL that will be used when a stale cache entry is returned.
   * ``temporaryFailureTTL=60``: int - On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds..
+  * ``cookieHashing=false``: bool - Whether EDNS Cookie values will be hashed, resulting in separate entries for different cookies in the packet cache. This is required if the backend is sending answers with EDNS Cookies, otherwise a client might receive an answer with the wrong cookie.
 
 .. class:: PacketCache
 
@@ -705,11 +750,14 @@ See :doc:`../guides/cache` for a how to.
     .. versionchanged:: 1.2.0
       ``suffixMatch`` parameter added.
 
+    .. versionchanged:: 1.6.0
+      ``name`` can now also be a string
+
     Remove entries matching ``name`` and type from the cache.
 
     :param DNSName name: The name to expunge
     :param int qtype: The type to expunge, can be a pre-defined :ref:`DNSQType`
-    :param bool suffixMatch: When set to true, remove al entries under ``name``
+    :param bool suffixMatch: When set to true, remove all entries under ``name``
 
   .. method:: PacketCache:getStats()
 
@@ -1124,6 +1172,37 @@ faster than the existing rules.
     :param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
     :param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
 
+  .. method:: DynBlockRulesGroup:setSuffixMatchRule(seconds, reason, blockingTime, action , visitor)
+
+    .. versionadded:: 1.4.0
+
+    Set a Lua visitor function that will be called for each label of every domain seen in queries and responses. The function receives a `StatNode` object representing the stats of the parent, a second one with the stats of the current label and one with the stats of the current node plus all its children.
+    Note that this function will not be called if a FFI version has been set using :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`
+    If the function returns true, the current label will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters.
+    Selected domains can be excluded from this processing using the :meth:`DynBlockRulesGroup:excludeDomains` method.
+
+    This replaces the existing :func:`addDynBlockSMT` function.
+
+    :param int seconds: Number of seconds the rate has been exceeded
+    :param string reason: The message to show next to the blocks
+    :param int blockingTime: The number of seconds this block to expire
+    :param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+    :param function visitor: The Lua function to call.
+
+  .. method:: DynBlockRulesGroup:setSuffixMatchRuleFFI(seconds, reason, blockingTime, action , visitor)
+
+    .. versionadded:: 1.4.0
+
+    Set a Lua FFI visitor function that will be called for each label of every domain seen in queries and responses. The function receives a `dnsdist_ffi_stat_node_t` object containing the stats of the parent, a second one with the stats of the current label and one with the stats of the current node plus all its children.
+    If the function returns true, the current label will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters.
+    Selected domains can be excluded from this processing using the :meth:`DynBlockRulesGroup:excludeDomains` method.
+
+    :param int seconds: Number of seconds the rate has been exceeded
+    :param string reason: The message to show next to the blocks
+    :param int blockingTime: The number of seconds this block to expire
+    :param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+    :param function visitor: The Lua FFI function to call.
+
   .. method:: DynBlockRulesGroup:apply()
 
     Walk the in-memory query and response ring buffers and apply the configured rate-limiting rules, adding dynamic blocks when the limits have been exceeded.
@@ -1136,13 +1215,21 @@ faster than the existing rules.
 
     :param bool quiet: True means that insertions will not be logged, false that they will. Default is false.
 
+  .. method:: DynBlockRulesGroup:excludeDomains(domains)
+
+    .. versionadded:: 1.4.0
+
+    Exclude this domain, or list of domains, meaning that no dynamic block will ever be inserted for this domain via :meth:`DynBlockRulesGroup:setSuffixMatchRule` or :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`. Default to empty, meaning rules are applied to all domains.
+
+    :param str domain: A domain, or list of domains, as strings, like for example "powerdns.com"
+
   .. method:: DynBlockRulesGroup:excludeRange(netmasks)
 
     .. versionadded:: 1.3.1
 
     Exclude this range, or list of ranges, meaning that no dynamic block will ever be inserted for clients in that range. Default to empty, meaning rules are applied to all ranges. When used in combination with :meth:`DynBlockRulesGroup:includeRange`, the more specific entry wins.
 
-    :param int netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
+    :param list netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
 
   .. method:: DynBlockRulesGroup:includeRange(netmasks)
 
@@ -1150,7 +1237,7 @@ faster than the existing rules.
 
     Include this range, or list of ranges, meaning that rules will be applied to this range. When used in combination with :meth:`DynBlockRulesGroup:excludeRange`, the more specific entry wins.
 
-    :param int netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
+    :param list netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
 
   .. method:: DynBlockRulesGroup:toString()
 
@@ -1158,6 +1245,49 @@ faster than the existing rules.
 
     Return a string describing the rules and range exclusions of this DynBlockRulesGroup.
 
+StatNode
+~~~~~~~~
+
+.. class:: StatNode
+
+  Represent metrics about a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRul` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`. Note that some nodes includes the metrics for their children as well as their own.
+
+  .. attribute:: StatNode.bytes
+
+    The number of bytes for all responses returned for that node.
+
+  .. attribute:: StatNode.drops
+
+    The number of drops for that node.
+
+  .. attribute:: StatNode.fullname
+
+    The complete name of that node, ie 'www.powerdns.com'.
+
+  .. attribute:: StatNode.labelsCount
+
+    The number of labels in that node, for example 3 for 'www.powerdns.com'.
+
+  .. attribute:: StatNode.noerrors
+
+    The number of No Error answers returned for that node.
+
+  .. attribute:: StatNode.nxdomains
+
+    The number of NXDomain answers returned for that node.
+
+  .. attribute:: StatNode.queries
+
+    The number of queries for that node.
+
+  .. attribute:: StatNode.servfails
+
+    The number of Server Failure answers returned for that node.
+
+  .. method:: StatNode:numChildren
+
+    The number of children of that node.
+
 SuffixMatchNode
 ~~~~~~~~~~~~~~~
 
@@ -1186,6 +1316,16 @@ If you are looking for exact name matching, your might want to consider using a
     :param string name: The suffix to add to the set.
     :param table name: The suffixes to add to the set. Elements of the table should be of the same type, either DNSName or string.
 
+  .. method:: SuffixMatchNode:remove(name)
+
+    .. versionadded:: 1.5.0
+
+    Remove a suffix from the current set.
+
+    :param DNSName name: The suffix to remove from the set.
+    :param string name: The suffix to remove from the set.
+    :param table name: The suffixes to remove from the set. Elements of the table should be of the same type, either DNSName or string.
+
   .. method:: SuffixMatchNode:check(name) -> bool
 
     Return true if the given name is a sub-domain of one of those in the set, and false otherwise.
@@ -1260,7 +1400,7 @@ DOHFrontend
 
   .. versionadded:: 1.4.0
 
-  Return a DOHResponseMapEntry that can be used with :meth:`DOHFrontend.setResponsesMap`. Every query whose path matches the regular expression supplied in ``regex`` will be immediately answered with a HTTP response.
+  Return a DOHResponseMapEntry that can be used with :meth:`DOHFrontend.setResponsesMap`. Every query whose path is listed in the ``urls`` parameter to :func:`addDOHLocal` and matches the regular expression supplied in ``regex`` will be immediately answered with a HTTP response.
   The status of the HTTP response will be the one supplied by ``status``, and the content set to the one supplied by ``content``, except if the status is a redirection (3xx) in which case the content is expected to be the URL to redirect to.
 
   :param str regex: A regular expression to match the path against.
@@ -1313,8 +1453,8 @@ record if the received request had one, which is the case by default and can be
 :func:`setAddEDNSToSelfGeneratedResponses`.
 
 We must, however, provide a responder's maximum payload size in this record, and we can't easily know the
-maximum payload size of the actual backend so we need to provide one. The default value is 1500 and can be
-overriden using :func:`setPayloadSizeOnSelfGeneratedAnswers`.
+maximum payload size of the actual backend so we need to provide one. The default value is 1232 since 1.6.0,
+and can be overridden using :func:`setPayloadSizeOnSelfGeneratedAnswers`.
 
 .. function:: setAddEDNSToSelfGeneratedResponses(add)
 
@@ -1328,10 +1468,13 @@ overriden using :func:`setPayloadSizeOnSelfGeneratedAnswers`.
 
   .. versionadded:: 1.3.3
 
+  .. versionchanged:: 1.6.0
+    Default value changed from 1500 to 1232.
+
   Set the UDP payload size advertised via EDNS on self-generated responses. In accordance with
   :rfc:`RFC 6891 <6891#section-6.2.5>`, values lower than 512 will be treated as equal to 512.
 
-  :param int payloadSize: The responder's maximum UDP payload size, in bytes. Default is 1500.
+  :param int payloadSize: The responder's maximum UDP payload size, in bytes. Default is 1232 since 1.6.0, it was 1500 before.
 
 Security Polling
 ~~~~~~~~~~~~~~~~
old mode 100644 (file)
new mode 100755 (executable)
index aba032b..1b1d376
@@ -117,7 +117,7 @@ These constants represent an Action that can be returned from :func:`LuaAction`
  * ``DNSAction.Pool``: use the specified pool to forward this query
  * ``DNSAction.Refused``: return a response with a Refused rcode
  * ``DNSAction.ServFail``: return a response with a ServFail rcode
- * ``DNSAction.Spoof``: spoof the response using the supplied IPv4 (A), IPv6 (AAAA) or string (CNAME) value
+ * ``DNSAction.Spoof``: spoof the response using the supplied IPv4 (A), IPv6 (AAAA) or string (CNAME) value. TTL will be 60 seconds.
  * ``DNSAction.SpoofRaw``: spoof the response using the supplied raw value as record data
  * ``DNSAction.Truncate``: truncate the response
  * ``DNSAction.NoRecurse``: set rd=0 on the query
index 79e1ed9be7e9784437ac8bae7107e434102e550c..38e66c6c273b59c361099b1fd99ab199eda505d7 100644 (file)
@@ -3,7 +3,7 @@
 DNSName objects
 ===============
 
-A :class:`DNSName` object represents a name in the DNS. It has serveral functions that can manipulate it without conversions to strings.
+A :class:`DNSName` object represents a name in the DNS. It has several functions that can manipulate it without conversions to strings.
 Creating a ``DNSName`` is done with the :func:`newDNSName`::
 
   myname = newDNSName("www.example.com")
@@ -52,6 +52,10 @@ Functions and methods of a ``DNSName``
 
     :param DNSName name: The name to check against
 
+  .. method:: DNSName:toDNSString() -> string
+
+    Returns a wire format form of the DNSName, suitable for usage in :func:`SpoofRawAction`.
+
   .. method:: DNSName:toString() -> string
               DNSName:tostring() -> string
 
index 48bd895841663a6d6ac7096a5774c06231b005ae..9da5b96055725357b2af1f03f4c5a67f613c2cd0 100644 (file)
@@ -11,20 +11,50 @@ As an extension, :program:`dnsdist` can send raw dnstap protobuf messages over a
 
 To use FrameStream transport, :program:`dnsdist` must have been built with `libfstrm`.
 
-.. function:: newFrameStreamUnixLogger(path)
+.. function:: newFrameStreamUnixLogger(path [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Create a Frame Stream Logger object, to use with :func:`DnstapLogAction` and :func:`DnstapLogResponseAction`.
   This version will log to a local AF_UNIX socket.
 
   :param string path: A local AF_UNIX socket path. Note that most platforms have a rather short limit on the length.
+  :param table options: A table with key: value pairs with options.
+
+  The following options apply to the settings of the framestream library. Refer to the documentation of that
+  library for the default and allowed values for these options, as well as their exact descriptions.
+  For all these options, absence or a zero value has the effect of using the library-provided default value.
+
+  * ``bufferHint=0``: unsigned
+  * ``flushTimeout=0``: unsigned
+  * ``inputQueueSize=0``: unsigned
+  * ``outputQueueSize=0``: unsigned
+  * ``queueNotifyThreshold=0``: unsigned
+  * ``reopenInterval=0``: unsigned
+
+.. function:: newFrameStreamTcpLogger(address [, options])
 
-.. function:: newFrameStreamTcpLogger(address)
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Create a Frame Stream Logger object, to use with :func:`DnstapLogAction` and :func:`DnstapLogResponseAction`.
   This version will log to a possibly remote TCP socket.
   Needs tcp_writer support in libfstrm.
 
   :param string address: An IP:PORT combination where the logger will connect to.
+  :param table options: A table with key: value pairs with options.
+
+  The following options apply to the settings of the framestream library. Refer to the documentation of that
+  library for the default and allowed values for these options, as well as their exact descriptions.
+  For all these options, absence or a zero value has the effect of using the library-provided default value.
+
+  * ``bufferHint=0``: unsigned
+  * ``flushTimeout=0``: unsigned
+  * ``inputQueueSize=0``: unsigned
+  * ``outputQueueSize=0``: unsigned
+  * ``queueNotifyThreshold=0``: unsigned
+  * ``reopenInterval=0``: unsigned
 
 .. class:: DnstapMessage
 
index da2672f7534ca04c8a94ba5054a1b96f54e33830..378f942f2a5580879cdc1ce6c51e722fdd6c3b90 100644 (file)
@@ -130,7 +130,7 @@ This state can be modified from the various hooks.
 
     Return the HTTP scheme for a DoH query.
 
-    :returns: The scheme of the DoH query, for example ''http'' or ''https''
+    :returns: The scheme of the DoH query, for example ``http`` or ``https``
 
   .. method:: DNSQuestion:getServerNameIndication() -> string
 
@@ -179,19 +179,19 @@ This state can be modified from the various hooks.
     .. versionadded:: 1.4.0
 
     Set the HTTP status code and content to immediately send back to the client.
-    For HTTP redirects (3xx), the string supplied in ''body'' should be the URL to redirect to.
-    For 200 responses, the value of the content type header can be specified via the ''contentType'' parameter.
+    For HTTP redirects (3xx), the string supplied in ``body`` should be the URL to redirect to.
+    For 200 responses, the value of the content type header can be specified via the ``contentType`` parameter.
     In order for the response to be sent, the QR bit should be set before returning and the function should return Action.HeaderModify.
 
     :param int status: The HTTP status code to return
     :param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx)
-    :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
+    :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ``application/dns-message``.
 
   .. method:: DNSQuestion:setNegativeAndAdditionalSOA(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum)
 
     .. versionadded:: 1.5.0
 
-    Turn a question into a response, either a NXDOMAIN or a NODATA one based on ''nxd'', setting the QR bit to 1 and adding a SOA record in the additional section.
+    Turn a question into a response, either a NXDOMAIN or a NODATA one based on ``nxd``, setting the QR bit to 1 and adding a SOA record in the additional section.
 
     :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
     :param string zone: The owner name for the SOA record
@@ -204,6 +204,14 @@ This state can be modified from the various hooks.
     :param int expire: The value of the expire field in the SOA record
     :param int minimum: The value of the minimum field in the SOA record
 
+  .. method:: DNSQuestion:setProxyProtocolValues(values)
+
+    .. versionadded:: 1.5.0
+
+    Set the Proxy-Protocol Type-Length values to send to the backend along with this query.
+
+    :param table values: A table of types and values to send, for example: ``{ [0x00] = "foo", [0x42] = "bar" }``. Note that the type must be an integer. Try to avoid these values: 0x01 - 0x05, 0x20 - 0x25, 0x30 as those are predefined in https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt (search for `PP2_TYPE_ALPN`)
+
   .. method:: DNSQuestion:setTag(key, value)
 
     .. versionadded:: 1.2.0
old mode 100644 (file)
new mode 100755 (executable)
index 08c64c2..0f462f9
@@ -22,3 +22,4 @@ These chapters contain extensive information on all functions and object availab
   snmp
   tuning
   kvs
+  logging
index 47e69945824f3b23c9d09f66d3cad84d678ea7ad..8519c8828501eb2094c153be8dc0c8ec805e8c2c 100644 (file)
@@ -3,8 +3,6 @@ Key Value Store functions and objects
 
 These are all the functions, objects and methods related to the CDB and LMDB key value stores.
 
-As of 1.4.0, the CDB and LMDB code is considered experimental.
-
 A lookup into a key value store can be done via the :func:`KeyValueStoreLookupRule` rule or
 the :func:`KeyValueStoreLookupAction` action, using the usual selectors to match the incoming
 queries for which the lookup should be done.
@@ -81,12 +79,18 @@ If the value found in the LMDB database for the key '\\8powerdns\\3com\\0' was '
 
   :param bool wireFormat: Whether to do the lookup in wire format (default) or in plain text
 
-.. function:: KeyValueLookupKeySourceIP() -> KeyValueLookupKey
+.. function:: KeyValueLookupKeySourceIP([v4mask [, v6mask]]) -> KeyValueLookupKey
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.5.0
+    Optional parameters ``v4mask`` and ``v6mask`` added.
+
   Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction` or :func:`KeyValueStoreLookupRule`, will return the source IP of the client in network byte-order.
 
+  :param int v4mask: Mask applied to IPv4 addresses. Default is 32 (the whole address)
+  :param int v6mask: Mask applied to IPv6 addresses. Default is 128 (the whole address)
+
 .. function:: KeyValueLookupKeySuffix([minLabels [, wireFormat]]) -> KeyValueLookupKey
 
   .. versionadded:: 1.4.0
diff --git a/pdns/dnsdistdist/docs/reference/logging.rst b/pdns/dnsdistdist/docs/reference/logging.rst
new file mode 100755 (executable)
index 0000000..f6751d9
--- /dev/null
@@ -0,0 +1,24 @@
+Logging\r
+=======\r
+\r
+There are some functions to create log output.\r
+\r
+.. function:: errlog(line)\r
+\r
+  Writes a error line.\r
+\r
+  :param str line: The line to write.\r
+\r
+\r
+.. function:: warnlog(line)\r
+\r
+  Writes a warning line.\r
+\r
+  :param str line: The line to write.\r
+\r
+\r
+.. function:: infolog(line)\r
+\r
+  Writes an info line.\r
+\r
+  :param str line: The line to write.\r
index 840651a7e2041080f5e34e119bcd6f2fad3bbd4b..f42dd290aa8a5a603d55127d7a14dfc837cacac6 100644 (file)
@@ -10,7 +10,7 @@ NetmaskGroup
    Represents a group of netmasks that can be used to match :class:`ComboAddress`\ es against.
 
   .. method:: NetmaskGroup:addMask(mask)
-              NetmaskGroup:addMask(masks)
+              NetmaskGroup:addMasks(masks)
 
     Add one or more masks to the NMG.
 
index c534787af791e4dccbd8e9fb42cf987d46546151..2d5055c71ffeca28335177b6c16f593cce912a65 100644 (file)
@@ -255,7 +255,7 @@ Rule Generators
   .. deprecated:: 1.2.0
 
   Send at most ``limit`` queries/s for this pool, letting the subsequent rules apply otherwise.
-  This function has been deprecated as of 1.2.0 and removed in 1.3.0, as it is only a convience function for the following syntax::
+  This function has been deprecated as of 1.2.0 and removed in 1.3.0, as it is only a convenience function for the following syntax::
 
     addAction("192.0.2.0/24", QPSPoolAction(15, "myPool")
 
@@ -320,8 +320,8 @@ For Rules related to the incoming query:
 
   Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
 
-  :param Rule rule: A `Rule <#traffic-matching>`_
-  :param Action action: The `Action <#actions>`_ to apply to the matched traffic
+  :param Rule rule: A `Rule (see `Matching Packets (Selectors)`_)
+  :param Action action: The Action (see `Actions`_) to apply to the matched traffic
   :param table options: A table with key: value pairs with options.
 
   Options:
@@ -413,7 +413,7 @@ For Rules related to responses:
 
   Move the last response rule to the first position.
 
-Functions for manipulating Cache Hit Respone Rules:
+Functions for manipulating Cache Hit Response Rules:
 
 .. function:: addCacheHitResponseAction(DNSRule, action [, options])
 
@@ -606,8 +606,6 @@ These ``DNSRule``\ s be one of the following items:
 
   .. versionadded:: 1.4.0
 
-  As of 1.4.0, this code is considered experimental.
-
   Return true if the key returned by 'lookupKey' exists in the key value store referenced by 'kvs'.
   The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
   The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
@@ -828,11 +826,11 @@ These ``DNSRule``\ s be one of the following items:
   :param bool name: The name of the tag that has to be set
   :param bool value: If set, the value the tag has to be set to. Default is unset
 
-.. function:: TCPRule([tcp])
+.. function:: TCPRule(tcp)
 
   Matches question received over TCP if ``tcp`` is true, over UDP otherwise.
 
-  :param bool tcp: Match TCP traffic. Default is true.
+  :param bool tcp: Match TCP traffic if true, UDP traffic if false.
 
 .. function:: TrailingDataRule()
 
@@ -911,7 +909,8 @@ The following actions exist.
 
 .. function:: DelayAction(milliseconds)
 
-  Delay the response by the specified amount of milliseconds (UDP-only).
+  Delay the response by the specified amount of milliseconds (UDP-only). Note that the sending of the query to the backend, if needed,
+  is not delayed. Only the sending of the response to the client will be delayed.
   Subsequent rules are processed after this action.
 
   :param int milliseconds: The amount of milliseconds to delay the response
@@ -919,6 +918,7 @@ The following actions exist.
 .. function:: DelayResponseAction(milliseconds)
 
   Delay the response by the specified amount of milliseconds (UDP-only).
+  The only difference between this action and  :func:`DelayAction` is that they can only be applied on, respectively, responses and queries.
   Subsequent rules are processed after this action.
 
   :param int milliseconds: The amount of milliseconds to delay the response
@@ -1023,8 +1023,6 @@ The following actions exist.
 
   .. versionadded:: 1.4.0
 
-  As of 1.4.0, this code is considered experimental.
-
   Does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey',
   and storing the result if any into the tag named 'destinationTag'.
   The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
@@ -1251,6 +1249,14 @@ The following actions exist.
   * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
   * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
+.. function:: SetProxyProtocolValuesAction(values)
+
+  .. versionadded:: 1.5.0
+
+  Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to ``values``.
+
+  :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
+
 .. function:: SkipCacheAction()
 
   Don't lookup the cache for this query, don't store the answer.
@@ -1321,6 +1327,8 @@ The following actions exist.
     -- select reverse queries for '127.0.0.1' and answer with 'localhost'
     addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000"))
 
+  :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``.
+
   :param string rawAnswer: The raw record data
   :param table options: A table with key: value pairs with options.
 
@@ -1360,7 +1368,7 @@ The following actions exist.
   Send copy of query to ``remote``, keep stats on responses.
   If ``addECS`` is set to true, EDNS Client Subnet information will be added to the query.
 
-  :param string remote: An IP:PORT conbination to send the copied queries to
+  :param string remote: An IP:PORT combination to send the copied queries to
   :param bool addECS: Whether or not to add ECS information. Default false
 
 .. function:: TempFailureCacheTTLAction(ttl)
index daf58e2a99d449e7c2caefd3455255093eaf0270..ada3d95d6ff2278585cd6a488e7893fd041d2880 100644 (file)
@@ -11,7 +11,7 @@ Most likely this path is ``/etc/dnsdist``,  ``/etc`` or ``/usr/local/etc/``, dns
 dnsdist is designed to (re)start almost instantly.
 But to prevent downtime when changing configuration, the console (see :ref:`Console`) can be used for live configuration.
 
-Issueing :func:`delta` on the console will print the changes to the configuration that have been made since startup::
+Issuing :func:`delta` on the console will print the changes to the configuration that have been made since startup::
 
   > delta()
   -- Wed Feb 22 2017 11:31:44 CET
index b5e62f02ab91e2c33cae001f9d4ce5fad977f0ea..7621691bb461baf17694b86d4f73459f0b5666c4 100644 (file)
@@ -1,11 +1,23 @@
 Upgrade Guide
 =============
 
-1.4.0 to 1.5.x
+1.5.x to 1.6.0
 --------------
 
-DOH endpoints specified in the fourth parameter of :func:`addDOHLocal` are now specified as exact URLs instead of path prefixes. The default endpoint also switched from ``/`` to ``/dns-query``.
+The packet cache no longer hashes EDNS Cookies by default, which means that two queries that are identical except for the content of their cookie will now be served the same answer. This only works if the backend is not returning any answer containing EDNS Cookies, otherwise the wrong cookie might be returned to a client. To prevent this, the ``cookieHashing=true`` parameter might be passed to :func:`newPacketCache` so that cookies are hashed, resulting in separate entries in the packet cache.
+
+1.4.x to 1.5.0
+--------------
+
+DOH endpoints specified in the fourth parameter of :func:`addDOHLocal` are now specified as exact paths instead of path prefixes. The default endpoint also switched from ``/`` to ``/dns-query``.
 For example, ``addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', { "/dns-query" })`` will now only accept queries for ``/dns-query`` and no longer for ``/dns-query/foo/bar``.
+This change also impacts the HTTP response rules set via :meth:`DOHFrontend.setResponsesMap`, since queries whose paths are not allowed will be discarded before the rules are evaluated.
+If you want to accept DoH queries on ``/dns-query`` and redirect ``/rfc`` to the DoH RFC, you need to list ``/rfc`` in the list of paths:
+
+  addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', { '/dns-query', '/rfc'})
+  map = { newDOHResponseMapEntry("^/rfc$", 307, "https://www.rfc-editor.org/info/rfc8484") }
+  dohFE = getDOHFrontend(0)
+  dohFE:setResponsesMap(map)
 
 The systemd service-file that is installed no longer uses the ``root`` user to start. It uses the user and group set with the ``--with-service-user`` and ``--with-service-group`` switches during
 configuration, "dnsdist" by default.
@@ -15,6 +27,11 @@ This could mean that dnsdist can no longer read its own configuration, or other
 
 Packages provided on `the PowerDNS Repository <https://repo.powerdns.com>`__ will ``chown`` directories created by them accordingly in the post-installation steps.
 
+This might not be sufficient if the dnsdist configuration refers to files outside of the /etc/dnsdist directory, like DoT or DoH certificates and private keys.
+Many ACME clients used to get and renew certificates, like CertBot, set permissions assuming that services are started as root. For that particular case, making a copy of the necessary files in the /etc/dnsdist directory is advised, using for example CertBot's ``--deploy-hook`` feature to copy the files with the right permissions after a renewal.
+
+The :func:`webserver` configuration now has an optional ACL parameter, that defaults to "127.0.0.1, ::1".
+
 1.3.x to 1.4.0
 --------------
 
index fcddb9d92964c3dfabf95b9502c06099041de9ae..080c95e3b8174f07197f8225e7ac4ba1ea7d8bc0 100644 (file)
 #include "dns.hh"
 #include "dolog.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rules.hh"
 #include "dnsdist-xpf.hh"
 #include "libssl.hh"
 #include "threadname.hh"
-
-using namespace std;
+#include "views.hh"
 
 /* So, how does this work. We use h2o for our http2 and TLS needs.
    If the operator has configured multiple IP addresses to listen on,
@@ -41,11 +41,11 @@ using namespace std;
    dnsdist worker thread which we also launched.
 
    This dnsdist worker thread injects the query into the normal dnsdist flow
-   (as a datagram over a socketpair). The response also goes back over a
-   (different) socketpair, where we pick it up and deliver it back to h2o.
+   (over a pipe). The response also goes back over a (different) pipe,
+   where we pick it up and deliver it back to h2o.
 
    For coordination, we use the h2o socket multiplexer, which is sensitive to our
-   socketpair too.
+   pipe too.
 */
 
 /* h2o notes.
@@ -173,18 +173,36 @@ private:
 // through the bowels of h2o
 struct DOHServerConfig
 {
-  DOHServerConfig(uint32_t idleTimeout): accept_ctx(new DOHAcceptContext)
+  DOHServerConfig(uint32_t idleTimeout, uint32_t internalPipeBufferSize): accept_ctx(new DOHAcceptContext)
   {
-    if(socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohquerypair) < 0) {
-      unixDie("Creating a socket pair for DNS over HTTPS");
+    int fd[2];
+    if (pipe(fd) < 0) {
+      unixDie("Creating a pipe for DNS over HTTPS");
     }
+    dohquerypair[0] = fd[1];
+    dohquerypair[1] = fd[0];
 
-    if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohresponsepair) < 0) {
+    if (pipe(fd) < 0) {
       close(dohquerypair[0]);
       close(dohquerypair[1]);
-      unixDie("Creating a socket pair for DNS over HTTPS");
+      unixDie("Creating a pipe for DNS over HTTPS");
+    }
+
+    dohresponsepair[0] = fd[1];
+    dohresponsepair[1] = fd[0];
+
+    setNonBlocking(dohquerypair[0]);
+    if (internalPipeBufferSize > 0) {
+      setPipeBufferSize(dohquerypair[0], internalPipeBufferSize);
     }
 
+    setNonBlocking(dohresponsepair[0]);
+    if (internalPipeBufferSize > 0) {
+      setPipeBufferSize(dohresponsepair[0], internalPipeBufferSize);
+    }
+
+    setNonBlocking(dohresponsepair[1]);
+
     h2o_config_init(&h2o_config);
     h2o_config.http2.idle_timeout = idleTimeout * 1000;
   }
@@ -209,6 +227,8 @@ struct DOHServerConfig
   int dohresponsepair[2]{-1,-1};
 };
 
+/* This function is called from other threads than the main DoH one,
+   instructing it to send a 502 error to the client */
 void handleDOHTimeout(DOHUnit* oldDU)
 {
   if (oldDU == nullptr) {
@@ -220,9 +240,21 @@ void handleDOHTimeout(DOHUnit* oldDU)
 
   /* increase the ref counter before sending the pointer */
   oldDU->get();
-  if (send(oldDU->rsock, &oldDU, sizeof(oldDU), 0) != sizeof(oldDU)) {
+
+  static_assert(sizeof(oldDU) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+  ssize_t sent = write(oldDU->rsock, &oldDU, sizeof(oldDU));
+  if (sent != sizeof(oldDU)) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+      ++g_stats.dohResponsePipeFull;
+      vinfolog("Unable to pass a DoH timeout to the DoH worker thread because the pipe is full");
+    }
+    else {
+      vinfolog("Unable to pass a DoH timeout to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+    }
+
     oldDU->release();
   }
+
   oldDU->release();
   oldDU = nullptr;
 }
@@ -285,6 +317,7 @@ static const std::string& getReasonFromStatusCode(uint16_t statusCode)
   }
 }
 
+/* Always called from the main DoH thread */
 static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCode, const std::string& response, const std::vector<std::pair<std::string, std::string>>& customResponseHeaders, const std::string& contentType, bool addContentType)
 {
   constexpr int overwrite_if_exists = 1;
@@ -358,6 +391,7 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
 /*
    this function calls 'return -1' to drop a query without sending it
    caller should make sure HTTPS thread hears of that
+   We are not in the main DoH thread but in the DoH 'client' thread.
 */
 static int processDOHQuery(DOHUnit* du)
 {
@@ -365,12 +399,14 @@ static int processDOHQuery(DOHUnit* du)
   ComboAddress remote;
   bool duRefCountIncremented = false;
   try {
-    if(!du->req) {
+    if (!du->req) {
       // we got closed meanwhile. XXX small race condition here
+      // but we should be fine as long as we don't touch du->req
+      // outside of the main DoH thread
       return -1;
     }
     remote = du->remote;
-    DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(du->req->conn->ctx->storage.entries[0].data);
+    DOHServerConfig* dsc = du->dsc;
     auto& holders = dsc->holders;
     ClientState& cs = *dsc->cs;
 
@@ -408,13 +444,7 @@ static int processDOHQuery(DOHUnit* du)
     dq.ednsAdded = du->ednsAdded;
     dq.du = du;
     queryId = ntohs(dh->id);
-#ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
-    h2o_socket_t* sock = du->req->conn->callbacks->get_socket(du->req->conn);
-    const char * sni = h2o_socket_get_ssl_server_name(sock);
-    if (sni != nullptr) {
-      dq.sni = sni;
-    }
-#endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
+    dq.sni = std::move(du->sni);
 
     std::shared_ptr<DownstreamState> ss{nullptr};
     auto result = processQuery(dq, cs, holders, ss);
@@ -430,7 +460,18 @@ static int processDOHQuery(DOHUnit* du)
       }
       /* increase the ref counter before sending the pointer */
       du->get();
-      if (send(du->rsock, &du, sizeof(du), 0) != sizeof(du)) {
+
+      static_assert(sizeof(du) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+      ssize_t sent = write(du->rsock, &du, sizeof(du));
+      if (sent != sizeof(du)) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          ++g_stats.dohResponsePipeFull;
+          vinfolog("Unable to pass a DoH self-answered response to the DoH worker thread because the pipe is full");
+        }
+        else {
+          vinfolog("Unable to pass a DoH self-answered to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+        }
+
         du->release();
       }
       return 0;
@@ -502,6 +543,10 @@ static int processDOHQuery(DOHUnit* du)
 
     dh->id = idOffset;
 
+    if (ss->useProxyProtocol) {
+      addProxyProtocol(dq);
+    }
+
     int fd = pickBackendSocketForSending(ss);
     try {
       /* you can't touch du after this line, because it might already have been freed */
@@ -541,7 +586,7 @@ static int processDOHQuery(DOHUnit* du)
   return 0;
 }
 
-/* called when a HTTP response is about to be sent */
+/* called when a HTTP response is about to be sent, from the main DoH thread */
 static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
 {
   if (req == nullptr) {
@@ -584,56 +629,71 @@ static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h
   h2o_setup_next_ostream(req, slot);
 }
 
-static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
-{
-  h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0);
-  if (pathconf == nullptr) {
-    return pathconf;
-  }
-  h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter));
-  if (filter) {
-    filter->on_setup_ostream = on_response_ready_cb;
-  }
-
-  h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
-  if (handler != nullptr) {
-    handler->on_req = on_req;
-  }
-
-  return pathconf;
-}
-
 /* this is called by h2o when our request dies.
    We use this to signal to the 'du' that this req is no longer alive */
 static void on_generator_dispose(void *_self)
 {
-  DOHUnit** du = (DOHUnit**)_self;
-  if(*du) { // if 0, on_dnsdist cleaned up du already
-//    cout << "du "<<(void*)*du<<" containing req "<<(*du)->req<<" got killed"<<endl;
+  DOHUnit** du = reinterpret_cast<DOHUnit**>(_self);
+  if (*du) { // if 0, on_dnsdist cleaned up du already
+    (*du)->self = nullptr;
     (*du)->req = nullptr;
   }
 }
 
-/* We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread
+/* This executes in the main DoH thread.
+   We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread
    via a pipe */
-static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
+static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote, std::string&& path)
 {
   try {
+    /* we only parse it there as a sanity check, we will parse it again later */
     uint16_t qtype;
     DNSName qname(query.c_str(), query.size(), sizeof(dnsheader), false, &qtype);
 
     auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
+    du->dsc = dsc;
     du->req = req;
     du->dest = local;
     du->remote = remote;
     du->rsock = dsc->dohresponsepair[0];
     du->query = std::move(query);
-    du->qtype = qtype;
+    du->path = std::move(path);
+    /* we are doing quite some copies here, sorry about that,
+       but we can't keep accessing the req object once we are in a different thread
+       because the request might get killed by h2o at pretty much any time */
+    if (req->scheme != nullptr) {
+      du->scheme = std::string(req->scheme->name.base, req->scheme->name.len);
+    }
+    du->host = std::string(req->authority.base, req->authority.len);
+    du->query_at = req->query_at;
+    du->headers.reserve(req->headers.size);
+    for (size_t i = 0; i < req->headers.size; ++i) {
+      du->headers.push_back(std::make_pair(std::string(req->headers.entries[i].name->base, req->headers.entries[i].name->len),
+                                           std::string(req->headers.entries[i].value.base, req->headers.entries[i].value.len)));
+    }
+
+#ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
+    h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
+    const char * sni = h2o_socket_get_ssl_server_name(sock);
+    if (sni != nullptr) {
+      du->sni = sni;
+    }
+#endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
+
     du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
     auto ptr = du.release();
     *(ptr->self) = ptr;
     try  {
-      if(send(dsc->dohquerypair[0], &ptr, sizeof(ptr), 0) != sizeof(ptr)) {
+      static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+      ssize_t sent = write(dsc->dohquerypair[0], &ptr, sizeof(ptr));
+      if (sent != sizeof(ptr)) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          ++g_stats.dohQueryPipeFull;
+          vinfolog("Unable to pass a DoH query to the DoH worker thread because the pipe is full");
+        }
+        else {
+          vinfolog("Unable to pass a DoH query to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+        }
         ptr->release();
         ptr = nullptr;
         h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
@@ -649,8 +709,57 @@ static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_re
   }
 }
 
+/* can only be called from the main DoH thread */
+static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerName, pdns_string_view& value)
+{
+  bool found = false;
+  /* early versions of boost::string_ref didn't have the ability to compare to string */
+  pdns_string_view headerNameView(headerName);
+
+  for (size_t i = 0; i < req->headers.size; ++i) {
+    if (pdns_string_view(req->headers.entries[i].name->base, req->headers.entries[i].name->len) == headerNameView) {
+      value = pdns_string_view(req->headers.entries[i].value.base, req->headers.entries[i].value.len);
+      /* don't stop there, we might have more than one header with the same name, and we want the last one */
+      found = true;
+    }
+  }
+
+  return found;
+}
+
+/* can only be called from the main DoH thread */
+static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote)
+{
+  static const std::string headerName = "x-forwarded-for";
+  pdns_string_view value;
+
+  if (getHTTPHeaderValue(req, headerName, value)) {
+    try {
+      auto pos = value.rfind(',');
+      if (pos != pdns_string_view::npos) {
+        ++pos;
+        for (; pos < value.size() && value[pos] == ' '; ++pos)
+        {
+        }
+
+        if (pos < value.size()) {
+          value = value.substr(pos);
+        }
+      }
+      auto newRemote = ComboAddress(std::string(value));
+      remote = newRemote;
+    }
+    catch (const std::exception& e) {
+      vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what());
+    }
+    catch (const PDNSException& e) {
+      vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason);
+    }
+  }
+}
+
 /*
-  A query has been parsed by h2o.
+  A query has been parsed by h2o, this executes in the main DoH thread.
   For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
   For POST, the payload is the payload.
  */
@@ -664,10 +773,21 @@ try
   h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
   ComboAddress remote;
   ComboAddress local;
-  h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
+
+  if (h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote)) == 0) {
+    /* getpeername failed, likely because the connection has already been closed,
+       but anyway that means we can't get the remote address, which could allow an ACL bypass */
+    h2o_send_error_500(req, getReasonFromStatusCode(500).c_str(), "Internal Server Error - Unable to get remote address", 0);
+    return 0;
+  }
+
   h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&local));
   DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
 
+  if (dsc->df->d_trustForwardedForHeader) {
+    processForwardedForHeader(req, remote);
+  }
+
   auto& holders = dsc->holders;
   if (!holders.acl->match(remote)) {
     ++g_stats.aclDrops;
@@ -724,7 +844,7 @@ try
        at least s_maxPacketCacheEntrySize bytes to be able to fill the answer from the packet cache */
     query.reserve(std::max(req->entity.len + 512, s_maxPacketCacheEntrySize));
     query.assign(req->entity.base, req->entity.len);
-    doh_dispatch_query(dsc, self, req, std::move(query), local, remote);
+    doh_dispatch_query(dsc, self, req, std::move(query), local, remote, std::move(path));
   }
   else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
     auto pos = path.find("?dns=");
@@ -763,7 +883,7 @@ try
         else
           ++dsc->df->d_http1Stats.d_nbQueries;
 
-        doh_dispatch_query(dsc, self, req, std::move(decoded), local, remote);
+        doh_dispatch_query(dsc, self, req, std::move(decoded), local, remote, std::move(path));
       }
     }
     else
@@ -780,7 +900,7 @@ try
   }
   return 0;
 }
-catch(const exception& e)
+ catch(const std::exception& e)
 {
   errlog("DOH Handler function failed with error %s", e.what());
   return 0;
@@ -797,10 +917,9 @@ bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
     return false;
   }
 
-  for (size_t i = 0; i < dq->du->req->headers.size; ++i) {
-    if(std::string(dq->du->req->headers.entries[i].name->base, dq->du->req->headers.entries[i].name->len) == d_header &&
-       d_regex.match(std::string(dq->du->req->headers.entries[i].value.base, dq->du->req->headers.entries[i].value.len))) {
-      return true;
+  for (const auto& header : dq->du->headers) {
+    if (header.first == d_header) {
+      return d_regex.match(header.second);
     }
   }
   return false;
@@ -819,15 +938,15 @@ HTTPPathRule::HTTPPathRule(const std::string& path)
 
 bool HTTPPathRule::matches(const DNSQuestion* dq) const
 {
-  if(!dq->du) {
+  if (!dq->du) {
     return false;
   }
 
-  if(dq->du->req->query_at == SIZE_MAX) {
-    return dq->du->req->path.base == d_path;
+  if (dq->du->query_at == SIZE_MAX) {
+    return dq->du->path == d_path;
   }
   else {
-    return d_path.compare(0, d_path.size(), dq->du->req->path.base, dq->du->req->query_at) == 0;
+    return d_path.compare(0, d_path.size(), dq->du->path, 0, dq->du->query_at) == 0;
   }
 }
 
@@ -857,11 +976,10 @@ string HTTPPathRegexRule::toString() const
 std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
 {
   std::unordered_map<std::string, std::string> results;
-  results.reserve(req->headers.size);
+  results.reserve(headers.size());
 
-  for (size_t i = 0; i < req->headers.size; ++i) {
-    results.insert({std::string(req->headers.entries[i].name->base, req->headers.entries[i].name->len),
-                    std::string(req->headers.entries[i].value.base, req->headers.entries[i].value.len)});
+  for (const auto& header : headers) {
+    results.insert(header);
   }
 
   return results;
@@ -869,35 +987,31 @@ std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
 
 std::string DOHUnit::getHTTPPath() const
 {
-  if (req->query_at == SIZE_MAX) {
-    return std::string(req->path.base, req->path.len);
+  if (query_at == SIZE_MAX) {
+    return path;
   }
   else {
-    return std::string(req->path.base, req->query_at);
+    return std::string(path, 0, query_at);
   }
 }
 
 std::string DOHUnit::getHTTPHost() const
 {
-  return std::string(req->authority.base, req->authority.len);
+  return host;
 }
 
 std::string DOHUnit::getHTTPScheme() const
 {
-  if (req->scheme == nullptr) {
-    return std::string();
-  }
-
-  return std::string(req->scheme->name.base, req->scheme->name.len);
+  return scheme;
 }
 
 std::string DOHUnit::getHTTPQueryString() const
 {
-  if (req->query_at == SIZE_MAX) {
+  if (query_at == SIZE_MAX) {
     return std::string();
   }
   else {
-    return std::string(req->path.base + req->query_at, req->path.len - req->query_at);
+    return path.substr(query_at);
   }
 }
 
@@ -911,14 +1025,14 @@ void DOHUnit::setHTTPResponse(uint16_t statusCode, const std::string& body_, con
 /* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
    In order not to blockfor long, doh_handler() called doh_dispatch_query() which allocated
    a DOHUnit object and passed it to us */
-static void dnsdistclient(int qsock, int rsock)
+static void dnsdistclient(int qsock)
 {
   setThreadName("dnsdist/doh-cli");
 
   for(;;) {
     try {
       DOHUnit* du = nullptr;
-      ssize_t got = recv(qsock, &du, sizeof(du), 0);
+      ssize_t got = read(qsock, &du, sizeof(du));
       if (got < 0) {
         warnlog("Error receiving internal DoH query: %s", strerror(errno));
         continue;
@@ -927,11 +1041,22 @@ static void dnsdistclient(int qsock, int rsock)
         continue;
       }
 
+      /* we are not in the main DoH thread anymore, so there is a real risk of
+         a race condition where h2o kills the query while we are processing it,
+         so we can't touch the content of du->req until we are back into the
+         main DoH thread */
+      if (!du->req) {
+        // it got killed in flight already
+        du->self = nullptr;
+        du->release();
+        continue;
+      }
+
       // if there was no EDNS, we add it with a large buffer size
       // so we can use UDP to talk to the backend.
       auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str()));
 
-      if(!dh->arcount) {
+      if (!dh->arcount) {
         std::string res;
         generateOptRR(std::string(), res, 4096, 0, false);
 
@@ -944,12 +1069,24 @@ static void dnsdistclient(int qsock, int rsock)
         // we leave existing EDNS in place
       }
 
-      if(processDOHQuery(du) < 0) {
+      if (processDOHQuery(du) < 0) {
         du->status_code = 500;
         /* increase the ref count before sending the pointer */
         du->get();
-        if(send(du->rsock, &du, sizeof(du), 0) != sizeof(du)) {
-          du->release();     // XXX but now what - will h2o time this out for us?
+
+        static_assert(sizeof(du) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+        ssize_t sent = write(du->rsock, &du, sizeof(du));
+        if (sent != sizeof(du)) {
+          if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            ++g_stats.dohResponsePipeFull;
+            vinfolog("Unable to pass a DoH internal error to the DoH worker thread because the pipe is full");
+          }
+          else {
+            vinfolog("Unable to pass a DoH internal error to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+          }
+
+          // XXX but now what - will h2o time this out for us?
+          du->release();
         }
       }
       du->release();
@@ -963,7 +1100,7 @@ static void dnsdistclient(int qsock, int rsock)
   }
 }
 
-/* called if h2o finds that dnsdist gave us an answer by writing into
+/* Called in the main DoH thread if h2o finds that dnsdist gave us an answer by writing into
    the dohresponsepair[0] side of the pipe so from:
    - handleDOHTimeout() when we did not get a response fast enough (called
      either from the health check thread (active) or from the frontend ones (reused))
@@ -974,7 +1111,7 @@ static void on_dnsdist(h2o_socket_t *listener, const char *err)
 {
   DOHUnit *du = nullptr;
   DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
-  ssize_t got = recv(dsc->dohresponsepair[1], &du, sizeof(du), 0);
+  ssize_t got = read(dsc->dohresponsepair[1], &du, sizeof(du));
 
   if (got < 0) {
     warnlog("Error reading a DOH internal response: %s", strerror(errno));
@@ -984,13 +1121,18 @@ static void on_dnsdist(h2o_socket_t *listener, const char *err)
     return;
   }
 
-  if(!du->req) { // it got killed in flight
-//    cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
+  if (!du->req) { // it got killed in flight
+    du->self = nullptr;
     du->release();
     return;
   }
 
-  *du->self = nullptr; // so we don't clean up again in on_generator_dispose
+  if (du->self) {
+    // we are back in the h2o main thread now, so we don't risk
+    // a race (h2o killing the query) when accessing du->req anymore
+    *du->self = nullptr; // so we don't clean up again in on_generator_dispose
+    du->self = nullptr;
+  }
 
   handleResponse(*dsc->df, du->req, du->status_code, du->response, dsc->df->d_customResponseHeaders, du->contentType, true);
 
@@ -1011,8 +1153,8 @@ static void on_accept(h2o_socket_t *listener, const char *err)
     return;
   }
 
-  ComboAddress remote;
-  h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
+  // ComboAddress remote;
+  // h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
   //  cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
 
   sock->data = dsc;
@@ -1093,6 +1235,7 @@ static void setupTLSContext(DOHAcceptContext& acceptCtx,
 
   h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
 
+  acceptCtx.d_ticketsKeyRotationDelay = tlsConfig.d_ticketsKeyRotationDelay;
   if (tlsConfig.d_ticketKeyFile.empty()) {
     acceptCtx.handleTicketsKeyRotation();
   }
@@ -1177,7 +1320,7 @@ void DOHFrontend::setup()
 {
   registerOpenSSLUser();
 
-  d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);
+  d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout, d_internalPipeBufferSize);
 
   if  (!d_tlsConfig.d_certKeyPairs.empty()) {
     try {
@@ -1191,6 +1334,25 @@ void DOHFrontend::setup()
   }
 }
 
+static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
+{
+  h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0);
+  if (pathconf == nullptr) {
+    return pathconf;
+  }
+  h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter));
+  if (filter) {
+    filter->on_setup_ostream = on_response_ready_cb;
+  }
+
+  h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
+  if (handler != nullptr) {
+    handler->on_req = on_req;
+  }
+
+  return pathconf;
+}
+
 // this is the entrypoint from dnsdist.cc
 void dohThread(ClientState* cs)
 try
@@ -1202,7 +1364,7 @@ try
   dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
 
 
-  std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1], dsc->dohresponsepair[0]);
+  std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1]);
   dnsdistThread.detach(); // gets us better error reporting
 
   setThreadName("dnsdist/doh");
index 064c1b46d642ca00539158f4d39ab55ebdfaebc4..26647cddc56d67496aa823d04cdf7b481347e426 100644 (file)
@@ -7,6 +7,7 @@
 #include <atomic>
 #include <fstream>
 #include <cstring>
+#include <mutex>
 #include <pthread.h>
 
 #include <openssl/conf.h>
 
 #if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL)
 /* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */
-static pthread_mutex_t *openssllocks{nullptr};
+
+#include "lock.hh"
+static std::vector<std::mutex> openssllocks;
 
 extern "C" {
 static void openssl_pthreads_locking_callback(int mode, int type, const char *file, int line)
 {
   if (mode & CRYPTO_LOCK) {
-    pthread_mutex_lock(&(openssllocks[type]));
+    openssllocks.at(type).lock();
 
   } else {
-    pthread_mutex_unlock(&(openssllocks[type]));
+    openssllocks.at(type).unlock();
   }
 }
 
@@ -42,24 +45,15 @@ static unsigned long openssl_pthreads_id_callback()
 
 static void openssl_thread_setup()
 {
-  openssllocks = (pthread_mutex_t*)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
-
-  for (int i = 0; i < CRYPTO_num_locks(); i++)
-    pthread_mutex_init(&(openssllocks[i]), NULL);
-
-  CRYPTO_set_id_callback(openssl_pthreads_id_callback);
-  CRYPTO_set_locking_callback(openssl_pthreads_locking_callback);
+  openssllocks = std::vector<std::mutex>(CRYPTO_num_locks());
+  CRYPTO_set_id_callback(&openssl_pthreads_id_callback);
+  CRYPTO_set_locking_callback(&openssl_pthreads_locking_callback);
 }
 
 static void openssl_thread_cleanup()
 {
-  CRYPTO_set_locking_callback(NULL);
-
-  for (int i=0; i<CRYPTO_num_locks(); i++) {
-    pthread_mutex_destroy(&(openssllocks[i]));
-  }
-
-  OPENSSL_free(openssllocks);
+  CRYPTO_set_locking_callback(nullptr);
+  openssllocks.clear();
 }
 
 #endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL) */
@@ -487,13 +481,11 @@ bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx
 
 OpenSSLTLSTicketKeysRing::OpenSSLTLSTicketKeysRing(size_t capacity)
 {
-  pthread_rwlock_init(&d_lock, nullptr);
   d_ticketKeys.set_capacity(capacity);
 }
 
 OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing()
 {
-  pthread_rwlock_destroy(&d_lock);
 }
 
 void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey)
@@ -669,6 +661,9 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
 
   if (config.d_preferServerCiphers) {
     sslOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+#ifdef SSL_OP_PRIORITIZE_CHACHA
+    sslOptions |= SSL_OP_PRIORITIZE_CHACHA;
+#endif /* SSL_OP_PRIORITIZE_CHACHA */
   }
 
   SSL_CTX_set_options(ctx.get(), sslOptions);
diff --git a/pdns/dnsdistdist/packetcache.hh b/pdns/dnsdistdist/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/dnsdistdist/proxy-protocol.cc b/pdns/dnsdistdist/proxy-protocol.cc
new file mode 120000 (symlink)
index 0000000..ae6a943
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/proxy-protocol.hh b/pdns/dnsdistdist/proxy-protocol.hh
new file mode 120000 (symlink)
index 0000000..bc45ee8
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.hh
\ No newline at end of file
index 71ca39f516c0e5682eddf0a0908130601c80c9d6..b4ea4c96779b7be966dc7ef5e60b7831ec3424a1 100644 (file)
@@ -399,14 +399,14 @@ private:
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 
-void safe_memory_lock(void* data, size_t size)
+static void safe_memory_lock(void* data, size_t size)
 {
 #ifdef HAVE_LIBSODIUM
   sodium_mlock(data, size);
 #endif
 }
 
-void safe_memory_release(void* data, size_t size)
+static void safe_memory_release(void* data, size_t size)
 {
 #ifdef HAVE_LIBSODIUM
   sodium_munlock(data, size);
@@ -791,8 +791,6 @@ public:
       throw std::runtime_error("Error setting up TLS cipher preferences to '" + fe.d_tlsConfig.d_ciphers + "' (" + gnutls_strerror(rc) + ") on " + fe.d_addr.toStringWithPort());
     }
 
-    pthread_rwlock_init(&d_lock, nullptr);
-
     try {
       if (fe.d_tlsConfig.d_ticketKeyFile.empty()) {
         handleTicketsKeyRotation(time(nullptr));
@@ -802,15 +800,12 @@ public:
       }
     }
     catch(const std::runtime_error& e) {
-      pthread_rwlock_destroy(&d_lock);
       throw std::runtime_error("Error generating tickets key for TLS context on " + fe.d_addr.toStringWithPort() + ": " + e.what());
     }
   }
 
   virtual ~GnuTLSIOCtx() override
   {
-    pthread_rwlock_destroy(&d_lock);
-
     d_creds.reset();
 
     if (d_priorityCache) {
@@ -876,7 +871,7 @@ private:
   std::unique_ptr<gnutls_certificate_credentials_st, void(*)(gnutls_certificate_credentials_t)> d_creds;
   gnutls_priority_t d_priorityCache{nullptr};
   std::shared_ptr<GnuTLSTicketsKey> d_ticketsKey{nullptr};
-  pthread_rwlock_t d_lock;
+  ReadWriteLock d_lock;
   bool d_enableTickets{true};
 };
 
index 3d1c1b00dfac00e3d323a84dcc4f1ef23305ca35..e376cb93309cba0e2dc624d0ac3ebc5183deb9e5 100644 (file)
@@ -15,13 +15,13 @@ BOOST_AUTO_TEST_CASE(test_object_pipe) {
 
   int i;
   for(int n=0; n < 100; ++n) {
-    bool res=op.read(&i);
-    BOOST_CHECK_EQUAL(res, true);
+    int res=op.readTimeout(&i, -1);
+    BOOST_CHECK_EQUAL(res, 1);
     BOOST_CHECK_EQUAL(n, i);
   }
 
   op.close();
-  BOOST_CHECK_EQUAL(op.read(&i), false);
+  BOOST_CHECK_EQUAL(op.readTimeout(&i, 1), 0);
 
 };
 
index 757d14a349f02b744f2d17900d274e0ee0eb9f17..9322f61dad69a0a312fcb3bb32463b001fd90614 100644 (file)
@@ -6,11 +6,15 @@
 
 #include "dnsdist-kvs.hh"
 
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+static const ComboAddress v4ToMask("203.0.113.255");
+static const ComboAddress v6ToMask("2001:db8:ff:ff:ff:ff:ff:ff");
+
 static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress& lc, const ComboAddress& rem, const DNSQuestion& dq, const DNSName& plaintextDomain)
 {
   /* source IP */
   {
-    auto lookupKey = make_unique<KeyValueLookupKeySourceIP>();
+    auto lookupKey = make_unique<KeyValueLookupKeySourceIP>(32, 128);
     std::string value;
     /* local address is not in the db, remote is */
     BOOST_CHECK_EQUAL(kvs->getValue(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr)), value), false);
@@ -26,6 +30,27 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
     }
   }
 
+  /* masked source IP */
+  {
+    auto lookupKey = make_unique<KeyValueLookupKeySourceIP>(25, 65);
+
+    auto keys = lookupKey->getKeys(v4ToMask);
+    BOOST_CHECK_EQUAL(keys.size(), 1U);
+    for (const auto& key : keys) {
+      std::string value;
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+      BOOST_CHECK_EQUAL(value, "this is the value for the masked v4 addr");
+    }
+
+    keys = lookupKey->getKeys(v6ToMask);
+    BOOST_CHECK_EQUAL(keys.size(), 1U);
+    for (const auto& key : keys) {
+      std::string value;
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+      BOOST_CHECK_EQUAL(value, "this is the value for the masked v6 addr");
+    }
+  }
+
   const DNSName subdomain = DNSName("sub") + *dq.qname;
   const DNSName notPDNS("not-powerdns.com.");
 
@@ -195,6 +220,7 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
     BOOST_CHECK_EQUAL(value, "this is the value for the qname");
   }
 }
+#endif // defined(HAVE_LMDB) || defined(HAVE_CDB)
 
 BOOST_AUTO_TEST_SUITE(dnsdistkvs_cc)
 
@@ -219,6 +245,10 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
   gettime(&expiredTime);
 
   DNSQuestion dq(&qname, qtype, qclass, qname.wirelength(), &lc, &rem, &dh, bufferSize, queryLen, isTcp, &queryRealTime);
+  ComboAddress v4Masked(v4ToMask);
+  ComboAddress v6Masked(v6ToMask);
+  v4Masked.truncate(25);
+  v6Masked.truncate(65);
 
   const string dbPath("/tmp/test_lmdb.XXXXXX");
   {
@@ -226,6 +256,8 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
     auto transaction = env.getRWTransaction();
     auto dbi = transaction->openDB("db-name", MDB_CREATE);
     transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the remote addr"));
+    transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&v4Masked.sin4.sin_addr.s_addr), sizeof(v4Masked.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the masked v4 addr"));
+    transaction->put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&v6Masked.sin6.sin6_addr.s6_addr), sizeof(v6Masked.sin6.sin6_addr.s6_addr))), MDBInVal("this is the value for the masked v6 addr"));
     transaction->put(dbi, MDBInVal(qname.toDNSStringLC()), MDBInVal("this is the value for the qname"));
     transaction->put(dbi, MDBInVal(plaintextDomain.toStringRootDot()), MDBInVal("this is the value for the plaintext domain"));
     transaction->commit();
@@ -271,6 +303,10 @@ BOOST_AUTO_TEST_CASE(test_CDB) {
   gettime(&expiredTime);
 
   DNSQuestion dq(&qname, qtype, qclass, qname.wirelength(), &lc, &rem, &dh, bufferSize, queryLen, isTcp, &queryRealTime);
+  ComboAddress v4Masked(v4ToMask);
+  ComboAddress v6Masked(v6ToMask);
+  v4Masked.truncate(25);
+  v6Masked.truncate(65);
 
   char db[] = "/tmp/test_cdb.XXXXXX";
   {
@@ -278,6 +314,8 @@ BOOST_AUTO_TEST_CASE(test_CDB) {
     BOOST_REQUIRE(fd >= 0);
     CDBWriter writer(fd);
     BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr)), "this is the value for the remote addr"));
+    BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&v4Masked.sin4.sin_addr.s_addr), sizeof(v4Masked.sin4.sin_addr.s_addr)), "this is the value for the masked v4 addr"));
+    BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&v6Masked.sin6.sin6_addr.s6_addr), sizeof(v6Masked.sin6.sin6_addr.s6_addr)), "this is the value for the masked v6 addr"));
     BOOST_REQUIRE(writer.addEntry(qname.toDNSStringLC(), "this is the value for the qname"));
     BOOST_REQUIRE(writer.addEntry(plaintextDomain.toStringRootDot(), "this is the value for the plaintext domain"));
     writer.close();
index 99afe3acd0754c8cf4578ccf691bd41f4faf663c..4e21675deff70d47a70c880468544a1a74b8ca15 100644 (file)
@@ -27,6 +27,9 @@ GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
 GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
 #endif /* BENCH_POLICIES */
 
+GlobalStateHolder<pools_t> g_pools;
+std::vector<std::unique_ptr<ClientState>> g_frontends;
+
 /* add stub implementations, we don't want to include the corresponding object files
    and their dependencies */
 
@@ -76,6 +79,12 @@ bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& rea
   return false;
 }
 
+void setLuaNoSideEffect()
+{
+}
+
+string g_outputBuffer;
+
 static DNSQuestion getDQ(const DNSName* providedName = nullptr)
 {
   static const DNSName qname("powerdns.com.");
@@ -122,7 +131,7 @@ static void benchPolicy(const ServerPolicy& pol)
   for (size_t idx = 0; idx < 1000; idx++) {
   for (const auto& name : names) {
     auto dq = getDQ(&name);
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
   }
   }
   cerr<<pol.name<<" took "<<std::to_string(sw.udiff())<<" us for "<<names.size()<<endl;
@@ -149,38 +158,91 @@ BOOST_AUTO_TEST_CASE(test_firstAvailable) {
   servers.push_back({ 1, std::make_shared<DownstreamState>(ComboAddress("192.0.2.1:53")) });
 
   /* servers start as 'down' */
-  auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+  auto server = pol.getSelectedBackend(servers, dq);
   BOOST_CHECK(server == nullptr);
 
   /* mark the server as 'up' */
   servers.at(0).second->setUp();
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_CHECK(server != nullptr);
 
   /* add a second server, we should still get the first one */
   servers.push_back({ 2, std::make_shared<DownstreamState>(ComboAddress("192.0.2.2:53")) });
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_REQUIRE(server != nullptr);
   BOOST_CHECK(server == servers.at(0).second);
 
   /* mark the first server as 'down', second as 'up' */
   servers.at(0).second->setDown();
   servers.at(1).second->setUp();
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_REQUIRE(server != nullptr);
   BOOST_CHECK(server == servers.at(1).second);
 
-  std::vector<DNSName> names;
-  names.reserve(1000);
+  benchPolicy(pol);
+}
+
+BOOST_AUTO_TEST_CASE(test_roundRobin) {
+  auto dq = getDQ();
+
+  ServerPolicy pol{"roundrobin", roundrobin, false};
+  ServerPolicy::NumberedServerVector servers;
+
+  /* selecting a server on an empty server list */
+  g_roundrobinFailOnNoServer = false;
+  auto server = pol.getSelectedBackend(servers, dq);
+  BOOST_CHECK(server == nullptr);
+
+  servers.push_back({ 1, std::make_shared<DownstreamState>(ComboAddress("192.0.2.1:53")) });
+
+  /* servers start as 'down' but the RR policy returns a server unless g_roundrobinFailOnNoServer is set */
+  g_roundrobinFailOnNoServer = true;
+  server = pol.getSelectedBackend(servers, dq);
+  BOOST_CHECK(server == nullptr);
+  g_roundrobinFailOnNoServer = false;
+  server = pol.getSelectedBackend(servers, dq);
+  BOOST_CHECK(server != nullptr);
+
+  /* mark the server as 'up' */
+  servers.at(0).second->setUp();
+  server = pol.getSelectedBackend(servers, dq);
+  BOOST_CHECK(server != nullptr);
+
+  /* add a second server, we should get the first one then the second one */
+  servers.push_back({ 2, std::make_shared<DownstreamState>(ComboAddress("192.0.2.2:53")) });
+  servers.at(1).second->setUp();
+  server = pol.getSelectedBackend(servers, dq);
+  BOOST_REQUIRE(server != nullptr);
+  BOOST_CHECK(server == servers.at(0).second);
+  server = pol.getSelectedBackend(servers, dq);
+  BOOST_REQUIRE(server != nullptr);
+  BOOST_CHECK(server == servers.at(1).second);
+
+  /* mark the first server as 'down', second as 'up' */
+  servers.at(0).second->setDown();
+  servers.at(1).second->setUp();
+  server = pol.getSelectedBackend(servers, dq);
+  BOOST_REQUIRE(server != nullptr);
+  BOOST_CHECK(server == servers.at(1).second);
+
+  std::map<std::shared_ptr<DownstreamState>, uint64_t> serversMap;
+  /* mark all servers 'up' */
+  for (auto& s : servers) {
+    s.second->setUp();
+    serversMap[s.second] = 0;
+  }
+
   for (size_t idx = 0; idx < 1000; idx++) {
-    names.push_back(DNSName("powerdns-" + std::to_string(idx) + ".com."));
+    server = pol.getSelectedBackend(servers, dq);
+    BOOST_REQUIRE(serversMap.count(server) == 1);
+    ++serversMap[server];
   }
-  std::map<std::shared_ptr<DownstreamState>, uint64_t> serversMap;
-  for (size_t idx = 1; idx <= 10; idx++) {
-    servers.push_back({ idx, std::make_shared<DownstreamState>(ComboAddress("192.0.2." + std::to_string(idx) + ":53")) });
-    serversMap[servers.at(idx - 1).second] = 0;
-    servers.at(idx - 1).second->setUp();
+  uint64_t total = 0;
+  for (const auto& entry : serversMap) {
+    BOOST_CHECK_EQUAL(entry.second, 1000 / servers.size());
+    total += entry.second;
   }
+  BOOST_CHECK_EQUAL(total, 1000U);
 
   benchPolicy(pol);
 }
@@ -193,24 +255,24 @@ BOOST_AUTO_TEST_CASE(test_leastOutstanding) {
   servers.push_back({ 1, std::make_shared<DownstreamState>(ComboAddress("192.0.2.1:53")) });
 
   /* servers start as 'down' */
-  auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+  auto server = pol.getSelectedBackend(servers, dq);
   BOOST_CHECK(server == nullptr);
 
   /* mark the server as 'up' */
   servers.at(0).second->setUp();
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_CHECK(server != nullptr);
 
   /* add a second server, we should still get the first one */
   servers.push_back({ 2, std::make_shared<DownstreamState>(ComboAddress("192.0.2.2:53")) });
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_REQUIRE(server != nullptr);
   BOOST_CHECK(server == servers.at(0).second);
 
   /* mark the first server as 'down', second as 'up' */
   servers.at(0).second->setDown();
   servers.at(1).second->setUp();
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_REQUIRE(server != nullptr);
   BOOST_CHECK(server == servers.at(1).second);
 
@@ -218,7 +280,7 @@ BOOST_AUTO_TEST_CASE(test_leastOutstanding) {
   servers.at(0).second->setUp();
   servers.at(0).second->outstanding = 42;
   servers.at(1).second->setUp();
-  server = getSelectedBackendFromPolicy(pol, servers, dq);
+  server = pol.getSelectedBackend(servers, dq);
   BOOST_REQUIRE(server != nullptr);
   BOOST_CHECK(server == servers.at(1).second);
 
@@ -240,18 +302,18 @@ BOOST_AUTO_TEST_CASE(test_wrandom) {
   benchPolicy(pol);
 
   for (size_t idx = 0; idx < 1000; idx++) {
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     BOOST_REQUIRE(serversMap.count(server) == 1);
     ++serversMap[server];
   }
   uint64_t total = 0;
   for (const auto& entry : serversMap) {
-    BOOST_CHECK_GT(entry.second, 0);
+    BOOST_CHECK_GT(entry.second, 0U);
     BOOST_CHECK_GT(entry.second, (1000 / servers.size() / 2));
     BOOST_CHECK_LT(entry.second, (1000 / servers.size() * 2));
     total += entry.second;
   }
-  BOOST_CHECK_EQUAL(total, 1000);
+  BOOST_CHECK_EQUAL(total, 1000U);
 
   /* reset */
   for (auto& entry : serversMap) {
@@ -268,7 +330,7 @@ BOOST_AUTO_TEST_CASE(test_wrandom) {
   servers.at(servers.size()-1).second->weight = 100;
 
   for (size_t idx = 0; idx < 1000; idx++) {
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     BOOST_REQUIRE(serversMap.count(server) == 1);
     ++serversMap[server];
   }
@@ -279,7 +341,7 @@ BOOST_AUTO_TEST_CASE(test_wrandom) {
     total += entry.second;
     totalW += entry.first->weight;
   }
-  BOOST_CHECK_EQUAL(total, 1000);
+  BOOST_CHECK_EQUAL(total, 1000U);
   auto last = servers.at(servers.size()-1).second;
   const auto got = serversMap[last];
   float expected = (1000 * 1.0 * last->weight) / totalW;
@@ -307,14 +369,14 @@ BOOST_AUTO_TEST_CASE(test_whashed) {
 
   for (const auto& name : names) {
     auto dq = getDQ(&name);
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     BOOST_REQUIRE(serversMap.count(server) == 1);
     ++serversMap[server];
   }
 
   uint64_t total = 0;
   for (const auto& entry : serversMap) {
-    BOOST_CHECK_GT(entry.second, 0);
+    BOOST_CHECK_GT(entry.second, 0U);
     BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
     BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
     total += entry.second;
@@ -330,9 +392,9 @@ BOOST_AUTO_TEST_CASE(test_whashed) {
   /* request 1000 times the same name, we should go to the same server every time */
   {
     auto dq = getDQ(&names.at(0));
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     for (size_t idx = 0; idx < 1000; idx++) {
-      BOOST_CHECK(getSelectedBackendFromPolicy(pol, servers, dq) == server);
+      BOOST_CHECK(pol.getSelectedBackend(servers, dq) == server);
     }
   }
 
@@ -346,7 +408,7 @@ BOOST_AUTO_TEST_CASE(test_whashed) {
 
   for (const auto& name : names) {
     auto dq = getDQ(&name);
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     BOOST_REQUIRE(serversMap.count(server) == 1);
     ++serversMap[server];
   }
@@ -392,14 +454,14 @@ BOOST_AUTO_TEST_CASE(test_chashed) {
 
   for (const auto& name : names) {
     auto dq = getDQ(&name);
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     BOOST_REQUIRE(serversMap.count(server) == 1);
     ++serversMap[server];
   }
 
   uint64_t total = 0;
   for (const auto& entry : serversMap) {
-    BOOST_CHECK_GT(entry.second, 0);
+    BOOST_CHECK_GT(entry.second, 0U);
     BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
     BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
     total += entry.second;
@@ -415,9 +477,9 @@ BOOST_AUTO_TEST_CASE(test_chashed) {
   /* request 1000 times the same name, we should go to the same server every time */
   {
     auto dq = getDQ(&names.at(0));
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     for (size_t idx = 0; idx < 1000; idx++) {
-      BOOST_CHECK(getSelectedBackendFromPolicy(pol, servers, dq) == server);
+      BOOST_CHECK(pol.getSelectedBackend(servers, dq) == server);
     }
   }
 
@@ -431,7 +493,7 @@ BOOST_AUTO_TEST_CASE(test_chashed) {
 
   for (const auto& name : names) {
     auto dq = getDQ(&name);
-    auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+    auto server = pol.getSelectedBackend(servers, dq);
     BOOST_REQUIRE(serversMap.count(server) == 1);
     ++serversMap[server];
   }
@@ -483,18 +545,18 @@ BOOST_AUTO_TEST_CASE(test_lua) {
       serversMap[servers.at(idx - 1).second] = 0;
       servers.at(idx - 1).second->setUp();
     }
-    BOOST_REQUIRE_EQUAL(servers.size(), 10);
+    BOOST_REQUIRE_EQUAL(servers.size(), 10U);
 
     for (const auto& name : names) {
       auto dq = getDQ(&name);
-      auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+      auto server = pol.getSelectedBackend(servers, dq);
       BOOST_REQUIRE(serversMap.count(server) == 1);
       ++serversMap[server];
     }
 
     uint64_t total = 0;
     for (const auto& entry : serversMap) {
-      BOOST_CHECK_GT(entry.second, 0);
+      BOOST_CHECK_GT(entry.second, 0U);
       BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
       BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
       total += entry.second;
@@ -543,18 +605,18 @@ BOOST_AUTO_TEST_CASE(test_lua_ffi_rr) {
       serversMap[servers.at(idx - 1).second] = 0;
       servers.at(idx - 1).second->setUp();
     }
-    BOOST_REQUIRE_EQUAL(servers.size(), 10);
+    BOOST_REQUIRE_EQUAL(servers.size(), 10U);
 
     for (const auto& name : names) {
       auto dq = getDQ(&name);
-      auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+      auto server = pol.getSelectedBackend(servers, dq);
       BOOST_REQUIRE(serversMap.count(server) == 1);
       ++serversMap[server];
     }
 
     uint64_t total = 0;
     for (const auto& entry : serversMap) {
-      BOOST_CHECK_GT(entry.second, 0);
+      BOOST_CHECK_GT(entry.second, 0U);
       BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
       BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
       total += entry.second;
@@ -600,18 +662,18 @@ BOOST_AUTO_TEST_CASE(test_lua_ffi_hashed) {
       serversMap[servers.at(idx - 1).second] = 0;
       servers.at(idx - 1).second->setUp();
     }
-    BOOST_REQUIRE_EQUAL(servers.size(), 10);
+    BOOST_REQUIRE_EQUAL(servers.size(), 10U);
 
     for (const auto& name : names) {
       auto dq = getDQ(&name);
-      auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+      auto server = pol.getSelectedBackend(servers, dq);
       BOOST_REQUIRE(serversMap.count(server) == 1);
       ++serversMap[server];
     }
 
     uint64_t total = 0;
     for (const auto& entry : serversMap) {
-      BOOST_CHECK_GT(entry.second, 0);
+      BOOST_CHECK_GT(entry.second, 0U);
       BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
       BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
       total += entry.second;
@@ -655,18 +717,18 @@ BOOST_AUTO_TEST_CASE(test_lua_ffi_whashed) {
       serversMap[servers.at(idx - 1).second] = 0;
       servers.at(idx - 1).second->setUp();
     }
-    BOOST_REQUIRE_EQUAL(servers.size(), 10);
+    BOOST_REQUIRE_EQUAL(servers.size(), 10U);
 
     for (const auto& name : names) {
       auto dq = getDQ(&name);
-      auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+      auto server = pol.getSelectedBackend(servers, dq);
       BOOST_REQUIRE(serversMap.count(server) == 1);
       ++serversMap[server];
     }
 
     uint64_t total = 0;
     for (const auto& entry : serversMap) {
-      BOOST_CHECK_GT(entry.second, 0);
+      BOOST_CHECK_GT(entry.second, 0U);
       BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
       BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
       total += entry.second;
@@ -717,18 +779,18 @@ BOOST_AUTO_TEST_CASE(test_lua_ffi_chashed) {
       /* make sure that the hashes have been computed */
       servers.at(idx - 1).second->hash();
     }
-    BOOST_REQUIRE_EQUAL(servers.size(), 10);
+    BOOST_REQUIRE_EQUAL(servers.size(), 10U);
 
     for (const auto& name : names) {
       auto dq = getDQ(&name);
-      auto server = getSelectedBackendFromPolicy(pol, servers, dq);
+      auto server = pol.getSelectedBackend(servers, dq);
       BOOST_REQUIRE(serversMap.count(server) == 1);
       ++serversMap[server];
     }
 
     uint64_t total = 0;
     for (const auto& entry : serversMap) {
-      BOOST_CHECK_GT(entry.second, 0);
+      BOOST_CHECK_GT(entry.second, 0U);
       BOOST_CHECK_GT(entry.second, (names.size() / servers.size() / 2));
       BOOST_CHECK_LT(entry.second, (names.size() / servers.size() * 2));
       total += entry.second;
diff --git a/pdns/dnsdistdist/test-proxy_protocol_cc.cc b/pdns/dnsdistdist/test-proxy_protocol_cc.cc
new file mode 120000 (symlink)
index 0000000..6350ca4
--- /dev/null
@@ -0,0 +1 @@
+../test-proxy_protocol_cc.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/views.hh b/pdns/dnsdistdist/views.hh
new file mode 120000 (symlink)
index 0000000..2213b7d
--- /dev/null
@@ -0,0 +1 @@
+../views.hh
\ No newline at end of file
index 34fd0ca1d2c8238a9b986ad64081ed769ad2a884..a3bdc7d8d146cc5f3bbe9ead760a1d64084db61e 100644 (file)
@@ -40,7 +40,7 @@
 
 StatBag S;
 
-struct tm* pdns_localtime_r(const uint32_t* then, struct tm* tm)
+static struct tm* pdns_localtime_r(const uint32_t* then, struct tm* tm)
 {
   time_t t = *then;
   
@@ -49,7 +49,8 @@ struct tm* pdns_localtime_r(const uint32_t* then, struct tm* tm)
 
 int32_t g_clientQuestions, g_clientResponses, g_serverQuestions, g_serverResponses, g_skipped;
 struct pdns_timeval g_lastanswerTime, g_lastquestionTime;
-void makeReport(const struct pdns_timeval& tv)
+
+static void makeReport(const struct pdns_timeval& tv)
 {
   int64_t clientdiff = g_clientQuestions - g_clientResponses;
   int64_t serverdiff = g_serverQuestions - g_serverResponses;
@@ -100,7 +101,7 @@ void makeReport(const struct pdns_timeval& tv)
   g_skipped=0;
 }
 
-void usage() {
+static void usage() {
   cerr<<"syntax: dnsgram INFILE..."<<endl;
 }
 
index 826f666845bf3efcbdac60b737757686ce268b0c..ebd8944e11039b42e10b778cbe7e6c54d2549faf 100644 (file)
@@ -5,6 +5,7 @@
 #include <string>
 #include "dnsname.hh"
 #include "namespaces.hh"
+#include "dnswriter.hh"
 
 namespace {
 void appendSplit(vector<string>& ret, string& segment, char c)
index 6b33ab0930eb0e80eb17e05fc125a34306dbad62..9a4f18d08389a9ad85389793a7b850685e9b900b 100644 (file)
@@ -79,6 +79,8 @@ message PBDNSMessage {
     optional uint32 queryTimeSec = 5;           // Time of the corresponding query reception (seconds since epoch)
     optional uint32 queryTimeUsec = 6;          // Time of the corresponding query reception (additional micro-seconds)
     optional PolicyType appliedPolicyType = 7;  // Type of the filtering policy (RPZ or Lua) applied
+    optional string appliedPolicyTrigger = 8;   // The RPZ trigger
+    optional string appliedPolicyHit = 9;       // The value (qname or IP) that caused the hit
   }
 
   optional DNSResponse response = 13;
index 7ebc2c4f5be07adebedb714261e2f9cdb6db4c77..3c360f18a0d21ea2761b0fb450283865f50b241c 100644 (file)
@@ -208,7 +208,9 @@ std::string DNSName::toDNSString() const
 
 std::string DNSName::toDNSStringLC() const
 {
-  return toLower(toDNSString()); // label lengths are always < 'A'
+  auto result = toDNSString();
+  toLowerInPlace(result); // label lengths are always < 'A'
+  return result;
 }
 
 /**
@@ -254,8 +256,9 @@ DNSName DNSName::makeRelative(const DNSName& zone) const
 {
   DNSName ret(*this);
   ret.makeUsRelative(zone);
-  return ret.empty() ? zone : ret; // HACK FIXME400
+  return ret;
 }
+
 void DNSName::makeUsRelative(const DNSName& zone) 
 {
   if (isPartOf(zone)) {
index f933a62252c1c44281498414c187534248ee982c..222edbaf3810e0f00eb7db35e603f5288f7fdf94 100644 (file)
@@ -63,12 +63,29 @@ class DNSName
 {
 public:
   DNSName()  {}          //!< Constructs an *empty* DNSName, NOT the root!
+  // Work around assertion in some boost versions that do not like self-assignment of boost::container::string
+  DNSName& operator=(const DNSName& rhs)
+  {
+    if (this != &rhs) {
+      d_storage = rhs.d_storage;
+    }
+    return *this;
+  }
+  DNSName& operator=(const DNSName&& rhs)
+  {
+    if (this != &rhs) {
+      d_storage = std::move(rhs.d_storage);
+    }
+    return *this;
+  }
+  DNSName(const DNSName& a) = default;
+  DNSName(DNSName&& a) = default;
   explicit DNSName(const char* p): DNSName(p, std::strlen(p)) {} //!< Constructs from a human formatted, escaped presentation
   explicit DNSName(const char* p, size_t len);      //!< Constructs from a human formatted, escaped presentation
   explicit DNSName(const std::string& str) : DNSName(str.c_str(), str.length()) {}; //!< Constructs from a human formatted, escaped presentation
   DNSName(const char* p, int len, int offset, bool uncompress, uint16_t* qtype=nullptr, uint16_t* qclass=nullptr, unsigned int* consumed=nullptr, uint16_t minOffset=0); //!< Construct from a DNS Packet, taking the first question if offset=12. If supplied, consumed is set to the number of bytes consumed from the packet, which will not be equal to the wire length of the resulting name in case of compression.
   
-  bool isPartOf(const DNSName& rhs) const;   //!< Are we part of the rhs name?
+  bool isPartOf(const DNSName& rhs) const;   //!< Are we part of the rhs name? Note that name.isPartOf(name).
   inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty
   bool operator!=(const DNSName& other) const { return !(*this == other); }
 
index 97a372be32f141afd642aab116528b22490457f7..0c7a63f26090f0f70cea107f2c09e05e5f8b1ec3 100644 (file)
@@ -48,8 +48,8 @@
 #include "dnssecinfra.hh"
 #include "base64.hh"
 #include "ednssubnet.hh"
-#include "gss_context.hh"
 #include "dns_random.hh"
+#include "shuffle.hh"
 
 bool DNSPacket::s_doEDNSSubnetProcessing;
 uint16_t DNSPacket::s_udpTruncationThreshold;
@@ -226,7 +226,7 @@ void DNSPacket::wrapup()
   static bool mustNotShuffle = ::arg().mustDo("no-shuffle");
 
   if(!d_tcp && !mustNotShuffle) {
-    shuffle(d_rrs);
+    pdns::shuffle(d_rrs);
   }
   d_wrapped=true;
 
@@ -633,14 +633,12 @@ bool DNSPacket::checkForCorrectTSIG(UeberBackend* B, DNSName* keyname, string* s
     tt.algo = DNSName("hmac-md5");
 
   string secret64;
-  if (tt.algo != DNSName("gss-tsig")) {
-    if(!B->getTSIGKey(*keyname, &tt.algo, &secret64)) {
-      g_log<<Logger::Error<<"Packet for domain '"<<this->qdomain<<"' denied: can't find TSIG key with name '"<<*keyname<<"' and algorithm '"<<tt.algo<<"'"<<endl;
-      return false;
-    }
-    B64Decode(secret64, *secret);
-    tt.secret = *secret;
+  if(!B->getTSIGKey(*keyname, &tt.algo, &secret64)) {
+    g_log<<Logger::Error<<"Packet for domain '"<<this->qdomain<<"' denied: can't find TSIG key with name '"<<*keyname<<"' and algorithm '"<<tt.algo<<"'"<<endl;
+    return false;
   }
+  B64Decode(secret64, *secret);
+  tt.secret = *secret;
 
   bool result;
 
index f4b5e816f42421a56f75e8ecb83e78220f7393a9..a84fc2141ac0793289e0a93b75d076b080537152 100644 (file)
@@ -40,17 +40,29 @@ public:
     // parse the input
     vector<string> parts;
     stringtok(parts, zone);
-    if(parts.size()!=3 && !(parts.size()==2 && equals(parts[1],"0")) )
-      throw MOADNSException("Unknown record was stored incorrectly, need 3 fields, got "+std::to_string(parts.size())+": "+zone );
-    const string& relevant=(parts.size() > 2) ? parts[2] : "";
-    unsigned int total=pdns_stou(parts[1]);
-    if(relevant.size() % 2 || relevant.size() / 2 != total)
+    // we need exactly 3 parts, except if the length field is set to 0 then we only need 2
+    if (parts.size() != 3 && !(parts.size() == 2 && equals(parts.at(1), "0"))) {
+      throw MOADNSException("Unknown record was stored incorrectly, need 3 fields, got " + std::to_string(parts.size()) + ": " + zone);
+    }
+
+    if (parts.at(0) != "\\#") {
+      throw MOADNSException("Unknown record was stored incorrectly, first part should be '\\#', got '" + parts.at(0) + "'");
+    }
+
+    const string& relevant = (parts.size() > 2) ? parts.at(2) : "";
+    unsigned int total = pdns_stou(parts.at(1));
+    if (relevant.size() % 2 || (relevant.size() / 2) != total) {
       throw MOADNSException((boost::format("invalid unknown record length: size not equal to length field (%d != 2 * %d)") % relevant.size() % total).str());
+    }
+
     string out;
-    out.reserve(total+1);
-    for(unsigned int n=0; n < total; ++n) {
+    out.reserve(total + 1);
+
+    for (unsigned int n = 0; n < total; ++n) {
       int c;
-      sscanf(relevant.c_str()+2*n, "%02x", &c);
+      if (sscanf(&relevant.at(2*n), "%02x", &c) != 1) {
+        throw MOADNSException("unable to read data at position " + std::to_string(2 * n) + " from unknown record of size " + std::to_string(relevant.size()));
+      }
       out.append(1, (char)c);
     }
 
@@ -83,7 +95,7 @@ private:
   vector<uint8_t> d_record;
 };
 
-shared_ptr<DNSRecordContent> DNSRecordContent::unserialize(const DNSName& qname, uint16_t qtype, const string& serialized)
+shared_ptr<DNSRecordContent> DNSRecordContent::deserialize(const DNSName& qname, uint16_t qtype, const string& serialized)
 {
   dnsheader dnsheader;
   memset(&dnsheader, 0, sizeof(dnsheader));
@@ -94,7 +106,7 @@ shared_ptr<DNSRecordContent> DNSRecordContent::unserialize(const DNSName& qname,
 
   /* will look like: dnsheader, 5 bytes, encoded qname, dns record header, serialized data */
 
-  string encoded=qname.toDNSString();
+  const auto& encoded = qname.getStorage();
 
   packet.resize(sizeof(dnsheader) + 5 + encoded.size() + sizeof(struct dnsrecordheader) + serialized.size());
 
index e11802616458bd27afcab51f136b765883abd9fb..34ab2bc13fa65529066337b02c895b98cd3aeaca 100644 (file)
@@ -181,7 +181,7 @@ private:
   uint16_t d_pos;
   uint16_t d_startrecordpos; // needed for getBlob later on
   uint16_t d_recordlen;      // ditto
-  uint16_t not_used; // Aligns the whole class on 8-byte boundries
+  uint16_t not_used; // Aligns the whole class on 8-byte boundaries
   const std::string& d_content;
 };
 
@@ -220,7 +220,7 @@ public:
     return typeid(*this)==typeid(rhs) && this->getZoneRepresentation() == rhs.getZoneRepresentation();
   }
   
-  static shared_ptr<DNSRecordContent> unserialize(const DNSName& qname, uint16_t qtype, const string& serialized);
+  static shared_ptr<DNSRecordContent> deserialize(const DNSName& qname, uint16_t qtype, const string& serialized);
 
   void doRecordCheck(const struct DNSRecord&){}
 
index 3924a698d17a05007215ac8d4137dd765dedf65a..bf3ef84690e2d12d5e809b790ba882094b46b9ab 100644 (file)
 #include "namespaces.hh"
 PcapPacketReader::PcapPacketReader(const string& fname) : d_fname(fname)
 {
-  d_fp=fopen(fname.c_str(),"r");
-  if(!d_fp)
+  d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname.c_str(), "r"), fclose);
+  if (!d_fp) {
     unixDie("Unable to open file " + fname);
-  
-  int flags=fcntl(fileno(d_fp),F_GETFL,0);
-  fcntl(fileno(d_fp), F_SETFL,flags&(~O_NONBLOCK)); // bsd needs this in stdin (??)
-  
+  }
+
+  int flags = fcntl(fileno(d_fp.get()), F_GETFL, 0);
+  fcntl(fileno(d_fp.get()), F_SETFL, flags & (~O_NONBLOCK)); // bsd needs this in stdin (??)
+
   checkedFread(&d_pfh);
-  
-  if(d_pfh.magic != 2712847316UL)
+
+  if (d_pfh.magic != 2712847316UL) {
     throw runtime_error((format("PCAP file %s has bad magic %x, should be %x") % fname % d_pfh.magic % 2712847316UL).str());
-  
+  }
+
   if( d_pfh.linktype==1) {
     d_skipMediaHeader=sizeof(struct ether_header);
   }
+  else if( d_pfh.linktype==12) { // LOOP
+    d_skipMediaHeader=4;
+  }
   else if(d_pfh.linktype==101) {
     d_skipMediaHeader=0;
   }
@@ -52,22 +57,17 @@ PcapPacketReader::PcapPacketReader(const string& fname) : d_fname(fname)
     d_skipMediaHeader=16;
   }
   else throw runtime_error((format("Unsupported link type %d") % d_pfh.linktype).str());
-  
-  d_runts = d_oversized = d_correctpackets = d_nonetheripudp = 0;
-}
 
-PcapPacketReader::~PcapPacketReader()
-{
-  fclose(d_fp);
+  d_runts = d_oversized = d_correctpackets = d_nonetheripudp = 0;
 }
 
-
 void PcapPacketReader::checkedFreadSize(void* ptr, size_t size) 
 {
-  int ret=fread(ptr, 1, size, d_fp);
-  if(ret < 0)
+  int ret = fread(ptr, 1, size, d_fp.get());
+  if (ret < 0) {
     unixDie( (format("Error reading %d bytes from %s") % size % d_fname).str());
-  
+  }
+
   if(!ret)
     throw EofException();
   
@@ -114,6 +114,16 @@ try
       d_ether=reinterpret_cast<struct ether_header*>(d_buffer);
       contentCode=ntohs(d_ether->ether_type);
     }
+    else if(d_pfh.linktype == 12) { // LOOP
+      if (d_pheader.caplen < (d_skipMediaHeader + sizeof(*d_ip))) {
+        d_runts++;
+        continue;
+      }
+      if(d_ip->ip_v == 4)
+       contentCode = 0x0800;
+      else
+       contentCode = 0x86dd;
+    }
     else if(d_pfh.linktype==101) {
       if (d_pheader.caplen < (d_skipMediaHeader + sizeof(*d_ip))) {
         d_runts++;
@@ -218,12 +228,14 @@ PcapPacketWriter::PcapPacketWriter(const string& fname, const PcapPacketReader&
 
 PcapPacketWriter::PcapPacketWriter(const string& fname) : d_fname(fname)
 {
-  d_fp=fopen(fname.c_str(),"w");
-  if(!d_fp)
+  d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname.c_str(),"w"), fclose);
+
+  if (!d_fp) {
     unixDie("Unable to open file");
-  
-  int flags=fcntl(fileno(d_fp),F_GETFL,0);
-  fcntl(fileno(d_fp), F_SETFL,flags&(~O_NONBLOCK)); // bsd needs this in stdin (??)
+  }
+
+  int flags = fcntl(fileno(d_fp.get()), F_GETFL, 0);
+  fcntl(fileno(d_fp.get()), F_SETFL,flags & (~O_NONBLOCK)); // bsd needs this in stdin (??)
 }
 
 void PcapPacketWriter::write()
@@ -233,14 +245,9 @@ void PcapPacketWriter::write()
   }
 
   if(d_first) {
-    fwrite(&d_ppr->d_pfh, 1, sizeof(d_ppr->d_pfh), d_fp);
+    fwrite(&d_ppr->d_pfh, 1, sizeof(d_ppr->d_pfh), d_fp.get());
     d_first=false;
   }
-  fwrite(&d_ppr->d_pheader, 1, sizeof(d_ppr->d_pheader), d_fp);
-  fwrite(d_ppr->d_buffer, 1, d_ppr->d_pheader.caplen, d_fp);
-}
-
-PcapPacketWriter::~PcapPacketWriter()
-{
-  fclose(d_fp);
+  fwrite(&d_ppr->d_pheader, 1, sizeof(d_ppr->d_pheader), d_fp.get());
+  fwrite(d_ppr->d_buffer, 1, d_ppr->d_pheader.caplen, d_fp.get());
 }
index a8a6e9a1201cc2dc3d58e46625a600faf4e9c5bd..5cb73a325c3c6d921f845be23b0cf8ea537a68c3 100644 (file)
@@ -90,8 +90,6 @@ public:
 
   PcapPacketReader(const string& fname); 
 
-  ~PcapPacketReader();
-
   template<typename T>
   void checkedFread(T* ptr)
   {
@@ -119,7 +117,7 @@ public:
   unsigned int d_runts, d_oversized, d_correctpackets, d_nonetheripudp;
   char d_buffer[32768];
 private:
-  FILE* d_fp;
+  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
   string d_fname;
   unsigned int d_skipMediaHeader;
 };
@@ -132,12 +130,11 @@ public:
   
   void write();
   void setPPR(const PcapPacketReader& ppr) { d_ppr = &ppr; }
-  ~PcapPacketWriter();
 
 private:
   string d_fname;
   const PcapPacketReader* d_ppr{nullptr};
 
-  FILE *d_fp;
+  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
   bool d_first{true};
 }; 
index 22482737b06ee2764e1f15b2ff4255f7d1307a32..664ba89e5ecbc3eb615b1ed4c599678597a14fc6 100644 (file)
@@ -64,7 +64,7 @@ try {
 
   PcapPacketReader pr(argv[1]);
 
-  FILE* fp = fopen(argv[2], "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(argv[2], "w"), fclose);
   if (!fp) {
     cerr<<"Error opening output file "<<argv[2]<<": "<<stringerror()<<endl;
     exit(EXIT_FAILURE);
@@ -149,17 +149,14 @@ try {
       message.serialize(str);
 
       uint16_t mlen = htons(str.length());
-      fwrite(&mlen, 1, sizeof(mlen), fp);
-      fwrite(str.c_str(), 1, str.length(), fp);
+      fwrite(&mlen, 1, sizeof(mlen), fp.get());
+      fwrite(str.c_str(), 1, str.length(), fp.get());
     }
   }
   catch (const std::exception& e) {
     cerr<<"Error while parsing the PCAP file: "<<e.what()<<endl;
-    fclose(fp);
     exit(EXIT_FAILURE);
   }
-
-  fclose(fp);
 }
 catch(const std::exception& e) {
   cerr<<"Error opening PCAP file: "<<e.what()<<endl;
index decd631e4f5fa63d22909e53d5450d68cecc94d4..e2dd8e82034187921c2570ec1cb716c1de6d8295 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
+#include <sys/types.h>
+#include <thread>
+
 #include "packetcache.hh"
 #include "utility.hh"
 #include "dnsproxy.hh"
 #include "pdnsexception.hh"
-#include <sys/types.h>
 #include "dns.hh"
 #include "logger.hh"
 #include "statbag.hh"
@@ -39,7 +42,6 @@ extern StatBag S;
 
 DNSProxy::DNSProxy(const string &remote)
 {
-  pthread_mutex_init(&d_lock,0);
   d_resanswers=S.getPointer("recursing-answers");
   d_resquestions=S.getPointer("recursing-questions");
   d_udpanswers=S.getPointer("udp-answers");
@@ -83,8 +85,8 @@ DNSProxy::DNSProxy(const string &remote)
 
 void DNSProxy::go()
 {
-  pthread_t tid;
-  pthread_create(&tid,0,&launchhelper,this);
+  std::thread t(std::bind(&DNSProxy::mainloop, this));
+  t.detach();
 }
 
 //! look up qname target with r->qtype, plonk it in the answer section of 'r' with name aname
@@ -129,7 +131,7 @@ bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& targ
   uint16_t id;
   uint16_t qtype = r->qtype.getCode();
   {
-    Lock l(&d_lock);
+    std::lock_guard<std::mutex> l(d_lock);
     id=getID_locked();
 
     ConntrackEntry ce;
@@ -216,7 +218,7 @@ void DNSProxy::mainloop(void)
       dnsheader d;
       memcpy(&d,buffer,sizeof(d));
       {
-        Lock l(&d_lock);
+        std::lock_guard<std::mutex> l(d_lock);
 #if BYTE_ORDER == BIG_ENDIAN
         // this is needed because spoof ID down below does not respect the native byteorder
         d.id = ( 256 * (uint16_t)buffer[1] ) + (uint16_t)buffer[0];  
index e9b5807af49832b362573137820fd20671100352..0adc07af9a4f769044fd2141b2adb58abcece9e2 100644 (file)
@@ -20,8 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
-#include <pthread.h>
 #include <map>
+#include <mutex>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -56,11 +56,6 @@ public:
   bool completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& target,const DNSName& aname, const uint8_t scopeMask);
 
   void mainloop();                  //!< this is the main loop that receives reply packets and sends them out again
-  static void *launchhelper(void *p)
-  {
-    static_cast<DNSProxy *>(p)->mainloop();
-    return 0;
-  }
   bool recurseFor(DNSPacket* p);
 private:
   struct ConntrackEntry
@@ -84,7 +79,7 @@ private:
   AtomicCounter* d_resanswers;
   AtomicCounter* d_udpanswers;
   AtomicCounter* d_resquestions;
-  pthread_mutex_t d_lock;
+  std::mutex d_lock;
   map_t d_conntrack;
   int d_sock;
   int getID_locked();
index 2f2268bf544c342e8d2a102cd316cfa94a7666cb..04613000f23b113a603ef970f8efd9c599192eaf 100644 (file)
@@ -489,6 +489,225 @@ string EUI64RecordContent::getZoneRepresentation(bool noDot) const
 
 /* EUI64 end */
 
+/* APL start */
+/* https://tools.ietf.org/html/rfc3123 */
+void APLRecordContent::report(void)
+{
+  regist(1, QType::APL, &make, &make, "APL");
+}
+
+// Parse incoming packets (e.g. nsupdate)
+std::shared_ptr<DNSRecordContent> APLRecordContent::make(const DNSRecord &dr, PacketReader& pr) {
+  uint8_t temp;
+  APLRDataElement ard;
+  size_t processed = 0;
+
+  auto ret=std::make_shared<APLRecordContent>();
+
+  while (processed<dr.d_clen) {
+    pr.xfr16BitInt(ard.d_family);
+    pr.xfr8BitInt(ard.d_prefix);
+    pr.xfr8BitInt(temp);
+    ard.d_n = (temp & 128) >> 7;
+    ard.d_afdlength = temp & 127;
+
+    if (ard.d_family == APL_FAMILY_IPV4) {
+      if (ard.d_afdlength > 4) {
+        throw MOADNSException("Invalid IP length for IPv4 APL");
+      }
+      memset(ard.d_ip.d_ip4, 0, sizeof(ard.d_ip.d_ip4));
+      for (u_int i=0; i < ard.d_afdlength; i++)
+        pr.xfr8BitInt(ard.d_ip.d_ip4[i]);
+    } else if (ard.d_family == APL_FAMILY_IPV6) {
+      if (ard.d_afdlength > 16) {
+        throw MOADNSException("Invalid IP length for IPv6 APL");
+      }
+      memset(ard.d_ip.d_ip6, 0, sizeof(ard.d_ip.d_ip6));
+      for (u_int i=0; i < ard.d_afdlength; i++)
+        pr.xfr8BitInt(ard.d_ip.d_ip6[i]);
+    } else
+    throw MOADNSException("Unknown family for APL record");
+
+    processed += 4 + ard.d_afdlength;
+
+    ret->aplrdata.push_back(ard);
+  }
+
+  return ret;
+}
+
+// Parse a single APL <apitem>
+APLRDataElement APLRecordContent::parseAPLElement(const string& element) {
+  string record;
+  Netmask nm;
+  unsigned int bytes;
+  bool done_trimming;
+  APLRDataElement ard;
+
+  // Parse the optional leading ! (negate)
+  if (element.at(0) == '!') {
+    ard.d_n = true;
+    record = element.substr(1, element.length()-1);
+  } else {
+    ard.d_n = false;
+    record = element;
+  }
+
+  if (record.find("/") == string::npos) { // Required by RFC section 5
+    throw MOADNSException("Asked to decode '"+element+"' as an APL record, but missing subnet mask");
+  }
+
+
+  if (record.find("1:", 0) == 0) { // IPv4
+    uint32_t v4ip;
+
+    ard.d_family = APL_FAMILY_IPV4;
+
+    // Ensure that a mask is provided
+
+    // Read IPv4 string into a Netmask object
+    nm = Netmask(record.substr(2, record.length() - 2));
+    ard.d_prefix = nm.getBits();
+
+    if (nm.getNetwork().isIPv4() == 0)
+      throw MOADNSException("Asked to decode '"+element+"' as an APL v4 record");
+
+    // Section 4.1 of RFC 3123 (don't send trailing "0" bytes)
+    // Copy data; using array of bytes since we might end up truncating them in the packet
+    v4ip = ntohl(nm.getNetwork().sin4.sin_addr.s_addr);
+    memset(ard.d_ip.d_ip4, 0, sizeof(ard.d_ip.d_ip4));
+    bytes  = 4; // Start by assuming we'll send 4 bytes
+    done_trimming = false;
+    for (int i=0; i<4; i++) {
+      ard.d_ip.d_ip4[3-i] = (v4ip & 255);
+      // Remove trailing "0" bytes from packet and update length
+      if ((v4ip & 255) == 0 and !done_trimming) {
+        bytes--;
+      } else {
+        done_trimming = true;
+      }
+      v4ip = v4ip >> 8;
+    }
+    ard.d_afdlength = bytes;
+
+  } else if (record.find("2:", 0) == 0) { // IPv6
+    ard.d_family = APL_FAMILY_IPV6;
+
+    // Parse IPv6 string into a Netmask object
+    nm = Netmask(record.substr(2, record.length() - 2));
+    ard.d_prefix = nm.getBits();
+
+    if (nm.getNetwork().isIPv6() == 0)
+      throw MOADNSException("Asked to decode '"+element+"' as an APL v6 record");
+
+    // Section 4.2 of RFC 3123 (don't send trailing "0" bytes)
+    // Remove trailing "0" bytes from packet and reduce length
+    memset(ard.d_ip.d_ip6, 0, sizeof(ard.d_ip.d_ip6));
+    bytes = 16; // Start by assuming we'll send 16 bytes
+    done_trimming = false;
+    for (int i=0; i<16; i++) {
+      ard.d_ip.d_ip6[15-i] = nm.getNetwork().sin6.sin6_addr.s6_addr[15-i];
+      if (nm.getNetwork().sin6.sin6_addr.s6_addr[15-i] == 0 and !done_trimming) {
+        // trailing 0 byte, update length
+        bytes--;
+      } else {
+        done_trimming = true;
+      }
+    }
+    ard.d_afdlength = bytes;
+
+  } else {
+      throw MOADNSException("Asked to encode '"+element+"' as an IPv6 APL record but got unknown Address Family");
+  }
+  return ard;
+
+}
+
+// Parse backend record (0, 1 or more <apitem>)
+std::shared_ptr<DNSRecordContent> APLRecordContent::make(const string& zone) {
+  APLRDataElement ard;
+  vector<string> elements;
+
+  auto ret=std::make_shared<APLRecordContent>();
+
+  boost::split(elements, zone, boost::is_any_of(" "));
+  for (std::vector<std::string>::iterator elem = elements.begin() ; elem != elements.end(); ++elem) {
+    if (!elem->empty()) {
+      ard = ret->parseAPLElement(*elem);
+      ret->aplrdata.push_back(ard);
+    }
+  }
+  return ret;
+}
+
+
+// DNSRecord to Packet conversion
+void APLRecordContent::toPacket(DNSPacketWriter& pw) {
+  for (std::vector<APLRDataElement>::iterator ard = aplrdata.begin() ; ard != aplrdata.end(); ++ard) {
+    pw.xfr16BitInt(ard->d_family);
+    pw.xfr8BitInt(ard->d_prefix);
+    pw.xfr8BitInt((ard->d_n << 7) + ard->d_afdlength);
+    if (ard->d_family == APL_FAMILY_IPV4) {
+      for (int i=0; i<ard->d_afdlength; i++) {
+        pw.xfr8BitInt(ard->d_ip.d_ip4[i]);
+      }
+    } else if (ard->d_family == APL_FAMILY_IPV6) {
+      for (int i=0; i<ard->d_afdlength; i++) {
+        pw.xfr8BitInt(ard->d_ip.d_ip6[i]);
+      }
+    }
+  }
+}
+
+// Decode record into string
+string APLRecordContent::getZoneRepresentation(bool noDot) const {
+  string s_n, s_family, output;
+  ComboAddress ca;
+  Netmask nm;
+
+  output = "";
+
+  for (std::vector<APLRDataElement>::const_iterator ard = aplrdata.begin() ; ard != aplrdata.end(); ++ard) {
+
+    // Negation flag
+    if (ard->d_n) {
+      s_n = "!";
+    } else {
+      s_n = "";
+    }
+
+    if (ard->d_family == APL_FAMILY_IPV4) { // IPv4
+      s_family = std::to_string(APL_FAMILY_IPV4);
+      ca = ComboAddress();
+      for (int i=0; i < 4; i++) {
+          ca.sin4.sin_addr.s_addr |= ard->d_ip.d_ip4[i] << (i*8);
+      }
+    } else if (ard->d_family == APL_FAMILY_IPV6) { // IPv6
+      s_family = std::to_string(APL_FAMILY_IPV6);
+      ca = ComboAddress();
+      ca.sin4.sin_family = AF_INET6;
+      for (int i=0; i < 16; i++) {
+        if (i < ard->d_afdlength) {
+          ca.sin6.sin6_addr.s6_addr[i] = ard->d_ip.d_ip6[i];
+        } else {
+          ca.sin6.sin6_addr.s6_addr[i] = 0;
+        }
+      }
+    } else {
+      throw MOADNSException("Asked to decode APL record but got unknown Address Family");
+    }
+
+    nm = Netmask(ca, ard->d_prefix);
+
+    output += s_n + s_family + ":" + nm.toString();
+    if (std::next(ard) != aplrdata.end())
+      output += " ";
+  }
+  return output;
+}
+
+/* APL end */
+
 boilerplate_conv(TKEY, QType::TKEY,
                  conv.xfrName(d_algo);
                  conv.xfr32BitInt(d_inception);
@@ -650,6 +869,7 @@ void reportOtherTypes()
    MINFORecordContent::report();
    URIRecordContent::report();
    CAARecordContent::report();
+   APLRecordContent::report();
 }
 
 void reportAllTypes()
index 3c2ab3d6f884345d110815ccdec4f8c84234d02f..b625ebfdee2255067f786f192477f2889a7a4636 100644 (file)
@@ -784,6 +784,29 @@ private:
  uint8_t d_eui64[8];
 };
 
+#define APL_FAMILY_IPV4 1
+#define APL_FAMILY_IPV6 2
+typedef struct s_APLRDataElement {
+  uint16_t d_family;
+  uint8_t d_prefix;
+  bool d_n : 1;
+  unsigned int d_afdlength : 7;
+  union u_d_ip {
+      uint8_t d_ip4[4];
+      uint8_t d_ip6[16];
+  } d_ip;
+} APLRDataElement;
+class APLRecordContent : public DNSRecordContent
+{
+public:
+  APLRecordContent() {};
+  includeboilerplate(APL)
+private:
+  std::vector<APLRDataElement> aplrdata;
+  APLRDataElement parseAPLElement(const string &element);
+};
+
+
 class TKEYRecordContent : public DNSRecordContent
 {
 public:
index f82d6542b0f179478f3cf6562bdd6e996e87453e..c7cda449bb4ac108a088b2943f102fce0c8ffde8 100644 (file)
@@ -93,7 +93,7 @@ namespace po = boost::program_options;
 
 po::variables_map g_vm;
 
-const struct timeval operator*(float fact, const struct timeval& rhs)
+static const struct timeval operator*(float fact, const struct timeval& rhs)
 {
   //  cout<<"In: "<<rhs.tv_sec<<" + "<<rhs.tv_usec<<"\n";
   struct timeval ret;
@@ -121,7 +121,7 @@ const struct timeval operator*(float fact, const struct timeval& rhs)
 }
 
 bool g_pleaseQuit;
-void pleaseQuitHandler(int)
+static void pleaseQuitHandler(int)
 {
   g_pleaseQuit=true;
 }
@@ -164,7 +164,7 @@ private:
 } s_idmanager;
 
 
-void setSocketBuffer(int fd, int optname, uint32_t size)
+static void setSocketBuffer(int fd, int optname, uint32_t size)
 {
   uint32_t psize=0;
   socklen_t len=sizeof(psize);
@@ -225,7 +225,7 @@ unsigned int s_wednserrors, s_origdnserrors, s_duplicates;
 
 
 
-void WeOrigSlowQueriesDelta(int& weOutstanding, int& origOutstanding, int& weSlow, int& origSlow)
+static void WeOrigSlowQueriesDelta(int& weOutstanding, int& origOutstanding, int& weSlow, int& origSlow)
 {
   struct timeval now;
   gettimeofday(&now, 0);
@@ -265,21 +265,21 @@ void WeOrigSlowQueriesDelta(int& weOutstanding, int& origOutstanding, int& weSlo
   }
 }
 
-void compactAnswerSet(MOADNSParser::answers_t orig, set<DNSRecord>& compacted)
+static void compactAnswerSet(MOADNSParser::answers_t orig, set<DNSRecord>& compacted)
 {
   for(MOADNSParser::answers_t::const_iterator i=orig.begin(); i != orig.end(); ++i)
     if(i->first.d_place==DNSResourceRecord::ANSWER)
       compacted.insert(i->first);
 }
 
-bool isRcodeOk(int rcode)
+static bool isRcodeOk(int rcode)
 {
   return rcode==0 || rcode==3;
 }
 
 set<pair<DNSName,uint16_t> > s_origbetterset;
 
-bool isRootReferral(const MOADNSParser::answers_t& answers)
+static bool isRootReferral(const MOADNSParser::answers_t& answers)
 {
   if(answers.empty())
     return false;
@@ -296,7 +296,7 @@ bool isRootReferral(const MOADNSParser::answers_t& answers)
 }
 
 vector<uint32_t> flightTimes;
-void accountFlightTime(qids_t::const_iterator iter)
+static void accountFlightTime(qids_t::const_iterator iter)
 {
   if(flightTimes.empty())
     flightTimes.resize(2050); 
@@ -310,7 +310,7 @@ void accountFlightTime(qids_t::const_iterator iter)
   flightTimes[mdiff]++;
 }
 
-uint64_t countLessThan(unsigned int msec)
+static uint64_t countLessThan(unsigned int msec)
 {
   uint64_t ret=0;
   for(unsigned int i = 0 ; i < msec && i < flightTimes.size() ; ++i) {
@@ -319,7 +319,7 @@ uint64_t countLessThan(unsigned int msec)
   return ret;
 }
 
-void emitFlightTimes()
+static void emitFlightTimes()
 {
   uint64_t totals = countLessThan(flightTimes.size());
   unsigned int limits[]={1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100, 200, 500, 1000, (unsigned int) flightTimes.size()};
@@ -338,7 +338,7 @@ void emitFlightTimes()
   }
 }
 
-void measureResultAndClean(qids_t::const_iterator iter)
+static void measureResultAndClean(qids_t::const_iterator iter)
 {
   const QuestionData& qd=*iter;
   accountFlightTime(iter);
@@ -406,7 +406,7 @@ void measureResultAndClean(qids_t::const_iterator iter)
 
 std::unique_ptr<Socket> s_socket = nullptr;
 
-void receiveFromReference()
+static void receiveFromReference()
 try
 {
   string packet;
@@ -469,7 +469,7 @@ catch(...)
   exit(1);
 }
 
-void pruneQids()
+static void pruneQids()
 {
   struct timeval now;
   gettimeofday(&now, 0);
@@ -491,7 +491,7 @@ void pruneQids()
   }
 }
 
-void printStats(uint64_t origWaitingFor=0, uint64_t weWaitingFor=0)
+static void printStats(uint64_t origWaitingFor=0, uint64_t weWaitingFor=0)
 {
   format headerfmt   ("%|9t|Questions - Pend. - Drop = Answers = (On time + Late) = (Err + Ok)\n");
   format datafmt("%s%|9t|%d %|21t|%d %|29t|%d %|36t|%d %|47t|%d %|57t|%d %|66t|%d %|72t|%d\n");
@@ -511,7 +511,7 @@ void printStats(uint64_t origWaitingFor=0, uint64_t weWaitingFor=0)
 
 }
 
-void houseKeeping()
+static void houseKeeping()
 {
   static timeval last;
 
@@ -703,7 +703,7 @@ static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, i
   return sent;
 }
 
-void usage(po::options_description &desc) {
+static void usage(po::options_description &desc) {
   cerr << "Usage: dnsreplay [OPTIONS] FILENAME [IP-ADDRESS] [PORT]"<<endl;
   cerr << desc << "\n";
 }
index 936a05dd9390707348fa20b898c9a4be03d3d51d..e447c1a681b6a614d4862625c1652e57c4ed9075 100644 (file)
@@ -44,7 +44,7 @@ using namespace ::boost::multi_index;
 #include "namespaces.hh"
 StatBag S;
 
-void usage() {
+static void usage() {
   cerr<<"syntax: dnsscan INFILE ..."<<endl;
 }
 
index e58be17ba3b687cb3ef2e05a56813ed1388cdc70..1427b1921651effb9fa1db6f6e2aa6c0846bac48 100644 (file)
@@ -74,7 +74,7 @@ struct QuestionData
 typedef map<QuestionIdentifier, QuestionData> statmap_t;
 statmap_t statmap;
 
-unsigned int liveQuestions()
+static unsigned int liveQuestions()
 {
   unsigned int ret=0;
   for(statmap_t::value_type& val :  statmap) {
@@ -107,7 +107,7 @@ struct LiveCounts
   }
 };
 
-void visitor(const StatNode* node, const StatNode::Stat& selfstat, const StatNode::Stat& childstat)
+static void visitor(const StatNode* node, const StatNode::Stat& selfstat, const StatNode::Stat& childstat)
 {
   // 20% servfails, >100 children, on average less than 2 copies of a query
   // >100 different subqueries
@@ -123,7 +123,7 @@ void visitor(const StatNode* node, const StatNode::Stat& selfstat, const StatNod
   }
 }
 
-const struct timeval operator-(const struct pdns_timeval& lhs, const struct pdns_timeval& rhs)
+static const struct timeval operator-(const struct pdns_timeval& lhs, const struct pdns_timeval& rhs)
 {
   struct timeval a{lhs.tv_sec, static_cast<suseconds_t>(lhs.tv_usec)}, b{rhs.tv_sec, static_cast<suseconds_t>(rhs.tv_usec)};
   return operator-(a,b);
index 426bf596a03460896d96a2c8fc1f53f830946ff0..80ef9c24d7f09f0d90468d47689cf6d68268b712 100644 (file)
@@ -44,7 +44,6 @@
 #ifdef HAVE_P11KIT1
 #include "pkcs11signers.hh"
 #endif
-#include "gss_context.hh"
 #include "misc.hh"
 
 using namespace boost::assign;
@@ -134,10 +133,10 @@ shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCString(DNSKEYRecor
 
 std::string DNSCryptoKeyEngine::convertToISC() const
 {
-  storvector_t stormap = this->convertToISCVector();
+  storvector_t storvector = this->convertToISCVector();
   ostringstream ret;
   ret<<"Private-key-format: v1.2\n";
-  for(const stormap_t::value_type& value :  stormap) {
+  for(const storvector_t::value_type& value :  storvector) {
     if(value.first != "Algorithm" && value.first != "PIN" && 
        value.first != "Slot" && value.first != "Engine" &&
        value.first != "Label" && value.first != "PubLabel")
@@ -541,55 +540,6 @@ DNSKEYRecordContent DNSSECPrivateKey::getDNSKEY() const
   return makeDNSKEYFromDNSCryptoKeyEngine(getKey(), d_algorithm, d_flags);
 }
 
-class DEREater
-{
-public:
-  DEREater(const std::string& str) : d_str(str), d_pos(0)
-  {}
-  
-  struct eof{};
-  
-  uint8_t getByte()
-  {
-    if(d_pos >= d_str.length()) {
-      throw eof();
-    }
-    return (uint8_t) d_str[d_pos++];
-  }
-  
-  uint32_t getLength()
-  {
-    uint8_t first = getByte();
-    if(first < 0x80) {
-      return first;
-    }
-    first &= ~0x80;
-    
-    uint32_t len=0;
-    for(int n=0; n < first; ++n) {
-      len *= 0x100;
-      len += getByte();
-    }
-    return len;
-  }
-  
-  std::string getBytes(unsigned int len)
-  {
-    std::string ret;
-    for(unsigned int n=0; n < len; ++n)
-      ret.append(1, (char)getByte());
-    return ret;
-  }
-  
-  std::string::size_type getOffset() 
-  {
-    return d_pos;
-  }
-private:
-  const std::string& d_str;
-  std::string::size_type d_pos;
-};
-
 static string calculateHMAC(const std::string& key, const std::string& text, TSIGHashEnum hasher) {
 
   const EVP_MD* md_type;
@@ -710,9 +660,7 @@ void addTSIG(DNSPacketWriter& pw, TSIGRecordContent& trc, const DNSName& tsigkey
   string toSign = makeTSIGPayload(tsigprevious, reinterpret_cast<const char*>(pw.getContent().data()), pw.getContent().size(), tsigkeyname, trc, timersonly);
 
   if (algo == TSIG_GSS) {
-    if (!gss_add_signature(tsigkeyname, toSign, trc.d_mac)) {
-      throw PDNSException(string("Could not add TSIG signature with algorithm 'gss-tsig' and key name '")+tsigkeyname.toLogString()+string("'"));
-    }
+    throw PDNSException(string("Unsupported TSIG GSS algorithm ") + trc.d_algoName.toLogString());
   } else {
     trc.d_mac = calculateHMAC(tsigsecret, toSign, algo);
     //  trc.d_mac[0]++; // sabotage
@@ -747,10 +695,7 @@ bool validateTSIG(const std::string& packet, size_t sigPos, const TSIGTriplet& t
   tsigMsg = makeTSIGMessageFromTSIGPacket(packet, sigPos, tt.name, trc, previousMAC, timersOnly, dnsHeaderOffset);
 
   if (algo == TSIG_GSS) {
-    GssContext gssctx(tt.name);
-    if (!gss_verify_signature(tt.name, tsigMsg, theirMAC)) {
-      throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate");
-    }
+    throw std::runtime_error("Unsupported TSIG GSS algorithm " + trc.d_algoName.toLogString());
   } else {
     string ourMac = calculateHMAC(tt.secret, tsigMsg, algo);
 
index c2fa1c0ab631b72475da626931492002d9d93ac6..f323b3adb1c684e5cc7566bb747af47a411421e4 100644 (file)
@@ -33,6 +33,7 @@
 #include "dnssecinfra.hh"
 #include "dnsrecords.hh"
 #include "ueberbackend.hh"
+#include "lock.hh"
 
 using namespace ::boost::multi_index;
 
@@ -186,6 +187,8 @@ public:
 
   static uint64_t dbdnssecCacheSizes(const std::string& str);
   static void clearAllCaches();
+  static bool clearKeyCache(const DNSName& name);
+  static bool clearMetaCache(const DNSName& name);
   static void clearCaches(const DNSName& name);
 
   bool doesDNSSEC();
@@ -238,8 +241,9 @@ public:
 
   static void setMaxEntries(size_t maxEntries);
 
+  typedef std::map<std::string, std::vector<std::string> > METAValues;
 private:
-
+  int64_t d_metaCacheCleanAction{0};
 
   struct KeyCacheEntry
   {
@@ -257,16 +261,14 @@ private:
   
   struct METACacheEntry
   {
-    uint32_t getTTD() const
+    time_t getTTD() const
     {
       return d_ttd;
     }
-  
+
     DNSName d_domain;
-    mutable std::string d_key, d_value;
-    mutable bool d_isset;
-    unsigned int d_ttd;
-  
+    mutable METAValues d_value;
+    time_t d_ttd;
   };
   
   struct KeyCacheTag{};
@@ -284,12 +286,7 @@ private:
   typedef multi_index_container<
     METACacheEntry,
     indexed_by<
-      ordered_unique<tag<CompositeTag>,
-        composite_key< 
-          METACacheEntry, 
-          member<METACacheEntry, DNSName, &METACacheEntry::d_domain> ,
-          member<METACacheEntry, std::string, &METACacheEntry::d_key>
-        >, composite_key_compare<std::less<DNSName>, CIStringCompare> >,
+      ordered_unique<member<METACacheEntry, DNSName, &METACacheEntry::d_domain> >,
       sequenced<tag<SequencedTag>>
     >
   > metacache_t;
@@ -298,8 +295,9 @@ private:
 
   static keycache_t s_keycache;
   static metacache_t s_metacache;
-  static pthread_rwlock_t s_metacachelock;
-  static pthread_rwlock_t s_keycachelock;
+  static int64_t s_metaCacheCleanActions;
+  static ReadWriteLock s_metacachelock;
+  static ReadWriteLock s_keycachelock;
   static AtomicCounter s_ops;
   static time_t s_last_prune;
   static size_t s_maxEntries;
index 7e6f485ee9fa269acfbab56353d7cb06cfd37577..3bb27930c8b183ac4c408ab731ea5b54825f27e0 100644 (file)
@@ -33,7 +33,7 @@
 #include "statbag.hh"
 extern StatBag S;
 
-static pthread_rwlock_t g_signatures_lock = PTHREAD_RWLOCK_INITIALIZER;
+static ReadWriteLock g_signatures_lock;
 typedef map<pair<string, string>, string> signaturecache_t;
 static signaturecache_t g_signatures;
 static int g_cacheweekno;
index b2e9257cd11a654d5b6a8b74d2875b4390c49132..26a0979994ba910c9385fd198100c5680c9d0df3 100644 (file)
@@ -106,7 +106,7 @@ message Message {
 
     enum Type {
         // AUTH_QUERY is a DNS query message received from a resolver by an
-        // authoritative name server, from the perspective of the authorative
+        // authoritative name server, from the perspective of the authoritative
         // name server.
         AUTH_QUERY = 1;
 
index eef62272903e951465ddf1863ee9db6078a9adc9..68cfddb67d741d4539ba2154b10403d39ce91689 100644 (file)
@@ -28,6 +28,8 @@
 
 #include <boost/accumulators/statistics.hpp>
 
+#include <thread>
+
 #include "dnsparser.hh"
 #include "sstuff.hh"
 #include "misc.hh"
@@ -51,7 +53,7 @@ unsigned int g_timeoutMsec;
 AtomicCounter g_networkErrors, g_otherErrors, g_OK, g_truncates, g_authAnswers, g_timeOuts;
 ComboAddress g_dest;
 
-unsigned int makeUsec(const struct timeval& tv)
+static unsigned int makeUsec(const struct timeval& tv)
 {
   return 1000000*tv.tv_sec + tv.tv_usec;
 }
@@ -69,7 +71,7 @@ struct BenchQuery
   time_t answerSecond;
 };
 
-void doQuery(BenchQuery* q)
+static void doQuery(BenchQuery* q)
 try
 {
   vector<uint8_t> packet;
@@ -172,7 +174,7 @@ AtomicCounter g_pos;
 
 vector<BenchQuery> g_queries;
 
-static void* worker(void*)
+static void worker()
 {
   setThreadName("dnstcpb/worker");
   for(;;) {
@@ -182,10 +184,9 @@ static void* worker(void*)
 
     doQuery(&g_queries[pos]); // this is safe as long as nobody *inserts* to g_queries
   }
-  return 0;
 }
 
-void usage(po::options_description &desc) {
+static void usage(po::options_description &desc) {
   cerr<<"Syntax: dnstcpbench REMOTE [PORT] < QUERIES"<<endl;
   cerr<<"Where QUERIES is one query per line, format: qname qtype, just 1 space"<<endl;
   cerr<<desc<<endl;
@@ -251,31 +252,33 @@ try
   }
 
 
-  std::vector<pthread_t> workers(numworkers);
+  std::vector<std::thread> workers;
+  workers.reserve(numworkers);
 
-  FILE* fp;
-  if(!g_vm.count("file"))
-    fp=fdopen(0, "r");
+  std::unique_ptr<FILE, int(*)(FILE*)> fp{nullptr, fclose};
+  if (!g_vm.count("file")) {
+    fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(0, "r"), fclose);
+  }
   else {
-    fp=fopen(g_vm["file"].as<string>().c_str(), "r");
-    if(!fp)
+    fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(g_vm["file"].as<string>().c_str(), "r"), fclose);
+    if (!fp) {
       unixDie("Unable to open "+g_vm["file"].as<string>()+" for input");
+    }
   }
   pair<string, string> q;
   string line;
-  while(stringfgets(fp, line)) {
+  while(stringfgets(fp.get(), line)) {
     trim_right(line);
     q=splitField(line, ' ');
     g_queries.push_back(BenchQuery(q.first, DNSRecordContent::TypeToNumber(q.second)));
   }
-  fclose(fp);
-    
-  for(unsigned int n = 0; n < numworkers; ++n) {
-    pthread_create(&workers[n], 0, worker, 0);
+  fp.reset();
+
+  for (unsigned int n = 0; n < numworkers; ++n) {
+    workers.push_back(std::thread(worker));
   }
-  for(unsigned int n = 0; n < numworkers; ++n) {
-    void* status;
-    pthread_join(workers[n], &status);
+  for (auto& w : workers) {
+    w.join();
   }
   
   using namespace boost::accumulators;
index c3da28c2d595845a749e49d34922b9b72779ce00..62142228616dee05f5ba7e76585af6aec9580544 100644 (file)
@@ -161,7 +161,7 @@ private:
 };
 
 
-void usage() {
+static void usage() {
   cerr<<"Syntax: dnswasher INFILE1 [INFILE2..] OUTFILE"<<endl;
 }
 
index 58b1bf2a4db238f1697de4118ecc8d06a0c26f53..2eed85c2967d449c42b0e22ff3be9b2c174a50ae 100644 (file)
@@ -156,7 +156,7 @@ public:
 private:
   uint16_t lookupName(const DNSName& name, uint16_t* matchlen);
   vector<uint16_t> d_namepositions;
-  // We declare 1 uint_16 in the public section, these 3 align on a 8-byte boundry
+  // We declare 1 uint_16 in the public section, these 3 align on a 8-byte boundary
   uint16_t d_sor;
   uint16_t d_rollbackmarker; // start of last complete packet, for rollback
 
index f7f9a80aaa7b2bfbda59fbf873c0a72189933889..36d720cd427df8304ea1d223c20c0a1d847706be 100644 (file)
@@ -97,7 +97,9 @@ struct DOHFrontend
 
   HTTPVersionStats d_http1Stats;
   HTTPVersionStats d_http2Stats;
+  uint32_t d_internalPipeBufferSize{0};
   bool d_sendCacheControlHeaders{true};
+  bool d_trustForwardedForHeader{false};
 
   time_t getTicketsKeyRotationDelay() const
   {
@@ -174,20 +176,30 @@ struct DOHUnit
   void release()
   {
     if (--d_refcnt == 0) {
+      if (self) {
+        *self = nullptr;
+      }
+
       delete this;
     }
   }
 
+  std::vector<std::pair<std::string, std::string>> headers;
   std::string query;
   std::string response;
+  std::string sni;
+  std::string path;
+  std::string scheme;
+  std::string host;
   ComboAddress remote;
   ComboAddress dest;
   st_h2o_req_t* req{nullptr};
   DOHUnit** self{nullptr};
+  DOHServerConfig* dsc{nullptr};
   std::string contentType;
   std::atomic<uint64_t> d_refcnt{1};
+  size_t query_at{0};
   int rsock;
-  uint16_t qtype;
   /* the status_code is set from
      processDOHQuery() (which is executed in
      the DOH client thread) so that the correct
index 5d457dc47f6f0888fb416571ca5eb333967c1b30..96565a025ef539e85f0334d2bbaaebf97639c74a 100644 (file)
@@ -105,12 +105,7 @@ catch(const std::exception& e) {
 static void tcpAcceptor(const ComboAddress local)
 {
   Socket tcpSocket(local.sin4.sin_family, SOCK_STREAM);
-#ifdef SO_REUSEPORT
-  int one=1;
-  if(setsockopt(tcpSocket.getHandle(), SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0)
-    unixDie("setsockopt for REUSEPORT");
-#endif
-
+  setReusePort(tcpSocket.getHandle());
   tcpSocket.bind(local);
   tcpSocket.listen(1024);
 
@@ -187,12 +182,7 @@ try
   }
 
   Socket s(local.sin4.sin_family, SOCK_DGRAM);
-#ifdef SO_REUSEPORT
-  int one=1;
-  if(setsockopt(s.getHandle(), SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0)
-    unixDie("setsockopt for REUSEPORT");
-#endif
-
+  setReusePort(s.getHandle());
   s.bind(local);
   cout<<"Bound to UDP "<<local.toStringWithPort()<<endl;
 
index db1615909d6df4a2a784905090acefbfdbaf9b56..5614f9c212a95af75a4891684fecec5941b42b8b 100644 (file)
@@ -133,6 +133,7 @@ string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
 
   if(parts.size()>1) {
     for (vector<string>::const_iterator i=++parts.begin();i<parts.end();++i) {
+      g_log<<Logger::Warning<<"Cache clear request for '"<<*i<<"' received from operator"<<endl;
       ret+=purgeAuthCaches(*i);
       if(!boost::ends_with(*i, "$"))
         DNSSECKeeper::clearCaches(DNSName(*i));
@@ -141,6 +142,7 @@ string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
     }
   }
   else {
+    g_log<<Logger::Warning<<"Cache clear request received from operator"<<endl;
     ret = purgeAuthCaches();
     DNSSECKeeper::clearAllCaches();
   }
@@ -220,6 +222,7 @@ string DLSettingsHandler(const vector<string>&parts, Utility::pid_t ppid)
       break;
   if(*p) {
     ::arg().set(parts[1])=parts[2];
+    g_log<<Logger::Warning<<"Configuration change for setting '"<<parts[1]<<"' to value '"<<parts[2]<<"' received from operator"<<endl;
     return "done";
   }
   else
@@ -236,8 +239,8 @@ string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid)
 {
   extern CommunicatorClass Communicator;
   ostringstream os;
-  if(parts.size()!=2)
-    return "syntax: retrieve domain";
+  if(parts.size()!=2 && parts.size()!=3)
+    return "syntax: retrieve domain [ip]";
 
   DNSName domain;
   try {
@@ -246,17 +249,36 @@ string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid)
     return "Failed to parse domain as valid DNS name";
   }
 
+  ComboAddress master_ip;
+  bool override_master = false;
+  if (parts.size() == 3) {
+    try {
+      master_ip = ComboAddress{parts[2], 53};
+    } catch (...) {
+      return "Invalid master address";
+    }
+    override_master = true;
+  }
+
   DomainInfo di;
   UeberBackend B;
-  if(!B.getDomainInfo(domain, di))
-    return "Domain '"+domain.toString()+"' unknown";
-  
-  if(di.kind != DomainInfo::Slave || di.masters.empty())
+  if(!B.getDomainInfo(domain, di)) {
+    return " Domain '"+domain.toString()+"' unknown";
+  }
+
+  if (override_master) {
+    di.masters.clear();
+    di.masters.push_back(master_ip);
+  }
+
+  if(!override_master && (di.kind != DomainInfo::Slave || di.masters.empty()))
     return "Domain '"+domain.toString()+"' is not a slave domain (or has no master defined)";
 
-  random_shuffle(di.masters.begin(), di.masters.end());
-  Communicator.addSuckRequest(domain, di.masters.front()); 
-  return "Added retrieval request for '"+domain.toString()+"' from master "+di.masters.front().toLogString();
+  shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
+  const auto& master = di.masters.front();
+  Communicator.addSuckRequest(domain, master, override_master);
+  g_log<<Logger::Warning<<"Retrieval request for domain '"<<domain<<"' from master '"<<master<<"' received from operator"<<endl;
+  return "Added retrieval request for '"+domain.toLogString()+"' from master "+master.toLogString();
 }
 
 string DLNotifyHostHandler(const vector<string>&parts, Utility::pid_t ppid)
index 774ae5c4bac23e05516db4062d73bf266a91a453..6ac9d982ead8d25c6150d29072bce94938514743 100644 (file)
@@ -54,4 +54,3 @@ string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid);
 string DLCurrentConfigHandler(const vector<string>&parts, Utility::pid_t ppid);
 string DLListZones(const vector<string>&parts, Utility::pid_t ppid);
 string DLTokenLogin(const vector<string>&parts, Utility::pid_t ppid);
-uint64_t udpErrorStats(const std::string& str);
index 2deb2182b928a970e842e1b917cbd330792b03c3..ab10d114dadc0adb4ced1f138a6e11e8cc4364c0 100644 (file)
@@ -28,7 +28,6 @@
 #include <sys/types.h>
 #include <sys/un.h>
 #include <dlfcn.h>
-#include <pthread.h>
 #include <unistd.h>
 #include <boost/algorithm/string.hpp>
 
@@ -43,6 +42,8 @@
 #include <fcntl.h>
 #include <unistd.h>
 #include <boost/algorithm/string.hpp> 
+#include <thread>
+
 #include "misc.hh"
 #include "dns.hh"
 #include "arguments.hh"
@@ -203,16 +204,8 @@ DynListener::DynListener(const string &progname)
 void DynListener::go()
 {
   d_ppid=getpid();
-  pthread_create(&d_tid,0,&DynListener::theListenerHelper,this);
-}
-
-void *DynListener::theListenerHelper(void *p)
-{
-  setThreadName("pdns/ctrlListen");
-  DynListener *us=static_cast<DynListener *>(p);
-  us->theListener();
-  g_log<<Logger::Error<<"Control listener aborted, please file a bug!"<<endl;
-  return 0;
+  std::thread listener(std::bind(&DynListener::theListener,this));
+  listener.detach();
 }
 
 string DynListener::getLine()
@@ -331,6 +324,8 @@ void DynListener::registerRestFunc(g_funk_t *gf)
 
 void DynListener::theListener()
 {
+  setThreadName("pdns/ctrlListen");
+
   try {
     signal(SIGPIPE,SIG_IGN);
 
index da61661ccb4c8bbfa8852137b26383211e05bff4..cbe45c47b53ab1f03d6a69a7dcc5216597bd78b2 100644 (file)
@@ -22,7 +22,6 @@
 #pragma once
 #include <string>
 #include <vector>
-#include <pthread.h>
 #include <sys/types.h>
 #include <errno.h>
 #include <iostream>
@@ -45,7 +44,6 @@ public:
   ~DynListener();
   void go();
   void theListener();
-  static void *theListenerHelper(void *p);
 
   typedef string g_funk_t(const vector<string> &parts, Utility::pid_t ppid); // guido!
   typedef struct { g_funk_t *func; string args; string usage; } g_funkwithusage_t;
@@ -66,7 +64,6 @@ private:
   NetmaskGroup d_tcprange;
   int d_s{-1};
   int d_client{-1};
-  pthread_t d_tid{0};
   bool d_nonlocal;
   bool d_tcp{false};
   pid_t d_ppid{0};
index d20755be732723363bcbd1dfe4f61cabe08f03ef..ecfba3f867d5485b521c692d936c08f2c50b5786 100644 (file)
 #include "ednsoptions.hh"
 #include "iputils.hh"
 
+bool getNextEDNSOption(const char* data, size_t dataLen, uint16_t& optionCode, uint16_t& optionLen)
+{
+  if (data == nullptr || dataLen < (sizeof(uint16_t) + sizeof(uint16_t))) {
+    return false;
+  }
+
+  size_t pos = 0;
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+
+  optionCode = (static_cast<uint16_t>(p[pos]) * 256) + p[pos + 1];
+  pos += EDNS_OPTION_CODE_SIZE;
+
+  optionLen = (static_cast<uint16_t>(p[pos]) * 256) + p[pos + 1];
+  pos += EDNS_OPTION_LENGTH_SIZE;
+
+  return true;
+}
+
 /* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
 int getEDNSOption(char* optRR, const size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize)
 {
@@ -42,14 +60,20 @@ int getEDNSOption(char* optRR, const size_t len, uint16_t wantedOption, char **
 
   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]);
+    uint16_t optionCode;
+    uint16_t optionLen;
+    if (!getNextEDNSOption(optRR + pos, len-pos, optionCode, optionLen)) {
+      break;
+    }
+
     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))
+
+    if (optionLen > (rdLen - rdPos) || optionLen > (len - pos)) {
       return EINVAL;
+    }
 
     if (optionCode == wantedOption) {
       *optionValue = optRR + pos - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE);
index cbd7b0d0a0209b92014a2c63bdef9d7215f19152..a8f0a87903dd4fe5a0be279135d5c87f68488c11 100644 (file)
@@ -47,5 +47,7 @@ typedef std::map<uint16_t, EDNSOptionView> EDNSOptionViewMap;
 int getEDNSOptions(const char* optRR, size_t len, EDNSOptionViewMap& options);
 /* extract all EDNS0 options from the content (so after rdLen) of the OPT RR */
 bool getEDNSOptionsFromContent(const std::string& content, std::vector<std::pair<uint16_t, std::string>>& options);
+/* parse the next EDNS option and the return the code and length. data should point to the beginning of the option code, dataLen should be maximum length of the data (minimum of remaining size in packet and remaining size in rdata) */
+bool getNextEDNSOption(const char* data, size_t dataLen, uint16_t& optionCode, uint16_t& optionLen);
 
 void generateEDNSOption(uint16_t optionCode, const std::string& payload, std::string& res);
index 95f7b70d64b73a40f0b111e694056be80faadbc3..b5f66d8f6a60e0ff768469f158d393f070ae8e84 100644 (file)
 #include "namespaces.hh"
 #include "dnsrecords.hh"
 
+// Names below are RPZ Actions and end with a dot (except "Local Data")
+static const std::string rpzDropName("rpz-drop."),
+  rpzTruncateName("rpz-tcp-only."),
+  rpzNoActionName("rpz-passthru."),
+  rpzCustomName("Local Data");
+
+// Names below are (part) of RPZ Trigger names and do NOT end with a dot
+static const std::string rpzClientIPName("rpz-client-ip"),
+  rpzIPName("rpz-ip"),
+  rpzNSDnameName("rpz-nsdname"),
+  rpzNSIPName("rpz-nsip");
+
 DNSFilterEngine::DNSFilterEngine()
 {
 }
@@ -94,6 +106,8 @@ bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DN
     iter = polmap.find(g_wildcarddnsname+s);
     if(iter != polmap.end()) {
       pol=iter->second;
+      pol.d_trigger = iter->first;
+      pol.d_hit = qname.toStringNoDot();
       return true;
     }
   }
@@ -109,6 +123,8 @@ bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSNam
   const auto& it = polmap.find(qname);
   if (it != polmap.end()) {
     pol = it->second;
+    pol.d_trigger = qname;
+    pol.d_hit = qname.toStringNoDot();
     return true;
   }
 
@@ -123,11 +139,11 @@ bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unord
   bool allEmpty = true;
   for (const auto& z : d_zones) {
     bool enabled = true;
-    const auto zoneName = z->getName();
-    if (z->getPriority() >= pol.d_priority) {
+    const auto& zoneName = z->getName();
+    if (z->getPriority() >= pol.getPriority()) {
       enabled = false;
     }
-    else if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+    else if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
       enabled = false;
     }
     else {
@@ -163,12 +179,18 @@ bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unord
     }
     if (z->findExactNSPolicy(qname, pol)) {
       // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+      pol.d_trigger = qname;
+      pol.d_trigger.appendRawLabel(rpzNSDnameName);
+      pol.d_hit = qname.toStringNoDot();
       return true;
     }
 
     for (const auto& wc : wcNames) {
       if (z->findExactNSPolicy(wc, pol)) {
         // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+        pol.d_trigger = wc;
+        pol.d_trigger.appendRawLabel(rpzNSDnameName);
+        pol.d_hit = qname.toStringNoDot();
         return true;
       }
     }
@@ -182,39 +204,63 @@ bool DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std
 {
   //  cout<<"Got question for nameserver IP "<<address.toString()<<endl;
   for(const auto& z : d_zones) {
-    if (z->getPriority() >= pol.d_priority) {
+    if (z->getPriority() >= pol.getPriority()) {
       break;
     }
-    const auto zoneName = z->getName();
-    if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+    const auto& zoneName = z->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
       continue;
     }
 
     if(z->findNSIPPolicy(address, pol)) {
       //      cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
+      // XXX should use ns RPZ
+      pol.d_trigger = Zone::maskToRPZ(address);
+      pol.d_trigger.appendRawLabel(rpzNSIPName);
+      pol.d_hit = address.toString();
+      return true;
+    }
+  }
+  return false;
+}
+
+bool DNSFilterEngine::getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
+{
+  // cout<<"Got question from "<<ca.toString()<<endl;
+  for (const auto& z : d_zones) {
+    if (z->getPriority() >= pol.getPriority()) {
+      break;
+    }
+    const auto& zoneName = z->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      continue;
+    }
+
+    if (z->findClientPolicy(ca, pol)) {
+      // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
       return true;
     }
   }
   return false;
 }
 
-bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
+bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
 {
-  // cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
+  //cerr<<"Got question for "<<qname<<' '<< pol.getPriority()<< endl;
   std::vector<bool> zoneEnabled(d_zones.size());
   size_t count = 0;
   bool allEmpty = true;
   for (const auto& z : d_zones) {
     bool enabled = true;
-    if (z->getPriority() >= pol.d_priority) {
+    if (z->getPriority() >= pol.getPriority()) {
       enabled = false;
     } else {
-      const auto zoneName = z->getName();
-      if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+      const auto& zoneName = z->getName();
+      if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
         enabled = false;
       }
       else {
-        if (z->hasQNamePolicies() || z->hasClientPolicies()) {
+        if (z->hasQNamePolicies()) {
           allEmpty = false;
         }
         else {
@@ -246,19 +292,18 @@ bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& c
       continue;
     }
 
-    if (z->findClientPolicy(ca, pol)) {
-      // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
-      return true;
-    }
-
     if (z->findExactQNamePolicy(qname, pol)) {
       // cerr<<"Had a hit on the name of the query"<<endl;
+      pol.d_trigger = qname;
+      pol.d_hit = qname.toStringNoDot();
       return true;
     }
 
     for (const auto& wc : wcNames) {
       if (z->findExactQNamePolicy(wc, pol)) {
         // cerr<<"Had a hit on the name of the query"<<endl;
+        pol.d_trigger = wc;
+        pol.d_hit = qname.toStringNoDot();
         return true;
       }
     }
@@ -270,38 +315,54 @@ bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& c
 }
 
 bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
+{
+  for (const auto& record : records) {
+    if (getPostPolicy(record, discardedPolicies, pol)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
 {
   ComboAddress ca;
-  for (const auto& r : records) {
-    if (r.d_place != DNSResourceRecord::ANSWER)
-      continue;
-    if (r.d_type == QType::A) {
-      if (auto rec = getRR<ARecordContent>(r)) {
-        ca = rec->getCA();
-      }
+  if (record.d_place != DNSResourceRecord::ANSWER) {
+    return false;
+  }
+
+  if (record.d_type == QType::A) {
+    if (auto rec = getRR<ARecordContent>(record)) {
+      ca = rec->getCA();
     }
-    else if(r.d_type == QType::AAAA) {
-      if (auto rec = getRR<AAAARecordContent>(r)) {
-        ca = rec->getCA();
-      }
+  }
+  else if(record.d_type == QType::AAAA) {
+    if (auto rec = getRR<AAAARecordContent>(record)) {
+      ca = rec->getCA();
     }
-    else
-      continue;
+  }
+  else {
+    return false;
+  }
 
-    for (const auto& z : d_zones) {
-      if (z->getPriority() >= pol.d_priority) {
-        break;
-      }
-      const auto zoneName = z->getName();
-      if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
-        continue;
-      }
+  for (const auto& z : d_zones) {
+    if (z->getPriority() >= pol.getPriority()) {
+      break;
+    }
+    const auto& zoneName = z->getName();
+    if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+      return false;
+    }
 
-      if (z->findResponsePolicy(ca, pol)) {
-       return true;
-      }
+    if (z->findResponsePolicy(ca, pol)) {
+      pol.d_trigger = Zone::maskToRPZ(ca);
+      pol.d_trigger.appendRawLabel(rpzIPName);
+      pol.d_hit = ca.toString();
+      return true;
     }
   }
+
   return false;
 }
 
@@ -311,33 +372,19 @@ void DNSFilterEngine::assureZones(size_t zone)
     d_zones.resize(zone+1);
 }
 
-void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol)
+void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
 {
-  pol.d_name = d_name;
-  pol.d_type = PolicyType::ClientIP;
-  d_qpolAddr.insert(nm).second=std::move(pol);
-}
+  auto it = map.find(n);
 
-void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol)
-{
-  pol.d_name = d_name;
-  pol.d_type = PolicyType::ResponseIP;
-  d_postpolAddr.insert(nm).second=std::move(pol);
-}
-
-void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
-{
-  auto it = d_qpolName.find(n);
-
-  if (it != d_qpolName.end()) {
+  if (it != map.end()) {
     auto& existingPol = it->second;
 
     if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
-      throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following QName: " + n.toLogString());
+      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following name: " + n.toLogString());
     }
 
     if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
-      throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following QName: " + n.toLogString());
+      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following name: " + n.toLogString());
     }
 
     existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
@@ -345,58 +392,93 @@ void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool
     std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
   }
   else {
-    auto& qpol = d_qpolName.insert({n, std::move(pol)}).first->second;
-    qpol.d_name = d_name;
-    qpol.d_type = PolicyType::QName;
+    auto& qpol = map.insert({n, std::move(pol)}).first->second;
+    qpol.d_zoneData = d_zoneData;
+    qpol.d_type = ptype;
   }
 }
 
-void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol)
+void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
 {
-  pol.d_name = d_name;
-  pol.d_type = PolicyType::NSDName;
-  d_propolName.insert({n, std::move(pol)});
-}
+  bool exists = nmt.has_key(nm);
 
-void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol)
-{
-  pol.d_name = d_name;
-  pol.d_type = PolicyType::NSIP;
-  d_propolNSAddr.insert(nm).second = std::move(pol);
-}
+  if (exists) {
+    // XXX NetMaskTree's node_type has a non-const second, but lookup() returns a const node_type *, so we cannot modify second
+    // Should look into making lookup) return a non-const node_type *...
+    auto& existingPol = const_cast<Policy&>(nmt.lookup(nm)->second);
 
-bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
-{
-  d_qpolAddr.erase(nm);
-  return true;
-}
+    if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following netmask: " + nm.toString());
+    }
 
-bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
-{
-  d_postpolAddr.erase(nm);
-  return true;
+    if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
+      throw std::runtime_error("Adding a " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following netmask: " + nm.toString());
+    }
+
+    existingPol.d_custom.reserve(existingPol.d_custom.size() + pol.d_custom.size());
+
+    std::move(pol.d_custom.begin(), pol.d_custom.end(), std::back_inserter(existingPol.d_custom));
+  }
+  else {
+    pol.d_zoneData = d_zoneData;
+    pol.d_type = ptype;
+    nmt.insert(nm).second = std::move(pol);
+  }
 }
 
-bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, const Policy& pol)
 {
-  auto found = d_qpolName.find(n);
-  if (found == d_qpolName.end()) {
+  auto found = map.find(n);
+  if (found == map.end()) {
     return false;
   }
 
   auto& existing = found->second;
   if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
-    d_qpolName.erase(found);
+    map.erase(found);
     return true;
   }
 
   /* for custom types, we might have more than one type,
      and then we need to remove only the right ones. */
-  if (existing.d_custom.size() <= 1) {
-    d_qpolName.erase(found);
+  bool result = false;
+  for (auto& toRemove : pol.d_custom) {
+    for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
+      if (**it == *toRemove) {
+        existing.d_custom.erase(it);
+        result = true;
+        break;
+      }
+    }
+  }
+
+  // No records left for this trigger?
+  if (existing.d_custom.size() == 0) {
+    map.erase(found);
     return true;
   }
 
+  return result;
+}
+
+bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol)
+{
+  bool found = nmt.has_key(nm);
+  if (!found) {
+    return false;
+  }
+
+  // XXX NetMaskTree's node_type has a non-const second, but lookup() returns a const node_type *, so we cannot modify second
+  // Should look into making lookup) return a non-const node_type *...
+  auto& existing = const_cast<Policy&>(nmt.lookup(nm)->second);
+  if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
+    nmt.erase(nm);
+    return true;
+  }
+
+  /* for custom types, we might have more than one type,
+     and then we need to remove only the right ones. */
+
   bool result = false;
   for (auto& toRemove : pol.d_custom) {
     for (auto it = existing.d_custom.begin(); it != existing.d_custom.end(); ++it) {
@@ -408,19 +490,67 @@ bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
     }
   }
 
+  // No records left for this trigger?
+  if (existing.d_custom.size() == 0) {
+    nmt.erase(nm);
+    return true;
+  }
+
   return result;
 }
 
+void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+{
+  addNetmaskTrigger(d_qpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
+}
+
+void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+{
+  addNetmaskTrigger(d_postpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
+}
+
+void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
+{
+  addNameTrigger(d_qpolName, n, std::move(pol), ignoreDuplicate, PolicyType::QName);
+}
+
+void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
+{
+  addNameTrigger(d_propolName, n, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
+}
+
+void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+{
+  addNetmaskTrigger(d_propolNSAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
+}
+
+bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
+{
+  return rmNetmaskTrigger(d_qpolAddr, nm, pol);
+}
+
+bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
+{
+  return rmNetmaskTrigger(d_postpolAddr, nm, pol);
+}
+
+bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
+{
+  return rmNameTrigger(d_qpolName, n, pol);
+}
+
 bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, const Policy& pol)
 {
-  d_propolName.erase(n); // XXX verify policy matched? =pol;
-  return true;
+  return rmNameTrigger(d_propolName, n, pol);
 }
 
 bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, const Policy& pol)
 {
-  d_propolNSAddr.erase(nm);
-  return true;
+  return rmNetmaskTrigger(d_propolNSAddr, nm, pol);
+}
+
+std::string DNSFilterEngine::Policy::getLogString() const {
+  return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + d_trigger.toLogString() + "; Hit=" + d_hit + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
 }
 
 DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const
@@ -487,22 +617,21 @@ std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName&
 
 std::string DNSFilterEngine::getKindToString(DNSFilterEngine::PolicyKind kind)
 {
-  static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
-  static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
-    rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
-  static const std::string rpzPrefix("rpz-");
+  //static const std::string rpzPrefix("rpz-");
 
   switch(kind) {
   case DNSFilterEngine::PolicyKind::NoAction:
-    return noaction.toString();
+    return rpzNoActionName;
   case DNSFilterEngine::PolicyKind::Drop:
-    return drop.toString();
+    return rpzDropName;
   case DNSFilterEngine::PolicyKind::NXDOMAIN:
     return g_rootdnsname.toString();
   case PolicyKind::NODATA:
     return g_wildcarddnsname.toString();
   case DNSFilterEngine::PolicyKind::Truncate:
-    return truncate.toString();
+    return rpzTruncateName;
+  case DNSFilterEngine::PolicyKind::Custom:
+    return rpzCustomName;
   default:
     throw std::runtime_error("Unexpected DNSFilterEngine::Policy kind");
   }
@@ -623,18 +752,25 @@ void DNSFilterEngine::Zone::dump(FILE* fp) const
   }
 
   for (const auto& pair : d_propolName) {
-    dumpNamedPolicy(fp, pair.first + DNSName("rpz-nsdname.") + d_domain, pair.second);
+    dumpNamedPolicy(fp, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
   }
 
-  for (const auto pair : d_qpolAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName("rpz-client-ip.") + d_domain, pair.second);
+  for (const auto& pair : d_qpolAddr) {
+    dumpAddrPolicy(fp, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
   }
 
-  for (const auto pair : d_propolNSAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName("rpz-nsip.") + d_domain, pair.second);
+  for (const auto& pair : d_propolNSAddr) {
+    dumpAddrPolicy(fp, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
   }
 
-  for (const auto pair : d_postpolAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName("rpz-ip.") + d_domain, pair.second);
+  for (const auto& pair : d_postpolAddr) {
+    dumpAddrPolicy(fp, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
+  }
+}
+
+void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags)
+{
+  for (const auto& tag : newTags) {
+    tags.insert(tag);
   }
 }
index 43c7404ff16e27ce2a0873560592783fed4fc830..bc91ed9e4b3ae4d564c29f3ab744404c803b03b2 100644 (file)
@@ -74,13 +74,22 @@ public:
   static std::string getKindToString(PolicyKind kind);
   static std::string getTypeToString(PolicyType type);
 
+  struct PolicyZoneData
+  {
+    /* shared by all the policies from a single zone */
+    std::unordered_set<std::string> d_tags;
+    std::string d_name;
+    Priority d_priority{maximumPriority};
+    bool d_policyOverridesGettag{true};
+  };
+
   struct Policy
   {
-    Policy(): d_name(nullptr), d_ttl(0), d_priority(maximumPriority), d_kind(PolicyKind::NoAction), d_type(PolicyType::None)
+    Policy(): d_ttl(0), d_kind(PolicyKind::NoAction), d_type(PolicyType::None)
     {
     }
 
-    Policy(PolicyKind kind, PolicyType type, int32_t ttl=0, std::shared_ptr<std::string> name=nullptr, const std::vector<std::shared_ptr<DNSRecordContent>>& custom={}): d_custom(custom), d_name(name), d_ttl(ttl), d_priority(maximumPriority), d_kind(kind), d_type(type)
+    Policy(PolicyKind kind, PolicyType type, int32_t ttl=0, std::shared_ptr<PolicyZoneData> data=nullptr, const std::vector<std::shared_ptr<DNSRecordContent>>& custom={}): d_custom(custom), d_zoneData(data), d_ttl(ttl), d_kind(kind), d_type(type)
     {
     }
 
@@ -88,14 +97,71 @@ public:
     {
       return d_kind == rhs.d_kind && d_type == rhs.d_type && d_ttl == rhs.d_ttl && d_custom == rhs.d_custom;
     }
+
+    const std::string& getName() const
+    {
+      static const std::string notSet;
+      if (d_zoneData) {
+        return d_zoneData->d_name;
+      }
+      return notSet;
+    }
+
+    void setName(const std::string& name)
+    {
+      /* until now the PolicyZoneData was shared,
+         we now need to copy it, then write to it */
+      std::shared_ptr<PolicyZoneData> newZoneData;
+      if (d_zoneData) {
+        newZoneData = std::make_shared<PolicyZoneData>(*d_zoneData);
+      }
+      else {
+        newZoneData = std::make_shared<PolicyZoneData>();
+      }
+      newZoneData->d_name = name;
+      d_zoneData = newZoneData;
+    }
+
+    const std::unordered_set<std::string>& getTags() const
+    {
+      static const std::unordered_set<std::string> notSet;
+      if (d_zoneData) {
+        return d_zoneData->d_tags;
+      }
+      return notSet;
+    }
+
+    Priority getPriority() const
+    {
+      static Priority notSet = maximumPriority;
+      if (d_zoneData) {
+        return d_zoneData->d_priority;
+      }
+      return notSet;
+    }
+
+    bool policyOverridesGettag() const {
+      if (d_zoneData) {
+        return d_zoneData->d_policyOverridesGettag;
+      }
+      return true;
+    }
+
+    bool wasHit() const
+    {
+      return (d_type != DNSFilterEngine::PolicyType::None && d_kind != DNSFilterEngine::PolicyKind::NoAction);
+    }
+
+    std::string getLogString() const;
     std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
     std::vector<DNSRecord> getRecords(const DNSName& qname) const;
 
     std::vector<std::shared_ptr<DNSRecordContent>> d_custom;
-    std::shared_ptr<std::string> d_name; // the name of the policy
+    std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
+    DNSName d_trigger;
+    string d_hit;
     /* Yup, we are currently using the same TTL for every record for a given name */
     int32_t d_ttl;
-    Priority d_priority;
     PolicyKind d_kind;
     PolicyType d_type;
 
@@ -105,6 +171,10 @@ public:
 
   class Zone {
   public:
+    Zone(): d_zoneData(std::make_shared<PolicyZoneData>())
+    {
+    }
+
     void clear()
     {
       d_qpolAddr.clear();
@@ -119,7 +189,7 @@ public:
     }
     void setName(const std::string& name)
     {
-      d_name = std::make_shared<std::string>(name);
+      d_zoneData->d_name = name;
     }
     void setDomain(const DNSName& domain)
     {
@@ -133,9 +203,17 @@ public:
     {
       d_refresh = refresh;
     }
-    const std::shared_ptr<std::string> getName() const
+    void setTags(std::unordered_set<std::string>&& tags)
     {
-      return d_name;
+      d_zoneData->d_tags = std::move(tags);
+    }
+    void setPolicyOverridesGettag(bool flag)
+    {
+      d_zoneData->d_policyOverridesGettag = flag;
+    }
+    const std::string& getName() const
+    {
+      return d_zoneData->d_name;
     }
 
     DNSName getDomain() const
@@ -160,11 +238,11 @@ public:
 
     void dump(FILE * fp) const;
 
-    void addClientTrigger(const Netmask& nm, Policy&& pol);
-    void addQNameTrigger(const DNSName& nm, Policy&& pol, bool ignoreDuplicate=false);
-    void addNSTrigger(const DNSName& dn, Policy&& pol);
-    void addNSIPTrigger(const Netmask& nm, Policy&& pol);
-    void addResponseTrigger(const Netmask& nm, Policy&& pol);
+    void addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
+    void addQNameTrigger(const DNSName& nm, Policy&& pol, bool ignoreDuplicate = false);
+    void addNSTrigger(const DNSName& dn, Policy&& pol, bool ignoreDuplicate = false);
+    void addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
+    void addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate = false);
 
     bool rmClientTrigger(const Netmask& nm, const Policy& pol);
     bool rmQNameTrigger(const DNSName& nm, const Policy& pol);
@@ -199,28 +277,21 @@ public:
       return !d_postpolAddr.empty();
     }
     Priority getPriority() const {
-      return d_priority;
+      return d_zoneData->d_priority;
     }
     void setPriority(Priority p) {
-      d_priority = p;
-      for (auto& pair : d_qpolName) {
-        pair.second.d_priority = p;
-      }
-      for (auto& pair : d_propolName) {
-        pair.second.d_priority = p;
-      }
-      for (auto& pair : d_qpolAddr) {
-        pair.second.d_priority = p;
-      }
-      for (auto& pair : d_propolNSAddr) {
-        pair.second.d_priority = p;
-      }
-      for (auto& pair : d_postpolAddr) {
-        pair.second.d_priority = p;
-      }
+      d_zoneData->d_priority = p;
     }
-  private:
+    
     static DNSName maskToRPZ(const Netmask& nm);
+
+  private:
+    void addNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
+    void addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
+    bool rmNameTrigger(std::unordered_map<DNSName,Policy>& map, const DNSName& n, const Policy& pol);
+    bool rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol);
+
+  private:
     static bool findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
     static bool findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
     static void dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol);
@@ -232,10 +303,9 @@ public:
     NetmaskTree<Policy> d_propolNSAddr;     // NSIP (RPZ)
     NetmaskTree<Policy> d_postpolAddr;      // IP trigger (RPZ)
     DNSName d_domain;
-    std::shared_ptr<std::string> d_name;
+    std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
     uint32_t d_serial{0};
     uint32_t d_refresh{0};
-    Priority d_priority{0};
   };
 
   DNSFilterEngine();
@@ -259,9 +329,9 @@ public:
   }
   const std::shared_ptr<Zone> getZone(const std::string& name) const
   {
-    for (const auto zone : d_zones) {
+    for (const auto& zone : d_zones) {
       const auto& zName = zone->getName();
-      if (zName && *zName == name) {
+      if (zName == name) {
         return zone;
       }
     }
@@ -282,36 +352,50 @@ public:
     }
   }
 
-  bool getQueryPolicy(const DNSName& qname, const ComboAddress& nm, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+  bool getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+  bool getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
   bool getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
   bool getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
   bool getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+  bool getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
 
   // A few convenience methods for the unit test code
-  Policy getQueryPolicy(const DNSName& qname, const ComboAddress& nm, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
+  Policy getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
+    Policy policy;
+    policy.d_zoneData = std::make_shared<PolicyZoneData>();
+    policy.d_zoneData->d_priority = p;
+    getQueryPolicy(qname, discardedPolicies, policy);
+    return policy;
+  }
+
+  Policy getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
     Policy policy;
-    policy.d_priority = p;
-    getQueryPolicy(qname, nm, discardedPolicies, policy);
+    policy.d_zoneData = std::make_shared<PolicyZoneData>();
+    policy.d_zoneData->d_priority = p;
+    getClientPolicy(ca, discardedPolicies, policy);
     return policy;
   }
 
   Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
     Policy policy;
-    policy.d_priority = p;
+    policy.d_zoneData = std::make_shared<PolicyZoneData>();
+    policy.d_zoneData->d_priority = p;
     getProcessingPolicy(qname, discardedPolicies, policy);
     return policy;
   }
 
   Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
     Policy policy;
-    policy.d_priority = p;
+    policy.d_zoneData = std::make_shared<PolicyZoneData>();
+    policy.d_zoneData->d_priority = p;
     getProcessingPolicy(address, discardedPolicies, policy);
     return policy;
   }
 
   Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
     Policy policy;
-    policy.d_priority = p;
+    policy.d_zoneData = std::make_shared<PolicyZoneData>();
+    policy.d_zoneData->d_priority = p;
     getPostPolicy(records, discardedPolicies, policy);
     return policy;
   }
@@ -323,3 +407,5 @@ private:
   void assureZones(size_t zone);
   vector<std::shared_ptr<Zone>> d_zones;
 };
+
+void mergePolicyTags(std::unordered_set<std::string>& tags, const std::unordered_set<std::string>& newTags);
index 8b2ed7d1fefbc438221bd146256b260d94a8da4d..79812ae4c73bba7bd66b2abdf99ee5ceb1fb14b9 100644 (file)
@@ -22,6 +22,8 @@
 
 #include "dnsdist-cache.hh"
 
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
 
   if (size > std::numeric_limits<uint16_t>::max()) {
@@ -29,12 +31,21 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   }
 
   /* dnsdist's version */
+  DNSDistPacketCache pcSkipCookies(10000);
+  pcSkipCookies.setECSParsingEnabled(true);
+  pcSkipCookies.setCookieHashing(false);
+
+  DNSDistPacketCache pcHashCookies(10000);
+  pcHashCookies.setECSParsingEnabled(true);
+  pcHashCookies.setCookieHashing(true);
+
   try {
     uint16_t qtype;
     uint16_t qclass;
     unsigned int consumed;
-    DNSName qname(reinterpret_cast<const char*>(data), size, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSDistPacketCache::getKey(qname.toString(), consumed, data, size, false);
+    const DNSName qname(reinterpret_cast<const char*>(data), size, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    pcSkipCookies.getKey(qname.getStorage(), consumed, data, size, false);
+    pcHashCookies.getKey(qname.getStorage(), consumed, data, size, false);
     boost::optional<Netmask> subnet;
     DNSDistPacketCache::getClientSubnet(reinterpret_cast<const char*>(data), consumed, size, subnet);
   }
index ea5fe25e9f7491f54c8872a013c27585b5837f26..2605ea82047efcb4abf3c630d32fb89080667641 100644 (file)
@@ -31,6 +31,8 @@ static void init()
   reportAllTypes();
 }
 
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   static bool initialized = false;
 
index faea0169205cf99f4225f1a58f9200740550b1de..98f99d372fbf4e67fab7b001683224936841305e 100644 (file)
 
 StatBag S;
 
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
 
-  if (size > std::numeric_limits<uint16_t>::max()) {
+  if (size > std::numeric_limits<uint16_t>::max() || size < sizeof(dnsheader)) {
     return 0;
   }
 
@@ -35,7 +37,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
 
   /* auth's version */
   try {
-    PacketCache::canHashPacket(input);
+    static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE };
+
+    PacketCache::canHashPacket(input, false);
+    DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
+    PacketCache::queryMatches(input, input, qname, optionsToIgnore);
   }
   catch(const std::exception& e) {
   }
@@ -44,9 +50,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
 
   /* recursor's version */
   try {
-    uint16_t ecsBegin = 0;
-    uint16_t ecsEnd = 0;
-    PacketCache::canHashPacket(input, &ecsBegin, &ecsEnd);
+    static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
+
+    PacketCache::canHashPacket(input, true);
+    DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
+    PacketCache::queryMatches(input, input, qname, optionsToIgnore);
   }
   catch(const std::exception& e) {
   }
diff --git a/pdns/fuzz_proxyprotocol.cc b/pdns/fuzz_proxyprotocol.cc
new file mode 100644 (file)
index 0000000..25885c8
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 "proxy-protocol.hh"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+
+  std::vector<ProxyProtocolValue> values;
+  ComboAddress source;
+  ComboAddress destination;
+  bool proxy = false;
+  bool tcp = false;  
+
+  try {
+    parseProxyHeader(std::string(reinterpret_cast<const char*>(data), size), proxy, source, destination, tcp, values);
+  }
+  catch(const std::exception& e) {
+  }
+  catch(const PDNSException& e) {
+  }
+
+  return 0;
+}
index 442555b12200d17b07640c0e22608ba589fe9c5f..3285860a9d1669b284ff95a25e50a2c8d404afdb 100644 (file)
@@ -33,6 +33,8 @@ static void init()
   reportAllTypes();
 }
 
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   static bool initialized = false;
 
diff --git a/pdns/gss_context.cc b/pdns/gss_context.cc
deleted file mode 100644 (file)
index 3c2ed29..0000000
+++ /dev/null
@@ -1,490 +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 <map>
-#include <string>
-#include "namespaces.hh"
-#include "dns.hh"
-#include "dnsparser.hh"
-#include "dnspacket.hh"
-#include "dnsrecords.hh"
-#include "logger.hh"
-#include "lock.hh"
-#include "arguments.hh"
-
-#include <boost/shared_ptr.hpp>
-#include <boost/make_shared.hpp>
-#include "gss_context.hh"
-
-#ifndef ENABLE_GSS_TSIG
-
-bool GssContext::supported() { return false; }
-GssContext::GssContext() : d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
-GssContext::GssContext(const DNSName& label) : d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
-void GssContext::setLocalPrincipal(const std::string& name) {}
-bool GssContext::getLocalPrincipal(std::string& name) { return false; }
-void GssContext::setPeerPrincipal(const std::string& name) {}
-bool GssContext::getPeerPrincipal(std::string& name) { return false; }
-void GssContext::generateLabel(const std::string& suffix) {}
-void GssContext::setLabel(const DNSName& label) {}
-bool GssContext::init(const std::string &input, std::string& output) { return false; }
-bool GssContext::accept(const std::string &input, std::string& output) { return false; }
-bool GssContext::destroy() { return false; }
-bool GssContext::expired() { return false; }
-bool GssContext::valid() { return false; }
-bool GssContext::sign(const std::string &input, std::string& output) { return false; }
-bool GssContext::verify(const std::string &input, const std::string &signature) { return false; }
-GssContextError GssContext::getError() { return GSS_CONTEXT_UNSUPPORTED; }
-
-#else
-
-class GssCredential : boost::noncopyable {
-public:
-  GssCredential(const std::string& name, const gss_cred_usage_t usage) :
-    d_valid(false), d_nameS(name), d_name(GSS_C_NO_NAME), d_cred(GSS_C_NO_CREDENTIAL), d_usage(usage) {
-    gss_buffer_desc buffer;
-    
-    if (name.empty() == false) {
-      buffer.length = name.size();
-      buffer.value = (void*)name.c_str();
-      d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
-      if (d_maj != GSS_S_COMPLETE) {
-        d_valid = false;
-        return;       
-      }
-    }
-
-    renew();
-  };
-
-  ~GssCredential() {
-    OM_uint32 tmp_maj  __attribute__((unused)), tmp_min __attribute__((unused));
-    if (d_cred != GSS_C_NO_CREDENTIAL) 
-      tmp_maj = gss_release_cred(&tmp_min, &d_cred);
-    if (d_name != GSS_C_NO_NAME) 
-      tmp_maj = gss_release_name(&tmp_min, &d_name);
-  };
-
-  bool expired() const {
-    if (d_expires == -1) return false;
-    return time((time_t*)NULL)>d_expires;
-  }
-
-  bool renew() {
-    OM_uint32 time_rec, tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
-    d_maj = gss_acquire_cred(&d_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, NULL, &time_rec);
-
-    if (d_maj != GSS_S_COMPLETE) {
-      d_valid = false;
-      tmp_maj = gss_release_name(&tmp_min, &d_name);
-      d_name = GSS_C_NO_NAME;
-      return false;
-    }
-
-    d_valid = true;
-
-    if (time_rec > GSS_C_INDEFINITE) {
-      d_expires = time((time_t*)NULL)+time_rec;
-    } else {
-      d_expires = -1;
-    }
-
-    return true;
-  }
-
-  bool valid() {
-    return d_valid && !expired();
-  }
-
-  OM_uint32 d_maj,d_min;
-
-  bool d_valid;
-  int64_t d_expires;
-  std::string d_nameS;
-  gss_name_t d_name;
-  gss_cred_id_t d_cred;
-  gss_cred_usage_t d_usage;
-};
-
-std::map<std::string, boost::shared_ptr<GssCredential> > s_gss_accept_creds;
-std::map<std::string, boost::shared_ptr<GssCredential> > s_gss_init_creds;
-
-class GssSecContext : boost::noncopyable {
-public:
-  GssSecContext(boost::shared_ptr<GssCredential> cred) {
-    if (cred->valid() == false) throw PDNSException("Invalid credential " + cred->d_nameS);
-    d_cred = cred;
-    d_state = GssStateInitial;
-    d_ctx = GSS_C_NO_CONTEXT;
-    d_expires = 0;
-    d_maj = d_min = 0;
-    d_peer_name = GSS_C_NO_NAME;
-    d_type = GSS_CONTEXT_NONE;
-  }
-
-  ~GssSecContext() {
-    OM_uint32 tmp_maj  __attribute__((unused)), tmp_min __attribute__((unused));
-    if (d_ctx != GSS_C_NO_CONTEXT) {
-      tmp_maj = gss_delete_sec_context(&tmp_min, &d_ctx, GSS_C_NO_BUFFER);
-    }
-    if (d_peer_name != GSS_C_NO_NAME) {
-      tmp_maj = gss_release_name(&tmp_min, &(d_peer_name));
-    }
-  }
-
-  GssContextType d_type;
-  gss_ctx_id_t d_ctx;
-  gss_name_t d_peer_name;
-  int64_t d_expires;
-  boost::shared_ptr<GssCredential> d_cred;
-  OM_uint32 d_maj,d_min;
-
-  enum {
-    GssStateInitial,
-    GssStateNegotiate,
-    GssStateComplete,
-    GssStateError
-  } d_state;
-
-};
-
-std::map<DNSName, boost::shared_ptr<GssSecContext> > s_gss_sec_context;
-
-bool GssContext::supported() { return true; }
-
-void GssContext::initialize() {
-  d_peerPrincipal = "";
-  d_localPrincipal = "";
-  d_error = GSS_CONTEXT_NO_ERROR;
-  d_type = GSS_CONTEXT_NONE;
-} 
-
-GssContext::GssContext() {
-  initialize();
-  generateLabel("pdns.tsig.");
-}
-
-GssContext::GssContext(const DNSName& label) {
-  initialize();
-  setLabel(label);
-}
-
-void GssContext::generateLabel(const std::string& suffix) {
-  std::ostringstream oss;
-  oss << std::hex << time((time_t*)NULL) << "." << suffix;
-  setLabel(DNSName(oss.str()));
-}
-
-void GssContext::setLabel(const DNSName& label) {
-  d_label = label;
-  if (s_gss_sec_context.find(d_label) != s_gss_sec_context.end()) {
-    d_ctx = s_gss_sec_context[d_label];
-    d_type = d_ctx->d_type;
-  }
-}
-
-bool GssContext::expired() {
-  return (!d_ctx || (d_ctx->d_expires > -1 && d_ctx->d_expires < time((time_t*)NULL)));
-}
-
-bool GssContext::valid() {
-  return (d_ctx && !expired() && d_ctx->d_state == GssSecContext::GssStateComplete);
-}
-
-bool GssContext::init(const std::string &input, std::string& output) {
-  OM_uint32 tmp_maj  __attribute__((unused)), tmp_min __attribute__((unused));
-  OM_uint32 maj,min;
-  gss_buffer_desc recv_tok, send_tok, buffer;
-  OM_uint32 flags;
-  OM_uint32 expires;
-
-  boost::shared_ptr<GssCredential> cred;
-  if (d_label.empty()) {
-    d_error = GSS_CONTEXT_INVALID;
-    return false;
-  }
-
-  d_type = GSS_CONTEXT_INIT;
-
-  if (s_gss_init_creds.find(d_localPrincipal) != s_gss_init_creds.end()) {
-    cred = s_gss_init_creds[d_localPrincipal];
-  } else {
-    s_gss_init_creds[d_localPrincipal] = boost::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE);
-    cred = s_gss_init_creds[d_localPrincipal];
-  }
-
-  // see if we can find a context in non-completed state
-  if (d_ctx) {
-    if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
-      d_error = GSS_CONTEXT_INVALID;
-      return false;
-    }
-  } else {
-    // make context
-    s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred);
-    s_gss_sec_context[d_label]->d_type = d_type;
-    d_ctx = s_gss_sec_context[d_label];
-    d_ctx->d_state = GssSecContext::GssStateNegotiate;
-  }
-
-  recv_tok.length = input.size();
-  recv_tok.value = (void*)input.c_str();
-
-  if (d_peerPrincipal.empty() == false) {
-    buffer.value = (void*)d_peerPrincipal.c_str();
-    buffer.length = d_peerPrincipal.size();
-    maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_ctx->d_peer_name));
-    if (maj != GSS_S_COMPLETE) {
-      processError("gss_import_name", maj, min);
-      return false;
-    }
-  }
-
-  maj = gss_init_sec_context(&min, cred->d_cred, &(d_ctx->d_ctx), d_ctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, NULL, &send_tok, &flags, &expires);
-
-  if (send_tok.length>0) {
-    output.assign((const char*)send_tok.value, send_tok.length);
-    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
-  }
-
-  if (maj == GSS_S_COMPLETE) {
-    if (expires > GSS_C_INDEFINITE) {
-      d_ctx->d_expires = time((time_t*)NULL) + expires;
-    } else {
-      d_ctx->d_expires = -1;
-    }
-    d_ctx->d_state = GssSecContext::GssStateComplete;
-    return true;
-  } else if (maj != GSS_S_CONTINUE_NEEDED) {
-    processError("gss_init_sec_context", maj,min);
-  }
-
-  return (maj == GSS_S_CONTINUE_NEEDED);
-}
-
-bool GssContext::accept(const std::string &input, std::string& output) {
-  OM_uint32 tmp_maj  __attribute__((unused)), tmp_min __attribute__((unused));
-  OM_uint32 maj,min;
-  gss_buffer_desc recv_tok, send_tok;
-  OM_uint32 flags;
-  OM_uint32 expires;
-
-  boost::shared_ptr<GssCredential> cred;
-  if (d_label.empty()) {
-    d_error = GSS_CONTEXT_INVALID;
-    return false;
-  }
-
-  d_type = GSS_CONTEXT_ACCEPT;
-
-  if (s_gss_accept_creds.find(d_localPrincipal) != s_gss_accept_creds.end()) {
-    cred = s_gss_accept_creds[d_localPrincipal];
-  } else {
-    s_gss_accept_creds[d_localPrincipal] = boost::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT);
-    cred = s_gss_accept_creds[d_localPrincipal];
-  }
-
-  // see if we can find a context in non-completed state
-  if (d_ctx) {
-    if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
-      d_error = GSS_CONTEXT_INVALID;
-      return false;
-    } 
-  } else {
-    // make context
-    s_gss_sec_context[d_label] = boost::make_shared<GssSecContext>(cred);
-    s_gss_sec_context[d_label]->d_type = d_type;
-    d_ctx = s_gss_sec_context[d_label];
-    d_ctx->d_state = GssSecContext::GssStateNegotiate;
-  }
-
-  recv_tok.length = input.size();
-  recv_tok.value = (void*)input.c_str();
-
-  maj = gss_accept_sec_context(&min, &(d_ctx->d_ctx), cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &(d_ctx->d_peer_name), NULL, &send_tok, &flags, &expires, NULL);
-
-  if (send_tok.length>0) {
-    output.assign((const char*)send_tok.value, send_tok.length);
-    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
-  }
-
-  if (maj == GSS_S_COMPLETE) {
-    if (expires > GSS_C_INDEFINITE) {
-      d_ctx->d_expires = time((time_t*)NULL) + expires;
-    } else {
-      d_ctx->d_expires = -1;
-    }
-    d_ctx->d_state = GssSecContext::GssStateComplete;
-    return true;
-  } else if (maj != GSS_S_CONTINUE_NEEDED) {
-    processError("gss_accept_sec_context", maj,min);
-  }
-  return (maj == GSS_S_CONTINUE_NEEDED);
-};
-
-bool GssContext::sign(const std::string& input, std::string& output) {
-  OM_uint32 tmp_maj  __attribute__((unused)), tmp_min __attribute__((unused));
-  OM_uint32 maj,min;
-
-  gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
-  gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
-
-  recv_tok.length = input.size();
-  recv_tok.value = (void*)input.c_str();
-
-  maj = gss_get_mic(&min, d_ctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
-
-  if (send_tok.length>0) {
-    output.assign((const char*)send_tok.value, send_tok.length);
-    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
-  }
-
-  if (maj != GSS_S_COMPLETE) {
-    processError("gss_get_mic", maj,min);
-  }
-
-  return (maj == GSS_S_COMPLETE);
-}
-
-bool GssContext::verify(const std::string& input, const std::string& signature) {
-  OM_uint32 maj,min;
-
-  gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
-  gss_buffer_desc sign_tok = GSS_C_EMPTY_BUFFER;
-
-  recv_tok.length = input.size();
-  recv_tok.value = (void*)input.c_str();
-  sign_tok.length = signature.size();
-  sign_tok.value = (void*)signature.c_str();
-
-  maj = gss_verify_mic(&min, d_ctx->d_ctx, &recv_tok, &sign_tok, NULL);
-
-  if (maj != GSS_S_COMPLETE) {
-    processError("gss_get_mic", maj,min);
-  }
-
-  return (maj == GSS_S_COMPLETE);
-}
-
-bool GssContext::destroy() {
-  return false;
-}
-
-void GssContext::setLocalPrincipal(const std::string& name) {
-  d_localPrincipal = name;
-}
-
-bool GssContext::getLocalPrincipal(std::string& name) {
-  name = d_localPrincipal;
-  return name.size()>0;
-}
-
-void GssContext::setPeerPrincipal(const std::string& name) {
-  d_peerPrincipal = name;
-}
-
-bool GssContext::getPeerPrincipal(std::string& name) {
-  gss_buffer_desc value;
-  OM_uint32 maj,min;
-
-  if (d_ctx->d_peer_name != GSS_C_NO_NAME) {
-    maj = gss_display_name(&min, d_ctx->d_peer_name, &value, NULL);
-    if (maj == GSS_S_COMPLETE && value.length > 0) {
-      name.assign((const char*)value.value, value.length);
-      maj = gss_release_buffer(&min, &value);
-      return true;
-    } else {
-      return false;
-    }
-  } else { 
-    return false;
-  }
-}
-
-void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min) {
-   OM_uint32 tmp_min;
-   gss_buffer_desc msg;
-   OM_uint32 msg_ctx;
-
-   msg_ctx = 0;
-   while (1) {
-      ostringstream oss;
-      gss_display_status(&tmp_min, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
-      oss << method << ": " << (char*)msg.value;
-      d_gss_errors.push_back(oss.str());
-      if (!msg_ctx) break;
-   }
-   msg_ctx = 0;
-   while (1) {
-      ostringstream oss;
-      gss_display_status(&tmp_min, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
-      oss << method << ": " << (char*)msg.value;
-      d_gss_errors.push_back(oss.str());
-      if (!msg_ctx) break;
-   }
-}
-
-#endif
-
-bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac) {
-  string tmp_mac;
-  GssContext gssctx(context);
-  if (!gssctx.valid()) {
-    g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl;
-    for(const string& error :  gssctx.getErrorStrings()) {
-       g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
-    }
-    return false;
-  }
-
-  if (!gssctx.sign(message, tmp_mac)) {
-    g_log<<Logger::Error<<"Could not sign message using GSS context '"<<context<<"'"<<endl;
-    for(const string& error :  gssctx.getErrorStrings()) {
-       g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
-    }
-    return false;
-  }
-  mac = tmp_mac;
-  return true;
-}
-
-bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac) {
-  GssContext gssctx(context);
-  if (!gssctx.valid()) {
-    g_log<<Logger::Error<<"GSS context '"<<context<<"' is not valid"<<endl;
-    for(const string& error :  gssctx.getErrorStrings()) {
-       g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
-    }
-    return false;
-  }
-
-  if (!gssctx.verify(message, mac)) {
-    g_log<<Logger::Error<<"Could not verify message using GSS context '"<<context<<"'"<<endl;
-    for(const string& error :  gssctx.getErrorStrings()) {
-       g_log<<Logger::Error<<"GSS error: "<<error<<endl;;
-    }
-    return false;
-  }
-  return true;
-}
diff --git a/pdns/gss_context.hh b/pdns/gss_context.hh
deleted file mode 100644 (file)
index a5d3160..0000000
+++ /dev/null
@@ -1,180 +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.
- */
-#pragma once
-
-#ifdef ENABLE_GSS_TSIG
-#include <gssapi/gssapi.h>
-#include <gssapi/gssapi_krb5.h>
-#include <gssapi/gssapi_ext.h>
-#endif
-
-//! Generic errors
-enum GssContextError {
-  GSS_CONTEXT_NO_ERROR,
-  GSS_CONTEXT_UNSUPPORTED,
-  GSS_CONTEXT_NOT_FOUND,
-  GSS_CONTEXT_NOT_INITIALIZED,
-  GSS_CONTEXT_INVALID,
-  GSS_CONTEXT_EXPIRED,
-  GSS_CONTEXT_ALREADY_INITIALIZED
-};
-
-//! GSS context types
-enum GssContextType {
-  GSS_CONTEXT_NONE,
-  GSS_CONTEXT_INIT,
-  GSS_CONTEXT_ACCEPT
-};
-
-class GssSecContext;
-
-/*! Class for representing GSS names, such as host/host.domain.com@REALM.
-*/
-class GssName {
-public:
-  //! Initialize to empty name
-  GssName() {
-    setName("");
-  };
-
-  //! Initialize using specific name
-  GssName(const std::string& name) {
-    setName(name);
-  };
-
-  //! Parse name into native representation
-  bool setName(const std::string& name) {
-#ifdef ENABLE_GSS_TSIG
-    gss_buffer_desc buffer;
-    d_name = GSS_C_NO_NAME;
-
-    if (!name.empty()) {
-      buffer.length = name.size();
-      buffer.value = (void*)name.c_str();
-      d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
-      return d_maj == GSS_S_COMPLETE;
-    }
-
-    return true;
-#endif
-    return false;
-  };
-
-  ~GssName() {
-#ifdef ENABLE_GSS_TSIG
-     if (d_name != GSS_C_NO_NAME)
-       gss_release_name(&d_min, &d_name);
-#endif
-  };
-
-  //! Compare two Gss Names, if no gss support is compiled in, returns false always
-  //! This is not necessarily same as string comparison between two non-parsed names
-  bool operator==(const GssName& rhs) {
-#ifdef ENABLE_GSS_TSIG
-    OM_uint32 maj,min;
-    int result;
-    maj = gss_compare_name(&min, d_name, rhs.d_name, &result);
-    return (maj == GSS_S_COMPLETE && result != 0);
-#endif
-    return false;
-  }
-
-  //! Compare two Gss Names, if no gss support is compiled in, returns false always
-  //! This is not necessarily same as string comparison between two non-parsed names
-  bool match(const std::string& name) {
-#ifdef ENABLE_GSS_TSIG
-    OM_uint32 maj,min;
-    int result;
-    gss_name_t comp;
-    gss_buffer_desc buffer;
-    buffer.length = name.size();
-    buffer.value = (void*)name.c_str();
-    maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &comp);
-    if (maj != GSS_S_COMPLETE)
-      throw PDNSException("Could not import " + name + ": " + std::to_string(maj) + string(",") + std::to_string(min));
-    // do comparison
-    maj = gss_compare_name(&min, d_name, comp, &result);
-    gss_release_name(&min, &comp);
-    return (maj == GSS_S_COMPLETE && result != 0);
-#else
-   return false;
-#endif
-  };
-
-  //! Check if GSS name was parsed successfully.
-  bool valid() {
-#ifdef ENABLE_GSS_TSIG
-    return d_maj == GSS_S_COMPLETE;
-#else
-    return false;
-#endif
-  }
-private:
-#ifdef ENABLE_GSS_TSIG
-  OM_uint32 d_maj,d_min;
-  gss_name_t d_name;
-#endif
-};
-
-class GssContext {
-public:
-  static bool supported(); //<! Returns true if GSS is supported in the first place
-  GssContext(); //<! Construct new GSS context with random name
-  GssContext(const DNSName& label); //<! Create or open existing named context
-
-  void setLocalPrincipal(const std::string& name); //<! Set our gss name
-  bool getLocalPrincipal(std::string& name); //<! Get our name
-  void setPeerPrincipal(const std::string& name); //<! Set remote name (do not use after negotiation)
-  bool getPeerPrincipal(std::string &name); //<! Return remote name, returns actual name after negotiation
-
-  void generateLabel(const std::string& suffix); //<! Generate random context name using suffix (such as mydomain.com)
-  void setLabel(const DNSName& label); //<! Set context name to this label
-  const DNSName& getLabel() { return d_label; } //<! Return context name
-
-  bool init(const std::string &input, std::string& output); //<! Perform GSS Initiate Security Context handshake
-  bool accept(const std::string &input, std::string& output); //<! Perform GSS Accept Security Context handshake
-  bool destroy(); //<! Release the cached context
-  bool expired(); //<! Check if context is expired
-  bool valid(); //<! Check if context is valid
-
-  bool sign(const std::string &input, std::string& output); //<! Sign something using gss
-  bool verify(const std::string &input, const std::string &signature); //<! Validate gss signature with something
-
-  GssContextError getError(); //<! Get error
-  const std::vector<std::string> getErrorStrings() { return d_gss_errors; } //<! Get native error texts
- private:
-  void release(); //<! Release context
-  void initialize(); //<! Initialize context
-#ifdef ENABLE_GSS_TSIG
-  void processError(const string& method, OM_uint32 maj, OM_uint32 min); //<! Process and fill error text vector
-#endif
-  DNSName d_label; //<! Context name
-  std::string d_peerPrincipal; //<! Remote name
-  std::string d_localPrincipal; //<! Our name
-  GssContextError d_error; //<! Context error
-  GssContextType d_type; //<! Context type
-  std::vector<std::string> d_gss_errors; //<! Native error string(s)
-  boost::shared_ptr<GssSecContext> d_ctx; //<! Attached security context
-};
-
-bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac); //<! Create signature
-bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac); //<! Validate signature
index a13645cbd182c9e07d8da88d56506c66a063a050..624746db80375abc9769d3c0116c438c85380b5e 100644 (file)
@@ -158,6 +158,28 @@ void setSocketIgnorePMTU(int sockfd)
 #endif /* defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) */
 }
 
+bool setReusePort(int sockfd)
+{
+#if defined(SO_REUSEPORT_LB)
+  try {
+    SSetsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
+    return true;
+  }
+  catch (const std::exception& e) {
+    return false;
+  }
+#elif defined(SO_REUSEPORT)
+  try {
+    SSetsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, 1);
+    return true;
+  }
+  catch (const std::exception& e) {
+    return false;
+  }
+#endif
+  return false;
+}
+
 bool HarvestTimestamp(struct msghdr* msgh, struct timeval* tv) 
 {
 #ifdef SO_TIMESTAMP
index 2fb2805055597cbbd5686c4fbf70d4568a65c835..b8ea84fab3d599d8b3f47e2d183a634bac959c70 100644 (file)
@@ -339,9 +339,9 @@ union ComboAddress {
         index = 32 + index;
       }
 
-      uint32_t s_addr = ntohl(sin4.sin_addr.s_addr);
+      uint32_t ls_addr = ntohl(sin4.sin_addr.s_addr);
 
-      return ((s_addr & (1<<index)) != 0x00000000);
+      return ((ls_addr & (1<<index)) != 0x00000000);
     }
     if(isIPv6()) {
       if (index >= 128)
@@ -352,11 +352,11 @@ union ComboAddress {
         index = 128 + index;
       }
 
-      uint8_t *s_addr = (uint8_t*)sin6.sin6_addr.s6_addr;
+      uint8_t *ls_addr = (uint8_t*)sin6.sin6_addr.s6_addr;
       uint8_t byte_idx = index / 8;
       uint8_t bit_idx = index % 8;
 
-      return ((s_addr[15-byte_idx] & (1 << bit_idx)) != 0x00);
+      return ((ls_addr[15-byte_idx] & (1 << bit_idx)) != 0x00);
     }
     return false;
   }
@@ -989,7 +989,7 @@ public:
 
     // we turn left on 0 and right on 1
     int bits = 0;
-    for(; node && bits < key.getBits(); bits++) {
+    for(; bits < key.getBits(); bits++) {
       bool vall = key.getBit(-1-bits);
 
       if (bits >= node->d_bits) {
@@ -1390,6 +1390,7 @@ int SAccept(int sockfd, ComboAddress& remote);
 int SListen(int sockfd, int limit);
 int SSetsockopt(int sockfd, int level, int opname, int value);
 void setSocketIgnorePMTU(int sockfd);
+bool setReusePort(int sockfd);
 
 #if defined(IP_PKTINFO)
   #define GEN_IP_PKTINFO IP_PKTINFO
index 4c8b25b846b6bb876f88bb92ee8f6f1e0b1a009c..addfe060030e2b48e97280a478a98ec25a2bf9da 100644 (file)
@@ -96,3 +96,5 @@ class ixfrdistStats {
       return ret;
     };
 };
+
+extern string doGetStats();
index 485e720bcd8d2700be491a236f88feb1ed52b321..ebc456cedb371602cf12583e6dd8bfa7083ff3ca 100644 (file)
 #include "iputils.hh"
 #include "ixfrdist-stats.hh"
 
-string doGetStats();
-
 IXFRDistWebServer::IXFRDistWebServer(const ComboAddress &listenAddress, const NetmaskGroup &acl, const string &loglevel) :
   d_ws(std::unique_ptr<WebServer>(new WebServer(listenAddress.toString(), listenAddress.getPort())))
 {
   d_ws->setACL(acl);
   d_ws->setLogLevel(loglevel);
-  d_ws->registerWebHandler("/metrics", boost::bind(&IXFRDistWebServer::getMetrics, this, _1, _2));
+  d_ws->registerWebHandler("/metrics", std::bind(&IXFRDistWebServer::getMetrics, this, std::placeholders::_1, std::placeholders::_2));
   d_ws->bind();
 }
 
index 7d9a0eb0274c6c14cddc0c04c243cbf24fa8c254..bb48caad71aed3395d314f4d002bba3247c8baae 100644 (file)
@@ -36,7 +36,7 @@
 #include <condition_variable>
 #include "ixfr.hh"
 #include "ixfrutils.hh"
-#include "resolver.hh"
+#include "axfr-retriever.hh"
 #include "dns_random.hh"
 #include "sstuff.hh"
 #include "mplexer.hh"
@@ -271,7 +271,7 @@ static void updateCurrentZoneInfo(const DNSName& domain, std::shared_ptr<ixfrinf
   // FIXME: also report zone size?
 }
 
-void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) {
+static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) {
   setThreadName("ixfrdist/update");
   std::map<DNSName, time_t> lastCheck;
 
index a51070ec87d2dc324a4a1beb483e2ace3a3b46af..c2efb8ca857e7f5cce55e5365a677e434d0731fa 100644 (file)
@@ -12,13 +12,28 @@ ExecStart=@bindir@/ixfrdist
 Restart=on-failure
 RestartSec=1
 StartLimitInterval=0
-PrivateTmp=true
-PrivateDevices=true
+
+# Sandboxing
 CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID
+LockPersonality=true
 NoNewPrivileges=true
-ProtectSystem=full
+PrivateDevices=true
+PrivateTmp=true
+# Setting PrivateUsers=true prevents us from opening our sockets
+ProtectClock=true
+ProtectControlGroups=true
 ProtectHome=true
+ProtectHostname=true
+ProtectKernelLogs=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectSystem=full
 RestrictAddressFamilies=AF_INET AF_INET6
+RestrictNamespaces=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+SystemCallArchitectures=native
+SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
 
 [Install]
 WantedBy=multi-user.target
index feec7c33927eb86f217b07502a184c46f3bbb3a5..9d94c821508de0e384c14beff90d75a194fae5fa 100644 (file)
@@ -124,30 +124,31 @@ void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::s
   DNSRecord soa;
   auto serial = getSerialFromRecords(records, soa);
   string fname=directory +"/"+std::to_string(serial);
-  FILE* fp=fopen((fname+".partial").c_str(), "w");
-  if(!fp)
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen((fname+".partial").c_str(), "w"), fclose);
+  if (!fp) {
     throw runtime_error("Unable to open file '"+fname+".partial' for writing: "+stringerror());
+  }
 
   records_t soarecord;
   soarecord.insert(soa);
-  if(fprintf(fp, "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
+  if (fprintf(fp.get(), "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
     string error = "Error writing to zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
-    fclose(fp);
+    fp.reset();
     unlink((fname+".partial").c_str());
     throw std::runtime_error(error);
   }
 
   try {
-    writeRecords(fp, soarecord);
-    writeRecords(fp, records);
-    writeRecords(fp, soarecord);
+    writeRecords(fp.get(), soarecord);
+    writeRecords(fp.get(), records);
+    writeRecords(fp.get(), soarecord);
   } catch (runtime_error &e) {
-    fclose(fp);
+    fp.reset();
     unlink((fname+".partial").c_str());
     throw runtime_error("Error closing zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + e.what());
   }
 
-  if(fclose(fp) != 0) {
+  if (fclose(fp.release()) != 0) {
     string error = "Error closing zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
     unlink((fname+".partial").c_str());
     throw std::runtime_error(error);
index 9b525de1b00f925387376265a529d04865e6a2ef..3291e9e07324bece2fd5a97ba2d66acee2e819c2 100644 (file)
@@ -32,9 +32,8 @@
 #include "dnssecinfra.hh"
 
 #include "dns_random.hh"
-#include "gss_context.hh"
 #include <boost/multi_index_container.hpp>
-#include "resolver.hh"
+#include "axfr-retriever.hh"
 #include <fstream>
 #include "ixfr.hh"
 #include "ixfrutils.hh"
@@ -46,7 +45,7 @@ ArgvMap &arg()
   return theArg;
 }
 
-void usage() {
+static void usage() {
   cerr<<"Syntax: ixplore diff ZONE BEFORE_FILE AFTER_FILE"<<endl;
   cerr<<"Syntax: ixplore track IP-ADDRESS PORT ZONE DIRECTORY [TSIGKEY TSIGALGO TSIGSECRET]"<<endl;
 }
index b113592555c6a3c5856c73a24de685c743cadf7a..bdca6eaa140b82d40a4969a6f3a05b84aae0fedf 100644 (file)
@@ -94,7 +94,7 @@ public:
 
 private:
   boost::circular_buffer<std::shared_ptr<OpenSSLTLSTicketKey> > d_ticketKeys;
-  pthread_rwlock_t d_lock;
+  ReadWriteLock d_lock;
 };
 
 void* libssl_get_ticket_key_callback_data(SSL* s);
index 7e9a11547b57cd2f14b848e567b77b522421ba7c..5cbc4404d7dc05ede533b1d529b00839cf28fb2d 100644 (file)
 #include "misc.hh"
 #include "pdnsexception.hh"
 
-extern bool g_singleThreaded;
-
-class Lock
+class ReadWriteLock
 {
-  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)
+  ReadWriteLock()
   {
-    if(g_singleThreaded)
-      return;
-
-    int err;
-    if((err = pthread_mutex_lock(d_lock))) {
-      errno = err;
-      throw PDNSException("error acquiring lock: "+stringerror());
+    if (pthread_rwlock_init(&d_lock, nullptr) != 0) {
+      throw std::runtime_error("Error creating a read-write lock: " + stringerror());
     }
   }
-  ~Lock()
-  {
-    if(g_singleThreaded)
-      return;
 
-    pthread_mutex_unlock(d_lock);
+  ~ReadWriteLock() {
+    /* might have been moved */
+    pthread_rwlock_destroy(&d_lock);
   }
+
+  ReadWriteLock(const ReadWriteLock& rhs) = delete;
+  ReadWriteLock& operator=(const ReadWriteLock& rhs) = delete;
+
+  pthread_rwlock_t* getLock()
+  {
+    return &d_lock;
+  }
+
+private:
+  pthread_rwlock_t d_lock;
 };
 
-class WriteLock
+class ReadLock
 {
-  pthread_rwlock_t *d_lock;
 public:
+  ReadLock(ReadWriteLock& lock): ReadLock(lock.getLock())
+  {
+  }
 
-  WriteLock(pthread_rwlock_t *lock) : d_lock(lock)
+  ReadLock(ReadWriteLock* lock): ReadLock(lock->getLock())
   {
-    if(g_singleThreaded)
-      return;
+  }
 
+  ~ReadLock()
+  {
+    if(d_lock) // may have been moved
+      pthread_rwlock_unlock(d_lock);
+  }
+
+  ReadLock(ReadLock&& rhs)
+  {
+    d_lock = rhs.d_lock;
+    rhs.d_lock = nullptr;
+  }
+  ReadLock(const ReadLock& rhs) = delete;
+  ReadLock& operator=(const ReadLock& rhs) = delete;
+
+private:
+  ReadLock(pthread_rwlock_t *lock) : d_lock(lock)
+  {
     int err;
-    if((err = pthread_rwlock_wrlock(d_lock))) {
-      throw PDNSException("error acquiring rwlock wrlock: "+stringerror(err));
+    if((err = pthread_rwlock_rdlock(d_lock))) {
+      throw PDNSException("error acquiring rwlock readlock: "+stringerror(err));
     }
   }
-  ~WriteLock()
+
+ pthread_rwlock_t *d_lock;
+};
+
+class WriteLock
+{
+public:
+  WriteLock(ReadWriteLock& lock): WriteLock(lock.getLock())
+  {
+  }
+
+  WriteLock(ReadWriteLock* lock): WriteLock(lock->getLock())
   {
-    if(g_singleThreaded)
-      return;
-    if(d_lock) // might have been moved
-      pthread_rwlock_unlock(d_lock);
   }
 
   WriteLock(WriteLock&& rhs)
@@ -82,35 +105,40 @@ public:
     d_lock = rhs.d_lock;
     rhs.d_lock=0;
   }
+
+  ~WriteLock()
+  {
+    if(d_lock) // might have been moved
+      pthread_rwlock_unlock(d_lock);
+  }
+
   WriteLock(const WriteLock& rhs) = delete;
   WriteLock& operator=(const WriteLock& rhs) = delete;
 
+private:
+  WriteLock(pthread_rwlock_t *lock) : d_lock(lock)
+  {
+    int err;
+    if((err = pthread_rwlock_wrlock(d_lock))) {
+      throw PDNSException("error acquiring rwlock wrlock: "+stringerror(err));
+    }
+  }
+
+  pthread_rwlock_t *d_lock;
 };
 
-class TryWriteLock
+class TryReadLock
 {
-  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)
+  TryReadLock(ReadWriteLock& lock): TryReadLock(lock.getLock())
   {
-    if(g_singleThreaded) {
-      d_havelock=true;
-      return;
-    }
+  }
 
-    d_havelock=false;
-    int err;
-    if((err = pthread_rwlock_trywrlock(d_lock)) && err!=EBUSY) {
-      throw PDNSException("error acquiring rwlock tryrwlock: "+stringerror(err));
-    }
-    d_havelock=(err==0);
+  TryReadLock(ReadWriteLock* lock): TryReadLock(lock->getLock())
+  {
   }
 
-  TryWriteLock(TryWriteLock&& rhs)
+  TryReadLock(TryReadLock&& rhs)
   {
     d_lock = rhs.d_lock;
     rhs.d_lock = nullptr;
@@ -118,46 +146,46 @@ public:
     rhs.d_havelock = false;
   }
 
-  
-  ~TryWriteLock()
+  ~TryReadLock()
   {
-    if(g_singleThreaded)
-      return;
-
-    if(d_havelock && d_lock) // we might be moved
+    if(d_havelock && d_lock)
       pthread_rwlock_unlock(d_lock);
   }
+
+  TryReadLock(const TryReadLock& rhs) = delete;
+  TryReadLock& operator=(const TryReadLock& rhs) = delete;
+
   bool gotIt()
   {
-    if(g_singleThreaded)
-      return true;
-
     return d_havelock;
   }
-};
-
-class TryReadLock
-{
-  pthread_rwlock_t *d_lock;
-  bool d_havelock;
-public:
-  TryReadLock(const TryReadLock& rhs) = delete;
-  TryReadLock& operator=(const TryReadLock& rhs) = delete;
 
+private:
   TryReadLock(pthread_rwlock_t *lock) : d_lock(lock)
   {
-    if(g_singleThreaded) {
-      d_havelock=true;
-      return;
-    }
-
     int err;
     if((err = pthread_rwlock_tryrdlock(d_lock)) && err!=EBUSY) {
       throw PDNSException("error acquiring rwlock tryrdlock: "+stringerror(err));
     }
     d_havelock=(err==0);
   }
-  TryReadLock(TryReadLock&& rhs)
+
+  pthread_rwlock_t *d_lock;
+  bool d_havelock;
+};
+
+class TryWriteLock
+{
+public:
+  TryWriteLock(ReadWriteLock& lock): TryWriteLock(lock.getLock())
+  {
+  }
+
+  TryWriteLock(ReadWriteLock* lock): TryWriteLock(lock->getLock())
+  {
+  }
+
+  TryWriteLock(TryWriteLock&& rhs)
   {
     d_lock = rhs.d_lock;
     rhs.d_lock = nullptr;
@@ -165,52 +193,32 @@ public:
     rhs.d_havelock = false;
   }
 
-  ~TryReadLock()
+  ~TryWriteLock()
   {
-    if(g_singleThreaded)
-      return;
-
-    if(d_havelock && d_lock)
+    if(d_havelock && d_lock) // we might be moved
       pthread_rwlock_unlock(d_lock);
   }
+
+  TryWriteLock(const TryWriteLock& rhs) = delete;
+  TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
+
   bool gotIt()
   {
-    if(g_singleThreaded)
-      return true;
-
     return d_havelock;
   }
-};
 
-
-class ReadLock
-{
-  pthread_rwlock_t *d_lock;
-public:
-
-  ReadLock(pthread_rwlock_t *lock) : d_lock(lock)
+private:
+  TryWriteLock(pthread_rwlock_t *lock) : d_lock(lock)
   {
-    if(g_singleThreaded)
-      return;
-
+    d_havelock=false;
     int err;
-    if((err = pthread_rwlock_rdlock(d_lock))) {
-      throw PDNSException("error acquiring rwlock readlock: "+stringerror(err));
+    if((err = pthread_rwlock_trywrlock(d_lock)) && err!=EBUSY) {
+      throw PDNSException("error acquiring rwlock tryrwlock: "+stringerror(err));
     }
-  }
-  ~ReadLock()
-  {
-    if(g_singleThreaded)
-      return;
-    if(d_lock) // may have been moved
-      pthread_rwlock_unlock(d_lock);
+    d_havelock=(err==0);
   }
 
-  ReadLock(ReadLock&& rhs)
-  {
-    d_lock = rhs.d_lock;
-    rhs.d_lock=0;
-  }
-  ReadLock(const ReadLock& rhs) = delete;
-  ReadLock& operator=(const ReadLock& rhs) = delete;
+  pthread_rwlock_t *d_lock;
+  bool d_havelock;
 };
+
index ad64c3108456251174287214fb91098c79933cba..6804b3997959e6b72eff16601cda998f0a48cb1b 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
+#include <mutex>
+
 #include "logger.hh"
 #include "misc.hh"
 #ifndef RECURSOR
 #include "statbag.hh"
 extern StatBag S;
 #endif
-#include "lock.hh"
 #include "namespaces.hh"
 
 thread_local Logger::PerThread Logger::t_perThread;
@@ -48,7 +50,7 @@ Logger& getLogger()
   return log;
 }
 
-void Logger::log(const string &msg, Urgency u)
+void Logger::log(const string &msg, Urgency u) noexcept
 {
 #ifndef RECURSOR
   bool mustAccount(false);
@@ -96,8 +98,8 @@ void Logger::log(const string &msg, Urgency u)
       }
     }
 
-    static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
-    Lock l(&m); // the C++-2011 spec says we need this, and OSX actually does
+    static std::mutex m;
+    std::lock_guard<std::mutex> l(m); // the C++-2011 spec says we need this, and OSX actually does
     clog << string(buffer) + prefix + msg <<endl;
 #ifndef RECURSOR
     mustAccount=true;
@@ -111,8 +113,14 @@ void Logger::log(const string &msg, Urgency u)
   }
 
 #ifndef RECURSOR
-  if(mustAccount)
-    S.ringAccount("logmessages",msg);
+  if(mustAccount) {
+      try {
+        S.ringAccount("logmessages",msg);
+      }
+      catch (const runtime_error& e) {
+        cerr << e.what() << endl;
+      }
+  }
 #endif
 }
 
index 38689e2d5a88b16c51141cd84640c671b9110175..e8c4da7019f769c779d354f0d8377f87e5c18666 100644 (file)
@@ -45,7 +45,7 @@ public:
       \param msg Message you wish to log
       \param u Urgency of the message you wish to log
   */
-  void log(const string &msg, Urgency u=Notice);
+  void log(const string &msg, Urgency u=Notice) noexcept;
 
   void setFacility(int f){d_facility=f;open();} //!< Choose logging facility
   void setFlag(int f){flags|=f;open();} //!< set a syslog flag
index ede5d1b038e5b55ab9c4cbe5132c35879e6b3d0e..90c527626f0892c10c59a58e2a80e4bf7576b467 100644 (file)
@@ -117,21 +117,27 @@ bool AuthLua4::axfrfilter(const ComboAddress& remote, const DNSName& zone, const
 
   const auto& rows = std::get<1>(ret);
 
-  for(const auto& row: rows) {
-    DNSResourceRecord rec;
-    for(const auto& col: row.second) {
-      if (col.first == "qtype")
-        rec.qtype = QType(boost::get<unsigned int>(col.second));
-      else if (col.first == "qname")
-        rec.qname = DNSName(boost::get<std::string>(col.second)).makeLowerCase();
-      else if (col.first == "ttl")
-        rec.ttl = boost::get<unsigned int>(col.second);
-      else if (col.first == "content")
-        rec.setContent(boost::get<std::string>(col.second));
-      else
-        throw PDNSException("Cannot understand "+col.first+" in axfr filter response on row "+std::to_string(row.first));
+  try {
+    for(const auto& row: rows) {
+      DNSResourceRecord rec;
+
+      const auto& map = row.second;
+      rec.qtype = QType(boost::get<unsigned int>(map.at("qtype")));
+      rec.qname = DNSName(boost::get<std::string>(map.at("qname")));
+      rec.qname.makeUsLowerCase();
+      if (map.count("ttl")) {
+        rec.ttl = boost::get<unsigned int>(map.at("ttl"));
+      }
+      rec.setContent(boost::get<std::string>(map.at("content")));
+
+      out.push_back(rec);
     }
-    out.push_back(rec);
+  }
+  catch (const std::exception& e) {
+    throw PDNSException("Cannot understand axfr filter response: " + std::string(e.what()));
+  }
+  catch (const PDNSException& e) {
+    throw PDNSException("Cannot understand axfr filter response: " + e.reason);
   }
 
   return true;
index 163f4687a0504aed2270e106a1d0cc3c2b7afc5f..bce88d14182ce608192dead4f7ef7504bfbab642 100644 (file)
@@ -174,7 +174,7 @@ void BaseLua4::prepareContext() {
   d_lw->registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
 
   // DNSRecord
-  d_lw->writeFunction("newDR", [](const DNSName &name, const std::string &type, unsigned int ttl, const std::string &content, int place){ QType qtype; qtype = type; auto dr = DNSRecord(); dr.d_name = name; dr.d_type = qtype.getCode(); dr.d_ttl = ttl; dr.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, 1, content)); dr.d_place = static_cast<DNSResourceRecord::Place>(place); return dr; });
+  d_lw->writeFunction("newDR", [](const DNSName &name, const std::string &type, unsigned int ttl, const std::string &content, int place){ QType qtype; qtype = type; auto dr = DNSRecord(); dr.d_name = name; dr.d_type = qtype.getCode(); dr.d_ttl = ttl; dr.d_content = shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, QClass::IN, content)); dr.d_place = static_cast<DNSResourceRecord::Place>(place); return dr; });
   d_lw->registerMember("name", &DNSRecord::d_name);
   d_lw->registerMember("type", &DNSRecord::d_type);
   d_lw->registerMember("ttl", &DNSRecord::d_ttl);
index f7f3ef000990ea4e29a60a88d7c2d56b1765902c..9c7e5dcd86f79326a6dd78616564ea369b91338f 100644 (file)
@@ -71,15 +71,13 @@ private:
     std::atomic<time_t> lastAccess{0};
   };
 
-  pthread_rwlock_t d_lock;
+  ReadWriteLock d_lock;
 public:
   IsUpOracle()
   {
-    pthread_rwlock_init(&d_lock, nullptr);
   }
   ~IsUpOracle()
   {
-    pthread_rwlock_destroy(&d_lock);
   }
   bool isUp(const ComboAddress& remote, const opts_t& opts);
   bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
@@ -284,7 +282,7 @@ bool doCompare(const T& var, const std::string& res, const C& cmp)
 }
 
 
-std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
+static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
 {
   static bool initialized;
   extern std::function<std::string(const std::string& ip, int)> g_getGeo;
@@ -344,6 +342,10 @@ static ComboAddress pickwhashed(const ComboAddress& bestwho, vector<pair<int,Com
     sum += i.first;
     pick.push_back({sum, i.second});
   }
+  if (sum == 0) {
+    /* we should not have any weight of zero, but better safe than sorry */
+    return ComboAddress();
+  }
   ComboAddress::addressOnlyHash aoh;
   int r = aoh(bestwho) % sum;
   auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const decltype(pick)::value_type& a) { return rarg < a.first; });
@@ -368,7 +370,7 @@ static bool getLatLon(const std::string& ip, string& loc)
   double latsec, lonsec;
   char lathem='X', lonhem='X';
 
-  double lat, lon;
+  double lat = 0, lon = 0;
   if(!getLatLon(ip, lat, lon))
     return false;
 
@@ -527,12 +529,12 @@ typedef struct AuthLuaRecordContext
 
 static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
 
-void setupLuaRecords()
+static void setupLuaRecords()
 {
   LuaContext& lua = *s_LUA->getLua();
 
   lua.writeFunction("latlon", []() {
-      double lat, lon;
+      double lat = 0, lon = 0;
       getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
       return std::to_string(lat)+" "+std::to_string(lon);
     });
@@ -559,7 +561,7 @@ void setupLuaRecords()
       auto labels= s_lua_record_ctx->qname.getRawLabels();
       if(labels.size()<4)
         return std::string("unknown");
-      double lat, lon;
+      double lat = 0, lon = 0;
       getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
       return std::to_string(lat)+" "+std::to_string(lon);
     });
@@ -604,21 +606,47 @@ void setupLuaRecords()
       return std::string("error");
     });
   lua.writeFunction("createForward", []() {
+      static string allZerosIP("0.0.0.0");
       DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
+      // parts is something like ["1", "2", "3", "4", "static"] or
+      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
       auto parts = rel.getRawLabels();
-      if(parts.size()==4)
-        return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3];
-      if(parts.size()==1) {
+      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
+      if(parts.size()>=4) {
+        try {
+          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
+          return ca.toString();
+        } catch (const PDNSException &e) {
+          return allZerosIP;
+        }
+      } else if (parts.size() >= 1) {
         // either hex string, or 12-13-14-15
-        //        cout<<parts[0]<<endl;
+        vector<string> ip_parts;
+        stringtok(ip_parts, parts[0], "-");
         unsigned int x1, x2, x3, x4;
-        if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
+        if (ip_parts.size() >= 4) {
+          // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
+          string ret;
+          for (size_t n=4; n > 0; n--) {
+            auto octet = ip_parts[ip_parts.size() - n];
+            try {
+              auto octetVal = std::stol(octet);
+              if (octetVal >= 0 && octetVal <= 255) {
+                ret += ip_parts.at(ip_parts.size() - n) + ".";
+              } else {
+                return allZerosIP;
+              }
+            } catch (const std::exception &e) {
+              return allZerosIP;
+            }
+          }
+          ret.resize(ret.size() - 1); // remove trailing dot after last octet
+          return ret;
+        } else if(parts[0].length() == 10 && sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
           return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
         }
-
-
       }
-      return std::string("0.0.0.0");
+      return allZerosIP;
     });
 
   lua.writeFunction("createForward6", []() {
@@ -658,12 +686,12 @@ void setupLuaRecords()
         for(int i=0; i<8; ++i) {
           if(i)
             together+=":";
-          string quad;
+          string lquad;
           for(int j=0; j <4; ++j) {
-            quad.append(1, labels[31-i*4-j][0]);
+            lquad.append(1, labels[31-i*4-j][0]);
             together += labels[31-i*4-j][0];
           }
-          quads.push_back(quad);
+          quads.push_back(lquad);
         }
         ComboAddress ip6(together,0);
 
@@ -683,8 +711,8 @@ void setupLuaRecords()
           fmt % labels[i];
         fmt % dashed;
 
-        for(const auto& quad : quads)
-          fmt % quad;
+        for(const auto& lquad : quads)
+          fmt % lquad;
 
         return fmt.str();
       }
@@ -701,7 +729,7 @@ void setupLuaRecords()
    * Simplistic test to see if an IP address listens on a certain port
    * Will return a single IP address from the set of available IP addresses. If
    * no IP address is available, will return a random element of the set of
-   * addresses suppplied for testing.
+   * addresses supplied for testing.
    *
    * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
    */
index 47396a8386ecaa3607122712f6b7cba4de403e81..7051b7c549d51b417c044758c192947c157063ef 100644 (file)
@@ -29,6 +29,12 @@ extern "C" {
     const void* data;
   } pdns_ednsoption_t;
 
+  typedef struct pdns_proxyprotocol_value {
+    uint8_t     type;
+    uint16_t    len;
+    const void* data;
+  } pdns_proxyprotocol_value_t;
+
   typedef enum
   {
     answer = 1,
@@ -53,11 +59,16 @@ extern "C" {
   size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsoption_t** out) __attribute__ ((visibility ("default")));
   size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t optionCode, const pdns_ednsoption_t** out) __attribute__ ((visibility ("default")));
 
+  // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
+  size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out) __attribute__ ((visibility ("default")));
+
   void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_add_policytag(pdns_ffi_param_t *ref, const char* name) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name) __attribute__ ((visibility ("default")));
+  void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* name) __attribute__ ((visibility ("default")));
+  
   void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_set_ttl_cap(pdns_ffi_param_t* ref, uint32_t ttl) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery) __attribute__ ((visibility ("default")));
index 36239b0e14a779b788e7bbd0b6c19cab3fcef7be..0fdf23772ecba2c393a9c32e3973b26ae21768be 100644 (file)
 
 RecursorLua4::RecursorLua4() { prepareContext(); }
 
-static int getFakeAAAARecords(const DNSName& qname, const std::string& prefix, vector<DNSRecord>& ret)
-{
-  int rcode=directResolve(qname, QType(QType::A), 1, ret);
-
-  ComboAddress prefixAddress(prefix);
-
-  // Remove double CNAME records
-  std::set<DNSName> seenCNAMEs;
-  ret.erase(std::remove_if(
-        ret.begin(),
-        ret.end(),
-        [&seenCNAMEs](DNSRecord& rr) {
-          if (rr.d_type == QType::CNAME) {
-            auto target = getRR<CNAMERecordContent>(rr);
-            if (target == nullptr) {
-              return false;
-            }
-            if (seenCNAMEs.count(target->getTarget()) > 0) {
-              // We've had this CNAME before, remove it
-              return true;
-            }
-            seenCNAMEs.insert(target->getTarget());
-          }
-          return false;
-        }),
-      ret.end());
-
-  bool seenA = false;
-  for(DNSRecord& rr :  ret)
-  {
-    if(rr.d_type == QType::A && rr.d_place==DNSResourceRecord::ANSWER) {
-      if(auto rec = getRR<ARecordContent>(rr)) {
-        ComboAddress ipv4(rec->getCA());
-        uint32_t tmp;
-        memcpy((void*)&tmp, &ipv4.sin4.sin_addr.s_addr, 4);
-        // tmp=htonl(tmp);
-        memcpy(((char*)&prefixAddress.sin6.sin6_addr.s6_addr)+12, &tmp, 4);
-        rr.d_content = std::make_shared<AAAARecordContent>(prefixAddress);
-        rr.d_type = QType::AAAA;
-      }
-      seenA = true;
-    }
-  }
-
-  if (seenA) {
-    // We've seen an A in the ANSWER section, so there is no need to keep any
-    // SOA in the AUTHORITY section as this is not a NODATA response.
-    ret.erase(std::remove_if(
-          ret.begin(),
-          ret.end(),
-          [](DNSRecord& rr) {
-            return (rr.d_type == QType::SOA && rr.d_place==DNSResourceRecord::AUTHORITY);
-          }),
-        ret.end());
-  }
-  return rcode;
-}
-
-static int getFakePTRRecords(const DNSName& qname, const std::string& prefix, vector<DNSRecord>& ret)
-{
-  /* qname has a reverse ordered IPv6 address, need to extract the underlying IPv4 address from it
-     and turn it into an IPv4 in-addr.arpa query */
-  ret.clear();
-  vector<string> parts = qname.getRawLabels();
-
-  if(parts.size() < 8)
-    return -1;
-
-  string newquery;
-  for(int n = 0; n < 4; ++n) {
-    newquery +=
-      std::to_string(stoll(parts[n*2], 0, 16) + 16*stoll(parts[n*2+1], 0, 16));
-    newquery.append(1,'.');
-  }
-  newquery += "in-addr.arpa.";
-
-
-  DNSRecord rr;
-  rr.d_name = qname;
-  rr.d_type = QType::CNAME;
-  rr.d_content = std::make_shared<CNAMERecordContent>(newquery);
-  ret.push_back(rr);
-
-  int rcode = directResolve(DNSName(newquery), QType(QType::PTR), 1, ret);
-
-  return rcode;
-
-}
-
 boost::optional<dnsheader> RecursorLua4::DNSQuestion::getDH() const
 {
   if (dh)
@@ -183,6 +94,20 @@ boost::optional<Netmask>  RecursorLua4::DNSQuestion::getEDNSSubnet() const
   return boost::optional<Netmask>();
 }
 
+std::vector<std::pair<int, ProxyProtocolValue>> RecursorLua4::DNSQuestion::getProxyProtocolValues() const
+{
+  std::vector<std::pair<int, ProxyProtocolValue>> result;
+  if (proxyProtocolValues) {
+    result.reserve(proxyProtocolValues->size());
+
+    int idx = 1;
+    for (const auto& value: *proxyProtocolValues) {
+      result.push_back({ idx++, value });
+    }
+  }
+
+  return result;
+}
 
 vector<pair<int, DNSRecord> > RecursorLua4::DNSQuestion::getRecords() const
 {
@@ -208,7 +133,7 @@ void RecursorLua4::DNSQuestion::addRecord(uint16_t type, const std::string& cont
   dr.d_ttl=ttl.get_value_or(3600);
   dr.d_type = type;
   dr.d_place = place;
-  dr.d_content = DNSRecordContent::mastermake(type, 1, content);
+  dr.d_content = DNSRecordContent::mastermake(type, QClass::IN, content);
   records.push_back(dr);
 }
 
@@ -255,16 +180,16 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerMember("appliedPolicy", &DNSQuestion::appliedPolicy);
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyName",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
-      if(pol.d_name)
-        return *pol.d_name;
-      return std::string();
+      return pol.getName();
     },
     [](DNSFilterEngine::Policy& pol, const std::string& name) {
-      pol.d_name = std::make_shared<std::string>(name);
+      pol.setName(name);
     });
   d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
   d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
   d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
+  d_lw->registerMember("policyTrigger", &DNSFilterEngine::Policy::d_trigger);
+  d_lw->registerMember("policyHit", &DNSFilterEngine::Policy::d_hit);
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
       std::string result;
@@ -291,6 +216,7 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerFunction("getEDNSOptions", &DNSQuestion::getEDNSOptions);
   d_lw->registerFunction("getEDNSOption", &DNSQuestion::getEDNSOption);
   d_lw->registerFunction("getEDNSSubnet", &DNSQuestion::getEDNSSubnet);
+  d_lw->registerFunction("getProxyProtocolValues", &DNSQuestion::getProxyProtocolValues);
   d_lw->registerFunction("getEDNSFlags", &DNSQuestion::getEDNSFlags);
   d_lw->registerFunction("getEDNSFlag", &DNSQuestion::getEDNSFlag);
   d_lw->registerMember("name", &DNSRecord::d_name);
@@ -336,19 +262,22 @@ void RecursorLua4::postPrepareContext()
       return ret;
     });
 
+  d_lw->registerFunction<const ProxyProtocolValue, std::string()>("getContent", [](const ProxyProtocolValue& value) { return value.content; });
+  d_lw->registerFunction<const ProxyProtocolValue, uint8_t()>("getType", [](const ProxyProtocolValue& value) { return value.type; });
 
-  d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.d_content = DNSRecordContent::mastermake(dr.d_type, 1, newContent); });
+  d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.d_content = DNSRecordContent::mastermake(dr.d_type, QClass::IN, newContent); });
   d_lw->registerFunction("addAnswer", &DNSQuestion::addAnswer);
   d_lw->registerFunction("addRecord", &DNSQuestion::addRecord);
   d_lw->registerFunction("getRecords", &DNSQuestion::getRecords);
   d_lw->registerFunction("setRecords", &DNSQuestion::setRecords);
 
-  d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("addPolicyTag", [](DNSQuestion& dq, const std::string& tag) { if (dq.policyTags) { dq.policyTags->push_back(tag); } });
+  d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("addPolicyTag", [](DNSQuestion& dq, const std::string& tag) { if (dq.policyTags) { dq.policyTags->insert(tag); } });
   d_lw->registerFunction<void(DNSQuestion::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](DNSQuestion& dq, const std::vector<std::pair<int, std::string> >& tags) {
       if (dq.policyTags) {
         dq.policyTags->clear();
+        dq.policyTags->reserve(tags.size());
         for (const auto& tag : tags) {
-          dq.policyTags->push_back(tag.second);
+          dq.policyTags->insert(tag.second);
         }
       }
     });
@@ -356,6 +285,7 @@ void RecursorLua4::postPrepareContext()
       std::vector<std::pair<int, std::string> > ret;
       if (dq.policyTags) {
         int count = 1;
+        ret.reserve(dq.policyTags->size());
         for (const auto& tag : *dq.policyTags) {
           ret.push_back({count++, tag});
         }
@@ -403,14 +333,23 @@ void RecursorLua4::postPrepareContext()
     {"Custom",   (int)DNSFilterEngine::PolicyKind::Custom  }
     }});
 
+  d_pd.push_back({"policytypes", in_t {
+    {"None",       (int)DNSFilterEngine::PolicyType::None       },
+    {"QName",      (int)DNSFilterEngine::PolicyType::QName      },
+    {"ClientIP",   (int)DNSFilterEngine::PolicyType::ClientIP   },
+    {"ResponseIP", (int)DNSFilterEngine::PolicyType::ResponseIP },
+    {"NSDName",    (int)DNSFilterEngine::PolicyType::NSDName    },
+    {"NSIP",       (int)DNSFilterEngine::PolicyType::NSIP       }
+    }});
+
   for(const auto& n : QType::names)
     d_pd.push_back({n.first, n.second});
 
   d_pd.push_back({"validationstates", in_t{
-        {"Indeterminate", Indeterminate },
-        {"Bogus", Bogus },
-        {"Insecure", Insecure },
-        {"Secure", Secure },
+        {"Indeterminate", static_cast<unsigned int>(vState::Indeterminate) },
+        {"Bogus", static_cast<unsigned int>(vState::Bogus) },
+        {"Insecure", static_cast<unsigned int>(vState::Insecure) },
+        {"Secure", static_cast<unsigned int>(vState::Secure) },
   }});
 
   d_pd.push_back({"now", &g_now});
@@ -446,6 +385,38 @@ void RecursorLua4::postPrepareContext()
   d_lw->writeFunction("getregisteredname", [](const DNSName &dname) {
       return getRegisteredName(dname);
   });
+
+  d_lw->registerMember<const DNSName (PolicyEvent::*)>("qname", [](const PolicyEvent& event) -> const DNSName& { return event.qname; }, [](PolicyEvent& event, const DNSName& newName) { (void) newName; });
+  d_lw->registerMember<uint16_t (PolicyEvent::*)>("qtype", [](const PolicyEvent& event) -> uint16_t { return event.qtype.getCode(); }, [](PolicyEvent& event, uint16_t newType) { (void) newType; });
+  d_lw->registerMember<bool (PolicyEvent::*)>("isTcp", [](const PolicyEvent& event) -> bool { return event.isTcp; }, [](PolicyEvent& event, bool newTcp) { (void) newTcp; });
+  d_lw->registerMember<const ComboAddress (PolicyEvent::*)>("remote", [](const PolicyEvent& event) -> const ComboAddress& { return event.remote; }, [](PolicyEvent& event, const ComboAddress& newRemote) { (void) newRemote; });
+  d_lw->registerMember("appliedPolicy", &PolicyEvent::appliedPolicy);
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags) { event.policyTags->insert(tag); } });
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](PolicyEvent& event, const std::vector<std::pair<int, std::string> >& tags) {
+      if (event.policyTags) {
+        event.policyTags->clear();
+        event.policyTags->reserve(tags.size());
+        for (const auto& tag : tags) {
+          event.policyTags->insert(tag.second);
+        }
+      }
+    });
+  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(PolicyEvent::*)()>("getPolicyTags", [](const PolicyEvent& event) {
+      std::vector<std::pair<int, std::string> > ret;
+      if (event.policyTags) {
+        int count = 1;
+        ret.reserve(event.policyTags->size());
+        for (const auto& tag : *event.policyTags) {
+          ret.push_back({count++, tag});
+        }
+      }
+      return ret;
+    });
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("discardPolicy", [](PolicyEvent& event, const std::string& policy) {
+    if (event.discardedPolicies) {
+      (*event.discardedPolicies)[policy] = true;
+    }
+  });
 }
 
 void RecursorLua4::postLoad() {
@@ -460,11 +431,13 @@ void RecursorLua4::postLoad() {
   d_ipfilter = d_lw->readVariable<boost::optional<ipfilter_t>>("ipfilter").get_value_or(0);
   d_gettag = d_lw->readVariable<boost::optional<gettag_t>>("gettag").get_value_or(0);
   d_gettag_ffi = d_lw->readVariable<boost::optional<gettag_ffi_t>>("gettag_ffi").get_value_or(0);
+
+  d_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(0);
 }
 
 void RecursorLua4::getFeatures(Features & features) {
   // Add key-values pairs below.
-  // Make sure you add string values explicity converted to string.
+  // Make sure you add string values explicitly converted to string.
   // e.g. features.push_back(make_pair("somekey", string("stringvalue"));
   // Both int and double end up as a lua number type.
    features.push_back(make_pair("PR8001_devicename", true));
@@ -520,16 +493,43 @@ 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, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName) const
+bool RecursorLua4::policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string, bool>& dicardedPolicies) const
+{
+  if (!d_policyHitEventFilter) {
+    return false;
+  }
+
+  PolicyEvent event(remote, qname, qtype, tcp);
+  event.appliedPolicy = &policy;
+  event.policyTags = &tags;
+  event.discardedPolicies = &dicardedPolicies;
+
+  if (d_policyHitEventFilter(event)) {
+    return true;
+  }
+  else {
+    return false;
+  }
+}
+
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const
 {
   if(d_gettag) {
-    auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp);
+    std::vector<std::pair<int, const ProxyProtocolValue*>> proxyProtocolValuesMap;
+    proxyProtocolValuesMap.reserve(proxyProtocolValues.size());
+    int num = 1;
+    for (const auto& value : proxyProtocolValues) {
+      proxyProtocolValuesMap.emplace_back(num++, &value);
+    }
+
+    auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp, proxyProtocolValuesMap);
 
     if (policyTags) {
       const auto& tags = std::get<1>(ret);
       if (tags) {
+        policyTags->reserve(policyTags->size() + tags->size());
         for (const auto& tag : *tags) {
-          policyTags->push_back(tag.second);
+          policyTags->insert(tag.second);
         }
       }
     }
@@ -550,6 +550,12 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
     if (deviceNameret) {
       deviceName = *deviceNameret;
     }
+
+    const auto routingTarget = std::get<6>(ret);
+    if (routingTarget) {
+      routingTag = *routingTarget;
+    }
+
     return std::get<0>(ret);
   }
   return 0;
@@ -558,7 +564,7 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
 struct pdns_ffi_param
 {
 public:
-  pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::vector<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), rcode(rcode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_)
+  pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::unordered_set<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector<ProxyProtocolValue>& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, std::string& routingTag_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), rcode(rcode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_)
   {
   }
 
@@ -567,17 +573,20 @@ public:
   std::unique_ptr<std::string> remoteStr{nullptr};
   std::unique_ptr<std::string> ednssubnetStr{nullptr};
   std::vector<pdns_ednsoption_t> ednsOptionsVect;
+  std::vector<pdns_proxyprotocol_value_t> proxyProtocolValuesVect;
 
   const DNSName& qname;
   const ComboAddress& local;
   const ComboAddress& remote;
   const Netmask& ednssubnet;
-  std::vector<std::string>& policyTags;
+  std::unordered_set<std::string>& policyTags;
   std::vector<DNSRecord>& records;
   const EDNSOptionViewMap& ednsOptions;
+  const std::vector<ProxyProtocolValue>& proxyProtocolValues;
   std::string& requestorId;
   std::string& deviceId;
   std::string& deviceName;
+  std::string& routingTag;
   boost::optional<int>& rcode;
   uint32_t& ttlCap;
   bool& variable;
@@ -590,10 +599,10 @@ public:
   bool tcp;
 };
 
-unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const
+unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const
 {
   if (d_gettag_ffi) {
-    pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, requestorId, deviceId, deviceName, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords);
+    pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords);
 
     auto ret = d_gettag_ffi(&param);
     if (ret) {
@@ -635,10 +644,10 @@ loop:;
         ret = followCNAMERecords(dq.records, QType(dq.qtype));
       }
       else if(dq.followupFunction=="getFakeAAAARecords") {
-        ret=getFakeAAAARecords(dq.followupName, dq.followupPrefix, dq.records);
+        ret=getFakeAAAARecords(dq.followupName, ComboAddress(dq.followupPrefix), dq.records);
       }
       else if(dq.followupFunction=="getFakePTRRecords") {
-        ret=getFakePTRRecords(dq.followupName, dq.followupPrefix, dq.records);
+        ret=getFakePTRRecords(dq.followupName, dq.records);
       }
       else if(dq.followupFunction=="udpQueryResponse") {
         dq.udpAnswer = GenUDPQueryResponse(dq.udpQueryDest, dq.udpQuery);
@@ -823,6 +832,30 @@ size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t o
   return pos;
 }
 
+size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out)
+{
+  if (ref->proxyProtocolValues.empty()) {
+    return 0;
+  }
+
+  ref->proxyProtocolValuesVect.resize(ref->proxyProtocolValues.size());
+
+  size_t pos = 0;
+  for (const auto& value : ref->proxyProtocolValues) {
+    auto& dest = ref->proxyProtocolValuesVect.at(pos);
+    dest.type = value.type;
+    dest.len = value.content.size();
+    if (dest.len > 0) {
+      dest.data = value.content.data();
+    }
+    pos++;
+  }
+
+  *out = ref->proxyProtocolValuesVect.data();
+
+  return ref->proxyProtocolValuesVect.size();
+}
+
 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag)
 {
   ref->tag = tag;
@@ -830,7 +863,7 @@ void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag)
 
 void pdns_ffi_param_add_policytag(pdns_ffi_param_t *ref, const char* name)
 {
-  ref->policyTags.push_back(std::string(name));
+  ref->policyTags.insert(std::string(name));
 }
 
 void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name)
@@ -848,6 +881,11 @@ void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void*
   ref->deviceId = std::string(reinterpret_cast<const char*>(name), len);
 }
 
+void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag)
+{
+  ref->routingTag = std::string(rtag);
+}
+
 void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable)
 {
   ref->variable = variable;
index 4515136a4a650aaec0abecd1de75484d384a1671..3b911eed181b5f49855818a0b7b721648b8ab9d9 100644 (file)
@@ -33,6 +33,8 @@
 #include "ednsoptions.hh"
 #include "validate.hh"
 #include "lua-base4.hh"
+#include "proxy-protocol.hh"
+
 #include <unordered_map>
 
 #include "lua-recursor4-ffi.hh"
@@ -73,12 +75,13 @@ public:
     const uint16_t* ednsFlags{nullptr};
     vector<DNSRecord>* currentRecords{nullptr};
     DNSFilterEngine::Policy* appliedPolicy{nullptr};
-    std::vector<std::string>* policyTags{nullptr};
-    std::unordered_map<std::string,bool>* discardedPolicies{nullptr};
+    std::unordered_set<std::string>* policyTags{nullptr};
+    const std::vector<ProxyProtocolValue>* proxyProtocolValues{nullptr};
+    std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
     std::string requestorId;
     std::string deviceId;
     std::string deviceName;
-    vState validationState{Indeterminate};
+    vState validationState{vState::Indeterminate};
     bool& variable;
     bool& wantsRPZ;
     bool& logResponse;
@@ -91,6 +94,7 @@ public:
     vector<pair<uint16_t, string> > getEDNSOptions() const;
     boost::optional<string> getEDNSOption(uint16_t code) const;
     boost::optional<Netmask> getEDNSSubnet() const;
+    std::vector<std::pair<int, ProxyProtocolValue>> getProxyProtocolValues() const;
     vector<string> getEDNSFlags() const;
     bool getEDNSFlag(string flag) const;
     void setRecords(const vector<pair<int,DNSRecord> >& records);
@@ -111,8 +115,22 @@ public:
     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, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName) const;
-  unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const;
+  struct PolicyEvent
+  {
+    PolicyEvent(const ComboAddress& rem, const DNSName& name, const QType& type, bool tcp): qname(name), qtype(type), remote(rem), isTcp(tcp)
+    {
+    }
+    const DNSName& qname;
+    const QType qtype;
+    const ComboAddress& remote;
+    const bool isTcp;
+    DNSFilterEngine::Policy* appliedPolicy{nullptr};
+    std::unordered_set<std::string>* policyTags{nullptr};
+    std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
+  };
+
+  unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const;
+  unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const;
 
   void maintenance() const;
   bool prerpz(DNSQuestion& dq, int& ret) const;
@@ -124,6 +142,8 @@ public:
   bool preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret) const;
   bool ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader&) const;
 
+  bool policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string, bool>& dicardedPolicies) const;
+
   bool needDQ() const
   {
     return (d_prerpz ||
@@ -133,7 +153,7 @@ public:
             d_postresolve);
   }
 
-  typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject>,boost::optional<std::string>,boost::optional<std::string>,boost::optional<std::string> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool)> gettag_t;
+  typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int, string> >,boost::optional<LuaContext::LuaObject>,boost::optional<std::string>,boost::optional<std::string>,boost::optional<std::string>,boost::optional<string> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool, const std::vector<std::pair<int, const ProxyProtocolValue*>>&)> gettag_t;
   gettag_t d_gettag; // public so you can query if we have this hooked
   typedef std::function<boost::optional<LuaContext::LuaObject>(pdns_ffi_param_t*)> gettag_ffi_t;
   gettag_ffi_t d_gettag_ffi;
@@ -150,5 +170,7 @@ private:
   bool genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const;
   typedef std::function<bool(ComboAddress,ComboAddress, struct dnsheader)> ipfilter_t;
   ipfilter_t d_ipfilter;
+  typedef std::function<bool(PolicyEvent&)> policyEventFilter_t;
+  policyEventFilter_t d_policyHitEventFilter;
 };
 
index 2fb111fde1a878a16f858e184c80cb53f06089fd..cc40ae9b66ada023af6681ff14f11f7359864bf8 100644 (file)
@@ -47,6 +47,7 @@
 #include <boost/algorithm/string.hpp>
 #include "validate-recursor.hh"
 #include "ednssubnet.hh"
+#include "query-local-address.hh"
 
 #ifdef HAVE_PROTOBUF
 
@@ -55,6 +56,8 @@
 #ifdef HAVE_FSTRM
 #include "rec-dnstap.hh"
 #include "fstrm_logger.hh"
+
+
 bool g_syslog;
 
 static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
@@ -315,7 +318,7 @@ int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool d
       Socket s(ip.sin4.sin_family, SOCK_STREAM);
 
       s.setNonBlocking();
-      ComboAddress local = getQueryLocalAddress(ip.sin4.sin_family, 0);
+      ComboAddress local = pdns::getQueryLocalAddress(ip.sin4.sin_family, 0);
 
       s.bind(local);
         
index 36a6d714fcd4d7c647c8467f5a9f0bac81897203..2e813bd8cb69b4d20fb11ad111a4214c10d485d2 100644 (file)
@@ -47,7 +47,7 @@
 int asendto(const char *data, size_t len, int flags, const ComboAddress& ip, uint16_t id,
             const DNSName& domain, uint16_t qtype,  int* fd);
 int arecvfrom(std::string& packet, int flags, const ComboAddress& ip, size_t *d_len, uint16_t id,
-              const DNSName& domain, uint16_t qtype, int fd, struct timeval* now);
+              const DNSName& domain, uint16_t qtype, int fd, const struct timeval* now);
 
 class LWResException : public PDNSException
 {
index 4219e1d356bd0a374ef1e665e4029e232418fd09..9e0f9eb561e676b2f078347182ab638dfcc7d405 100644 (file)
@@ -40,6 +40,7 @@
 #include "packetcache.hh"
 #include "base64.hh"
 #include "namespaces.hh"
+#include "query-local-address.hh"
 
 
 void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
@@ -214,7 +215,7 @@ time_t CommunicatorClass::doNotifications(PacketHandler *P)
         ComboAddress remote(ip, 53); // default to 53
         if((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) ||
            (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET)) {
-             g_log<<Logger::Warning<<"Unable to notify "<<remote.toStringWithPort()<<" for domain '"<<domain<<"', address family is disabled. Is query-local-address"<<(remote.sin4.sin_family == AF_INET ? "" : "6")<<" unset?"<<endl;
+             g_log<<Logger::Warning<<"Unable to notify "<<remote.toStringWithPort()<<" for domain '"<<domain<<"', address family is disabled. Is an IPv"<<(remote.sin4.sin_family == AF_INET ? "4" : "6")<<" address set in query-local-address?"<<endl;
              d_nq.removeIf(remote.toStringWithPort(), id, domain); // Remove, we'll never be able to notify
              continue; // don't try to notify what we can't!
         }
@@ -280,13 +281,13 @@ void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const
 
 void CommunicatorClass::drillHole(const DNSName &domain, const string &ip)
 {
-  Lock l(&d_holelock);
+  std::lock_guard<std::mutex> l(d_holelock);
   d_holes[make_pair(domain,ip)]=time(0);
 }
 
 bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
 {
-  Lock l(&d_holelock);
+  std::lock_guard<std::mutex> l(d_holelock);
   if(d_holes.find(make_pair(domain,ip))==d_holes.end()) // no hole
     return false;
 
@@ -299,13 +300,13 @@ bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
 
 void CommunicatorClass::makeNotifySockets()
 {
-  if(!::arg()["query-local-address"].empty()) {
-    d_nsock4 = makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind"));
+  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+    d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
   } else {
     d_nsock4 = -1;
   }
-  if(!::arg()["query-local-address6"].empty()) {
-    d_nsock6 = makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind"));
+  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+    d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
   } else {
     d_nsock6 = -1;
   }
index 279151116a9ee6cf2d85cbd7c1d7c1f4473cc2da..426a6d55594cbb47690df8c0b12a886f95a78386 100644 (file)
@@ -57,6 +57,7 @@
 #include <sys/types.h>
 #include <pwd.h>
 #include <grp.h>
+#include <limits.h>
 #ifdef __FreeBSD__
 #  include <pthread_np.h>
 #endif
@@ -65,8 +66,6 @@
 #  include <sched.h>
 #endif
 
-bool g_singleThreaded;
-
 size_t writen2(int fd, const void *buf, size_t count)
 {
   const char *ptr = (char*)buf;
@@ -441,17 +440,6 @@ string humanDuration(time_t passed)
   return ret.str();
 }
 
-DTime::DTime()
-{
-//  set(); // saves lots of gettimeofday calls
-  d_set.tv_sec=d_set.tv_usec=0;
-}
-
-time_t DTime::time()
-{
-  return d_set.tv_sec;
-}
-
 const string unquotify(const string &item)
 {
   if(item.size()<2)
@@ -585,83 +573,6 @@ string makeHexDump(const string& str)
   return ret;
 }
 
-// shuffle, maintaining some semblance of order
-void shuffle(vector<DNSZoneRecord>& rrs)
-{
-  vector<DNSZoneRecord>::iterator first, second;
-  for(first=rrs.begin();first!=rrs.end();++first)
-    if(first->dr.d_place==DNSResourceRecord::ANSWER && first->dr.d_type != QType::CNAME) // CNAME must come first
-      break;
-  for(second=first;second!=rrs.end();++second)
-    if(second->dr.d_place!=DNSResourceRecord::ANSWER)
-      break;
-
-  if(second-first > 1)
-    random_shuffle(first,second);
-
-  // now shuffle the additional records
-  for(first=second;first!=rrs.end();++first)
-    if(first->dr.d_place==DNSResourceRecord::ADDITIONAL && first->dr.d_type != QType::CNAME) // CNAME must come first
-      break;
-  for(second=first;second!=rrs.end();++second)
-    if(second->dr.d_place!=DNSResourceRecord::ADDITIONAL)
-      break;
-
-  if(second-first>1)
-    random_shuffle(first,second);
-
-  // we don't shuffle the rest
-}
-
-
-// shuffle, maintaining some semblance of order
-void shuffle(vector<DNSRecord>& rrs)
-{
-  vector<DNSRecord>::iterator first, second;
-  for(first=rrs.begin();first!=rrs.end();++first)
-    if(first->d_place==DNSResourceRecord::ANSWER && first->d_type != QType::CNAME) // CNAME must come first
-      break;
-  for(second=first;second!=rrs.end();++second)
-    if(second->d_place!=DNSResourceRecord::ANSWER || second->d_type == QType::RRSIG) // leave RRSIGs at the end
-      break;
-
-  if(second-first>1)
-    random_shuffle(first,second);
-
-  // now shuffle the additional records
-  for(first=second;first!=rrs.end();++first)
-    if(first->d_place==DNSResourceRecord::ADDITIONAL && first->d_type != QType::CNAME) // CNAME must come first
-      break;
-  for(second=first; second!=rrs.end(); ++second)
-    if(second->d_place!=DNSResourceRecord::ADDITIONAL)
-      break;
-
-  if(second-first>1)
-    random_shuffle(first,second);
-
-  // we don't shuffle the rest
-}
-
-static uint16_t mapTypesToOrder(uint16_t type)
-{
-  if(type == QType::CNAME)
-    return 0;
-  if(type == QType::RRSIG)
-    return 65535;
-  else
-    return 1;
-}
-
-// make sure rrs is sorted in d_place order to avoid surprises later
-// then shuffle the parts that desire shuffling
-void orderAndShuffle(vector<DNSRecord>& rrs)
-{
-  std::stable_sort(rrs.begin(), rrs.end(), [](const DNSRecord&a, const DNSRecord& b) { 
-      return std::make_tuple(a.d_place, mapTypesToOrder(a.d_type)) < std::make_tuple(b.d_place, mapTypesToOrder(b.d_type));
-    });
-  shuffle(rrs);
-}
-
 void normalizeTV(struct timeval& tv)
 {
   if(tv.tv_usec > 1000000) {
@@ -777,9 +688,8 @@ int makeIPv6sockaddr(const std::string& addr, struct sockaddr_in6* ret)
     hints.ai_family = AF_INET6;
     hints.ai_flags = AI_NUMERICHOST;
 
-    int error;
     // getaddrinfo has anomalous return codes, anything nonzero is an error, positive or negative
-    if((error=getaddrinfo(ourAddr.c_str(), 0, &hints, &res))) {
+    if (getaddrinfo(ourAddr.c_str(), 0, &hints, &res) != 0) {
       return -1;
     }
 
@@ -1613,3 +1523,62 @@ bool setPipeBufferSize(int fd, size_t size)
   return false;
 #endif /* F_SETPIPE_SZ */
 }
+
+DNSName reverseNameFromIP(const ComboAddress& ip)
+{
+  if (ip.isIPv4()) {
+    std::string result("in-addr.arpa.");
+    auto ptr = reinterpret_cast<const uint8_t*>(&ip.sin4.sin_addr.s_addr);
+    for (size_t idx = 0; idx < sizeof(ip.sin4.sin_addr.s_addr); idx++) {
+      result = std::to_string(ptr[idx]) + "." + result;
+    }
+    return DNSName(result);
+  }
+  else if (ip.isIPv6()) {
+    std::string result("ip6.arpa.");
+    auto ptr = reinterpret_cast<const uint8_t*>(&ip.sin6.sin6_addr.s6_addr[0]);
+    for (size_t idx = 0; idx < sizeof(ip.sin6.sin6_addr.s6_addr); idx++) {
+      std::stringstream stream;
+      stream << std::hex << (ptr[idx] & 0x0F);
+      stream << '.';
+      stream << std::hex << (((ptr[idx]) >> 4) & 0x0F);
+      stream << '.';
+      result = stream.str() + result;
+    }
+    return DNSName(result);
+  }
+
+  throw std::runtime_error("Calling reverseNameFromIP() for an address which is neither an IPv4 nor an IPv6");
+}
+
+static size_t getMaxHostNameSize()
+{
+#if defined(HOST_NAME_MAX)
+  return HOST_NAME_MAX;
+#endif
+
+#if defined(_SC_HOST_NAME_MAX)
+  auto tmp = sysconf(_SC_HOST_NAME_MAX);
+  if (tmp != -1) {
+    return tmp;
+  }
+#endif
+
+  /* _POSIX_HOST_NAME_MAX */
+  return 255;
+}
+
+std::string getCarbonHostName()
+{
+  std::string hostname;
+  hostname.resize(getMaxHostNameSize() + 1, 0);
+
+  if (gethostname(const_cast<char*>(hostname.c_str()), hostname.size()) != 0) {
+    throw std::runtime_error(stringerror());
+  }
+
+  boost::replace_all(hostname, ".", "_");
+  hostname.resize(strlen(hostname.c_str()));
+
+  return hostname;
+}
index b4924c427468f4130f73956ccb58802ebc25f919..5d4f528e8be85942293515df14f79ffb1dd0b878 100644 (file)
@@ -152,6 +152,7 @@ size_t readn2(int fd, void* buffer, size_t len);
 size_t readn2WithTimeout(int fd, void* buffer, size_t len, int idleTimeout, int totalTimeout=0);
 size_t writen2WithTimeout(int fd, const void * buffer, size_t len, int timeout);
 
+void toLowerInPlace(string& str);
 const string toLower(const string &upper);
 const string toLowerCanonic(const string &upper);
 bool IpToU32(const string &str, uint32_t *ip);
@@ -190,56 +191,72 @@ On 32 bits systems this means that 2147 seconds is the longest time that can be
 class DTime
 {
 public:
-  DTime(); //!< Does not set the timer for you! Saves lots of gettimeofday() calls
+  //!< Does not set the timer for you! Saves lots of gettimeofday() calls
+  DTime() = default;
   DTime(const DTime &dt) = default;
   DTime & operator=(const DTime &dt) = default;
-  time_t time();
+  inline time_t time() const;
   inline void set();  //!< Reset the timer
-  inline int udiff(); //!< Return the number of microseconds since the timer was last set.
-  inline int udiffNoReset(); //!< Return the number of microseconds since the timer was last set.
+  inline int udiff(bool reset = true); //!< Return the number of microseconds since the timer was last set.
+
+  int udiffNoReset() //!< Return the number of microseconds since the timer was last set.
+  {
+    return udiff(false);
+  }
   void setTimeval(const struct timeval& tv)
   {
     d_set=tv;
   }
-  struct timeval getTimeval()
+  struct timeval getTimeval() const
   {
     return d_set;
   }
 private:
-  struct timeval d_set;
+struct timeval d_set{0, 0};
 };
 
-inline void DTime::set()
+inline time_t DTime::time() const
 {
-  gettimeofday(&d_set,0);
+  return d_set.tv_sec;
 }
 
-inline int DTime::udiff()
+inline void DTime::set()
 {
-  int res=udiffNoReset();
-  gettimeofday(&d_set,0);
-  return res;
+  gettimeofday(&d_set, nullptr);
 }
 
-inline int DTime::udiffNoReset()
+inline int DTime::udiff(bool reset)
 {
   struct timeval now;
+  gettimeofday(&now, nullptr);
 
-  gettimeofday(&now,0);
   int ret=1000000*(now.tv_sec-d_set.tv_sec)+(now.tv_usec-d_set.tv_usec);
+
+  if (reset) {
+    d_set = now;
+  }
+
   return ret;
 }
 
-inline const string toLower(const string &upper)
+inline void toLowerInPlace(string& str)
 {
-  string reply(upper);
-  const size_t length = reply.length();
+  const size_t length = str.length();
   char c;
-  for(unsigned int i = 0; i < length; ++i) {
-    c = dns_tolower(upper[i]);
-    if( c != upper[i])
-      reply[i] = c;
+  for (size_t i = 0; i < length; ++i) {
+    c = dns_tolower(str[i]);
+    if (c != str[i]) {
+      str[i] = c;
+    }
   }
+}
+
+inline const string toLower(const string &upper)
+{
+  string reply(upper);
+
+  toLowerInPlace(reply);
+
   return reply;
 }
 
@@ -287,12 +304,6 @@ inline void unixDie(const string &why)
 }
 
 string makeHexDump(const string& str);
-struct DNSRecord;
-struct DNSZoneRecord;
-void shuffle(vector<DNSRecord>& rrs);
-void shuffle(vector<DNSZoneRecord>& rrs);
-
-void orderAndShuffle(vector<DNSRecord>& rrs);
 
 void normalizeTV(struct timeval& tv);
 const struct timeval operator+(const struct timeval& lhs, const struct timeval& rhs);
@@ -609,3 +620,7 @@ bool isSettingThreadCPUAffinitySupported();
 int mapThreadToCPUList(pthread_t tid, const std::set<int>& cpus);
 
 std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath);
+
+DNSName reverseNameFromIP(const ComboAddress& ip);
+
+std::string getCarbonHostName();
index 2d01f12f272d8088eecfdadfdbf484ab72cd943b..7afc7d4012b2d7f01aeda2dc432061766624bcf1 100644 (file)
@@ -170,7 +170,7 @@ int main()
     \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer 
 */
 
-template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::waitEvent(EventKey &key, EventVal *val, unsigned int timeoutMsec, struct timeval* now)
+template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::waitEvent(EventKey &key, EventVal *val, unsigned int timeoutMsec, const struct timeval* now)
 {
   if(d_waiters.count(key)) { // there was already an exact same waiter
     return -1;
@@ -301,7 +301,7 @@ template<class Key, class Val>void MTasker<Key,Val>::makeThread(tfunc_t *start,
     \return Returns if there is more work scheduled and recalling schedule now would be useful
       
 */
-template<class Key, class Val>bool MTasker<Key,Val>::schedule(struct timeval*  now)
+template<class Key, class Val>bool MTasker<Key,Val>::schedule(const struct timeval*  now)
 {
   if(!d_runQueue.empty()) {
     d_tid=d_runQueue.front();
index bff0eca0ff1c5f77e88b1e4a2a8f1a0cf1214c67..ed6b02d0c59ca23b3c763a58a97c8ed8bfabb48f 100644 (file)
@@ -119,12 +119,12 @@ public:
   }
 
   typedef void tfunc_t(void *); //!< type of the pointer that starts a thread 
-  int waitEvent(EventKey &key, EventVal *val=0, unsigned int timeoutMsec=0, struct timeval* now=0);
+  int waitEvent(EventKey &key, EventVal *val=nullptr, unsigned int timeoutMsec=0, const struct timeval* now=nullptr);
   void yield();
-  int sendEvent(const EventKey& key, const EventVal* val=0);
+  int sendEvent(const EventKey& key, const EventVal* val=nullptr);
   void getEvents(std::vector<EventKey>& events);
   void makeThread(tfunc_t *start, void* val);
-  bool schedule(struct timeval* now=0);
+  bool schedule(const struct timeval* now=nullptr);
   bool noProcesses() const;
   unsigned int numProcesses() const;
   int getTid() const;
index bac4ede45fccae5778034c538158641ef07b4e2e..6d658155a3df88dbd88ca6765199d88d445bc806 100644 (file)
@@ -141,11 +141,11 @@ void UDPNameserver::bindAddresses()
       }
     }
 
-#ifdef SO_REUSEPORT
-    if( d_can_reuseport )
-        if( setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) )
-          d_can_reuseport = false;
-#endif
+    if (d_can_reuseport) {
+      if (!setReusePort(s)) {
+        d_can_reuseport = false;
+      }
+    }
 
     if( ::arg().mustDo("non-local-bind") )
       Utility::setBindAny(locala.sin4.sin_family, s);
@@ -208,9 +208,7 @@ bool AddressIsUs(const ComboAddress& remote)
 
 UDPNameserver::UDPNameserver( bool additional_socket )
 {
-#ifdef SO_REUSEPORT
   d_can_reuseport = ::arg().mustDo("reuseport");
-#endif
   // Are we the main socket (false) or a rebinding using SO_REUSEPORT ?
   d_additional_socket = additional_socket;
 
index 521d5387517b0303ae71ff467bcaf72a64834bbc..f6e92df644c6659048f8cd83e7951b147cbd7b1c 100644 (file)
@@ -82,18 +82,12 @@ public:
   bool receive(DNSPacket& packet, std::string& buffer); //!< call this in a while or for(;;) loop to get packets
   void send(DNSPacket&); //!< send a DNSPacket. Will call DNSPacket::truncate() if over 512 bytes
   inline bool canReusePort() {
-#ifdef SO_REUSEPORT
     return d_can_reuseport;
-#else
-    return false;
-#endif
   };
   
 private:
   bool d_additional_socket;
-#ifdef SO_REUSEPORT
-  bool d_can_reuseport;
-#endif
+  bool d_can_reuseport{false};
   vector<int> d_sockets;
   void bindAddresses();
   vector<pollfd> d_rfds;
index dace6775c03291eeee7dc14c2247d9c5e69793f7..597b833492343320445e175d98c2ef861d9574d6 100644 (file)
@@ -52,7 +52,7 @@ ArgvMap &arg()
   return arg;
 }
 
-void usage() {
+static void usage() {
   cerr<<"Syntax: pdns_notify IP_ADDRESS/HOSTNAME[:PORT] DOMAIN"<<endl;
 }
 
index e79f8de75966accec800dc6336413b08e6f8dc10..44d2f31d9f67bb59b2d0f4a70f1ef1be070e3bae 100644 (file)
@@ -67,13 +67,13 @@ struct NotificationInFlight
 typedef map<uint16_t, NotificationInFlight> nifs_t;
 nifs_t g_nifs;
 
-void syslogFmt(const boost::format& fmt)
+static void syslogFmt(const boost::format& fmt)
 {
   cerr<<"nproxy: "<<fmt<<endl;
   syslog(LOG_WARNING, "%s", str(fmt).c_str());
 }
 
-void handleOutsideUDPPacket(int fd, boost::any&)
+static void handleOutsideUDPPacket(int fd, boost::any&)
 try
 {
   char buffer[1500];
@@ -145,7 +145,7 @@ catch(std::exception &e)
 }
 
 
-void handleInsideUDPPacket(int fd, boost::any&)
+static void handleInsideUDPPacket(int fd, boost::any&)
 try
 {
   char buffer[1500];
@@ -191,7 +191,7 @@ catch(std::exception &e)
   syslogFmt(boost::format("Error parsing packet from internal nameserver: %s") % e.what());
 }
 
-void expireOldNotifications()
+static void expireOldNotifications()
 {
   time_t limit = time(0) - 10;
   for(nifs_t::iterator iter = g_nifs.begin(); iter != g_nifs.end(); ) {
@@ -204,9 +204,19 @@ void expireOldNotifications()
   }
 }
 
-void daemonize(int null_fd);
+static void daemonize(int null_fd)
+{
+  if(fork())
+    exit(0); // bye bye
 
-void usage(po::options_description &desc) {
+  setsid();
+
+  dup2(null_fd,0); /* stdin */
+  dup2(null_fd,1); /* stderr */
+  dup2(null_fd,2); /* stderr */
+}
+
+static void usage(po::options_description &desc) {
   cerr<<"nproxy"<<endl;
   cerr<<desc<<endl;
 }
@@ -353,15 +363,3 @@ catch(PDNSException& e)
 {
   syslogFmt(boost::format("Fatal: %s") % e.reason);
 }
-
-void daemonize(int null_fd)
-{
-  if(fork())
-    exit(0); // bye bye
-
-  setsid();
-
-  dup2(null_fd,0); /* stdin */
-  dup2(null_fd,1); /* stderr */
-  dup2(null_fd,2); /* stderr */
-}
index 35ca57bf18835e84af66e78c74e08380a1561c93..d448b2eec4f462c18dc5ec3ee27408d3f0afca31 100644 (file)
@@ -37,7 +37,7 @@ StatBag S;
 typedef std::pair<string,string> nsec3;
 typedef set<nsec3> nsec3set;
 
-string nsec3Hash(const DNSName &qname, const string &salt, unsigned int iters)
+static string nsec3Hash(const DNSName &qname, const string &salt, unsigned int iters)
 {
   NSEC3PARAMRecordContent ns3prc;
   ns3prc.d_iterations = iters;
@@ -45,7 +45,7 @@ string nsec3Hash(const DNSName &qname, const string &salt, unsigned int iters)
   return toBase32Hex(hashQNameWithSalt(ns3prc, qname));
 }
 
-void proveOrDeny(const nsec3set &nsec3s, const DNSName &qname, const string &salt, unsigned int iters, set<DNSName> &proven, set<DNSName> &denied)
+static void proveOrDeny(const nsec3set &nsec3s, const DNSName &qname, const string &salt, unsigned int iters, set<DNSName> &proven, set<DNSName> &denied)
 {
   string hashed = nsec3Hash(qname, salt, iters);
 
@@ -79,7 +79,7 @@ void proveOrDeny(const nsec3set &nsec3s, const DNSName &qname, const string &sal
   }
 }
 
-void usage() {
+static void usage() {
   cerr<<"nsec3dig"<<endl;
   cerr<<"Syntax: nsec3dig IP-ADDRESS PORT QUESTION QUESTION-TYPE [recurse]\n";
 }
index 6322db0ebd65fbdf6a3315d39efe170122f5d16d..983f39fb6b3c995c73da4c2a3e911cadd650cfa2 100644 (file)
@@ -29,6 +29,7 @@
 #if defined(HAVE_LIBCRYPTO_ED25519) || defined(HAVE_LIBCRYPTO_ED448)
 #include <openssl/evp.h>
 #endif
+#include <openssl/bn.h>
 #include <openssl/sha.h>
 #include <openssl/rand.h>
 #include <openssl/rsa.h>
 
 #if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL)
 /* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */
-static pthread_mutex_t *openssllocks;
+
+#include "lock.hh"
+static std::vector<std::mutex> openssllocks;
 
 extern "C" {
-void openssl_pthreads_locking_callback(int mode, int type, const char *file, int line)
+static void openssl_pthreads_locking_callback(int mode, int type, const char *file, int line)
 {
   if (mode & CRYPTO_LOCK) {
-    pthread_mutex_lock(&(openssllocks[type]));
+    openssllocks.at(type).lock();
 
-  }else {
-    pthread_mutex_unlock(&(openssllocks[type]));
+  } else {
+    openssllocks.at(type).unlock();
   }
 }
 
-unsigned long openssl_pthreads_id_callback()
+static unsigned long openssl_pthreads_id_callback(void)
 {
   return (unsigned long)pthread_self();
 }
@@ -61,24 +64,15 @@ unsigned long openssl_pthreads_id_callback()
 
 void openssl_thread_setup()
 {
-  openssllocks = (pthread_mutex_t*)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
-
-  for (int i = 0; i < CRYPTO_num_locks(); i++)
-    pthread_mutex_init(&(openssllocks[i]), NULL);
-
-  CRYPTO_set_id_callback(openssl_pthreads_id_callback);
-  CRYPTO_set_locking_callback(openssl_pthreads_locking_callback);
+  openssllocks = std::vector<std::mutex>(CRYPTO_num_locks());
+  CRYPTO_set_id_callback(&openssl_pthreads_id_callback);
+  CRYPTO_set_locking_callback(&openssl_pthreads_locking_callback);
 }
 
 void openssl_thread_cleanup()
 {
-  CRYPTO_set_locking_callback(NULL);
-
-  for (int i=0; i<CRYPTO_num_locks(); i++) {
-    pthread_mutex_destroy(&(openssllocks[i]));
-  }
-
-  OPENSSL_free(openssllocks);
+  CRYPTO_set_locking_callback(nullptr);
+  openssllocks.clear();
 }
 
 #ifndef HAVE_RSA_GET0_KEY
index 7dd0c8828e5f03a3b34414a25ae966e391c74dd5..da3b62190d34af9e2ab0baa0ea5fab9bf314371d 100644 (file)
 #include "ednsoptions.hh"
 #include "misc.hh"
 #include "iputils.hh"
+#include "views.hh"
 
 class PacketCache : public boost::noncopyable
 {
 public:
-  static uint32_t canHashPacket(const std::string& packet, uint16_t* ecsBegin, uint16_t* ecsEnd)
+
+  /* hash the packet from the provided position, which should point right after tje qname. This skips:
+     - the query ID ;
+     - EDNS Cookie options, if any ;
+     - EDNS Client Subnet options, if any and skipECS is true.
+  */
+  static uint32_t hashAfterQname(const pdns_string_view& packet, uint32_t currentHash, size_t pos, bool skipECS)
   {
-    uint32_t ret = 0;
-    ret = burtle(reinterpret_cast<const unsigned char*>(packet.c_str()) + 2, sizeof(dnsheader) - 2, ret); // rest of dnsheader, skip id
-    size_t packetSize = packet.size();
-    size_t pos = sizeof(dnsheader);
-    const char* end = packet.c_str() + packetSize;
-    const char* p = packet.c_str() + pos;
-
-    for(; p < end && *p; ++p, ++pos) { // 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
-
-    const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.c_str());
-    const char* skipBegin = p;
-    const char* skipEnd = p;
-    if (ecsBegin != nullptr && ecsEnd != nullptr) {
-      *ecsBegin = 0;
-      *ecsEnd = 0;
-    }
-    /* we need at least 1 (final empty label) + 2 (QTYPE) + 2 (QCLASS)
+    const size_t packetSize = packet.size();
+    assert(packetSize >= sizeof(dnsheader));
+
+    /* we need at least 2 (QTYPE) + 2 (QCLASS)
+
        + OPT root label (1), type (2), class (2) and ttl (4)
        + the OPT RR rdlen (2)
-       = 16
+       = 15
     */
-    if(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(const_cast<char*>(reinterpret_cast<const char*>(p)) + 14, end - (p + 14), EDNSOptionCode::ECS, &optionBegin, &optionLen);
-      if (res == 0) {
-        skipBegin = optionBegin;
-        skipEnd = optionBegin + optionLen;
-        if (ecsBegin != nullptr && ecsEnd != nullptr) {
-          *ecsBegin = optionBegin - packet.c_str();
-          *ecsEnd = *ecsBegin + optionLen;
+    const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
+    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= packetSize) {
+      if (packetSize > pos) {
+        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
+      }
+      return currentHash;
+    }
+
+    currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 15, currentHash);
+    /* skip the qtype (2), qclass (2) */
+    /* root label (1), type (2), class (2) and ttl (4) */
+    /* already hashed above */
+    pos += 13;
+
+    const uint16_t rdLen = ((static_cast<uint16_t>(packet.at(pos)) * 256) + static_cast<uint16_t>(packet.at(pos + 1)));
+    /* skip the rd length */
+    /* already hashed above */
+    pos += 2;
+
+    if (rdLen > (packetSize - pos)) {
+      if (pos < packetSize) {
+        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
+      }
+      return currentHash;
+    }
+
+    uint16_t rdataRead = 0;
+    uint16_t optionCode;
+    uint16_t optionLen;
+
+    while (pos < packetSize && rdataRead < rdLen && getNextEDNSOption(&packet.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
+      if (optionLen > (rdLen - rdataRead - 4)) {
+        if (packetSize > pos) {
+          currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
         }
+        return currentHash;
       }
+
+      bool skip = false;
+      if (optionCode == EDNSOptionCode::COOKIE) {
+        skip = true;
+      }
+      else if (optionCode == EDNSOptionCode::ECS) {
+        if (skipECS) {
+          skip = true;
+        }
+      }
+
+      if (!skip) {
+        /* hash the option code, length and content */
+        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4 + optionLen, currentHash);
+      }
+      else {
+        /* hash the option code and length */
+        currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), 4, currentHash);
+      }
+
+      pos += 4 + optionLen;
+      rdataRead += 4 + optionLen;
     }
-    if (skipBegin > p) {
-      ret = burtle(reinterpret_cast<const unsigned char*>(p), skipBegin-p, ret);
+
+    if (pos < packetSize) {
+      currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(pos)), packetSize - pos, currentHash);
     }
-    if (skipEnd < end) {
-      ret = burtle(reinterpret_cast<const unsigned char*>(skipEnd), end-skipEnd, ret);
+
+    return currentHash;
+  }
+
+  static uint32_t hashHeaderAndQName(const std::string& packet, size_t& pos)
+  {
+    uint32_t currentHash = 0;
+    const size_t packetSize = packet.size();
+    assert(packetSize >= sizeof(dnsheader));
+    currentHash = burtle(reinterpret_cast<const unsigned char*>(&packet.at(2)), sizeof(dnsheader) - 2, currentHash); // rest of dnsheader, skip id
+    pos = sizeof(dnsheader);
+
+    for (; pos < packetSize; ) {
+      const unsigned char labelLen = static_cast<unsigned char>(packet.at(pos));
+      currentHash = burtle(&labelLen, 1, currentHash);
+      ++pos;
+      if (labelLen == 0) {
+        break;
+      }
+
+      for (size_t idx = 0; idx < labelLen && pos < packetSize; ++idx, ++pos) {
+        const unsigned char l = dns_tolower(packet.at(pos));
+        currentHash = burtle(&l, 1, currentHash);
+      }
     }
 
-    return ret;
+    return currentHash;
   }
 
-  static uint32_t canHashPacket(const std::string& packet)
+  /* hash the packet from the beginning, including the qname. This skips:
+     - the query ID ;
+     - EDNS Cookie options, if any ;
+     - EDNS Client Subnet options, if any and skipECS is true.
+  */
+  static uint32_t canHashPacket(const std::string& packet, bool skipECS)
   {
-    uint32_t ret = 0;
-    ret = burtle(reinterpret_cast<const unsigned char*>(packet.c_str()) + 2, sizeof(dnsheader) - 2, ret); // rest of dnsheader, skip id
+    size_t pos = 0;
+    uint32_t currentHash = hashHeaderAndQName(packet, pos);
     size_t packetSize = packet.size();
-    size_t pos = sizeof(dnsheader);
-    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
 
-    if (p < end) {
-      ret = burtle(reinterpret_cast<const unsigned char*>(p), end-p, ret);
+    if (pos >= packetSize) {
+      return currentHash;
     }
 
-    return ret;
+    return hashAfterQname(packet, currentHash, pos, skipECS);
   }
 
   static bool queryHeaderMatches(const std::string& cachedQuery, const std::string& query)
@@ -108,41 +165,80 @@ public:
     return (cachedQuery.compare(/* skip the ID */ 2, sizeof(dnsheader) - 2, query, 2, sizeof(dnsheader) - 2) == 0);
   }
 
-  static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname)
+  static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, const std::unordered_set<uint16_t>& optionsToIgnore)
   {
+    const size_t querySize = query.size();
+    const size_t cachedQuerySize = cachedQuery.size();
+    if (querySize != cachedQuerySize) {
+      return false;
+    }
+
     if (!queryHeaderMatches(cachedQuery, query)) {
       return false;
     }
 
     size_t pos = sizeof(dnsheader) + qname.wirelength();
 
-    return (cachedQuery.compare(pos, cachedQuery.size() - pos, query, pos, query.size() - pos) == 0);
-  }
+    /* we need at least 2 (QTYPE) + 2 (QCLASS)
+       + OPT root label (1), type (2), class (2) and ttl (4)
+       + the OPT RR rdlen (2)
+       = 15
+    */
+    const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(query.data());
+    if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) != 1 || (pos + 15) >= querySize || optionsToIgnore.empty()) {
+      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
+    }
 
-  static bool queryMatches(const std::string& cachedQuery, const std::string& query, const DNSName& qname, uint16_t ecsBegin, uint16_t ecsEnd)
-  {
-    if (!queryHeaderMatches(cachedQuery, query)) {
+    /* compare up to the first option, if any */
+    if (cachedQuery.compare(pos, 15, query, pos, 15) != 0) {
       return false;
     }
 
-    size_t pos = sizeof(dnsheader) + qname.wirelength();
+    /* skip the qtype (2), qclass (2) */
+    /* root label (1), type (2), class (2) and ttl (4) */
+    /* already compared above */
+    pos += 13;
 
-    if (ecsBegin != 0 && ecsBegin >= pos && ecsEnd > ecsBegin) {
-      if (cachedQuery.compare(pos, ecsBegin - pos, query, pos, ecsBegin - pos) != 0) {
-        return false;
+    const uint16_t rdLen = ((static_cast<unsigned char>(query.at(pos)) * 256) + static_cast<unsigned char>(query.at(pos + 1)));
+    /* skip the rd length */
+    /* already compared above */
+    pos += sizeof(uint16_t);
+
+    if (rdLen > (querySize - pos)) {
+      /* something is wrong, let's just compare everything */
+      return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
+    }
+
+    uint16_t rdataRead = 0;
+    uint16_t optionCode;
+    uint16_t optionLen;
+
+    while (pos < querySize && rdataRead < rdLen && getNextEDNSOption(&query.at(pos), rdLen - rdataRead, optionCode, optionLen)) {
+      if (optionLen > (rdLen - rdataRead)) {
+        return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
       }
 
-      if (cachedQuery.compare(ecsEnd, cachedQuery.size() - ecsEnd, query, ecsEnd, query.size() - ecsEnd) != 0) {
+      /* compare the option code and length */
+      if (cachedQuery.compare(pos, 4, query, pos, 4) != 0) {
         return false;
       }
-    }
-    else {
-      if (cachedQuery.compare(pos, cachedQuery.size() - pos, query, pos, query.size() - pos) != 0) {
-        return false;
+      pos += 4;
+      rdataRead += 4;
+
+      if (optionLen > 0 && optionsToIgnore.count(optionCode) == 0) {
+        if (cachedQuery.compare(pos, optionLen, query, pos, optionLen) != 0) {
+          return false;
+        }
       }
+      pos += optionLen;
+      rdataRead += optionLen;
+    }
+
+    if (pos >= querySize) {
+        return true;
     }
 
-    return true;
+    return cachedQuery.compare(pos, cachedQuerySize - pos, query, pos, querySize - pos) == 0;
   }
 
 };
index 6defb6f1f210d909b5b9bcdc2dfdadb2d02e2e39..c8cfb165243a276fa76684889da68671886e99a8 100644 (file)
@@ -63,7 +63,6 @@ PacketHandler::PacketHandler():B(s_programname), d_dk(&B)
   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"];
   if(fname.empty())
   {
@@ -439,61 +438,45 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const SOAData& sd, const DNSNa
   return haveSomething;
 }
 
-/** dangling is declared true if we were unable to resolve everything */
-int PacketHandler::doAdditionalProcessingAndDropAA(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& soadata, bool retargeted)
-{
-  DNSZoneRecord rr;
-  SOAData sd;
-  sd.db=0;
-
-  if(p.qtype.getCode()!=QType::AXFR) { // this packet needs additional processing
-    // we now have a copy, push_back on packet might reallocate!
-    auto& records = r->getRRS();
-    vector<DNSZoneRecord> toAdd;
-
-    for(auto i = records.cbegin() ; i!= records.cend(); ++i) {
-      if(i->dr.d_place==DNSResourceRecord::ADDITIONAL ||
-         !(i->dr.d_type==QType::MX || i->dr.d_type==QType::NS || i->dr.d_type==QType::SRV))
-        continue;
-
-      if(r->d.aa && i->dr.d_name.countLabels() && i->dr.d_type==QType::NS && !B.getSOA(i->dr.d_name,sd) && !retargeted) { // drop AA in case of non-SOA-level NS answer, except for root referral
-        r->setA(false);
-        //        i->d_place=DNSResourceRecord::AUTHORITY; // XXX FIXME
-      }
-
-      DNSName lookup;
-
-      if(i->dr.d_type == QType::MX)
-        lookup = getRR<MXRecordContent>(i->dr)->d_mxname;
-      else if(i->dr.d_type == QType::SRV)
-        lookup = getRR<SRVRecordContent>(i->dr)->d_target;
-      else if(i->dr.d_type == QType::NS) 
-        lookup = getRR<NSRecordContent>(i->dr)->getNS();
-      else
-        continue;
-
-      B.lookup(QType(d_doIPv6AdditionalProcessing ? QType::ANY : QType::A), lookup, soadata.domain_id, &p);
 
-      while(B.get(rr)) {
-        if(rr.dr.d_type != QType::A && rr.dr.d_type!=QType::AAAA)
-          continue;
-        if(!rr.dr.d_name.isPartOf(soadata.qname)) {
-          // FIXME we might still pass on the record if it is occluded and the
-          // backend uses a single id for all zones
+void PacketHandler::doAdditionalProcessing(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& soadata)
+{
+  DNSName content;
+  std::unordered_set<DNSName> lookup;
+  const auto& rrs = r->getRRS();
+  lookup.reserve(rrs.size());
+  for(auto& rr : rrs) {
+    if(rr.dr.d_place != DNSResourceRecord::ADDITIONAL) {
+      switch(rr.dr.d_type) {
+        case QType::NS:
+          content=std::move(getRR<NSRecordContent>(rr.dr)->getNS());
+          break;
+        case QType::MX:
+          content=std::move(getRR<MXRecordContent>(rr.dr)->d_mxname);
+          break;
+        case QType::SRV:
+          content=std::move(getRR<SRVRecordContent>(rr.dr)->d_target);
+          break;
+        default:
           continue;
-        }
-        rr.dr.d_place=DNSResourceRecord::ADDITIONAL;
-        toAdd.push_back(rr);
+      }
+      if(content.isPartOf(soadata.qname)) {
+        lookup.emplace(std::move(content));
       }
     }
+  }
 
-    for(auto& rec : toAdd) {
-      r->addRecord(std::move(rec));
+  DNSZoneRecord dzr;
+  for(const auto& name : lookup) {
+    B.lookup(QType(QType::ANY), name, soadata.domain_id, &p);
+    while(B.get(dzr)) {
+      if(dzr.dr.d_type == QType::A || dzr.dr.d_type == QType::AAAA) {
+        dzr.dr.d_place=DNSResourceRecord::ADDITIONAL;
+        r->addRecord(std::move(dzr));
+      }
     }
-    
-    //records.insert(records.end(), toAdd.cbegin(), toAdd.cend()); // would be faster, but no dedup
   }
-  return 1;
 }
 
 
@@ -507,16 +490,19 @@ void PacketHandler::emitNSEC(std::unique_ptr<DNSPacket>& r, const SOAData& sd, c
   if(sd.qname == name) {
     nrc.set(QType::SOA); // 1dfd8ad SOA can live outside the records table
     auto keyset = d_dk.getKeys(name);
-    if (!keyset.empty()) {
-      nrc.set(QType::DNSKEY);
-      string publishCDNSKEY;
-      d_dk.getPublishCDNSKEY(name, publishCDNSKEY);
-      if (publishCDNSKEY == "1")
-        nrc.set(QType::CDNSKEY);
-      string publishCDS;
-      d_dk.getPublishCDS(name, publishCDS);
-      if (! publishCDS.empty())
-        nrc.set(QType::CDS);
+    for(const auto& value: keyset) {
+      if (value.second.published) {
+        nrc.set(QType::DNSKEY);
+        string publishCDNSKEY;
+        d_dk.getPublishCDNSKEY(name, publishCDNSKEY);
+        if (publishCDNSKEY == "1")
+          nrc.set(QType::CDNSKEY);
+        string publishCDS;
+        d_dk.getPublishCDS(name, publishCDS);
+        if (! publishCDS.empty())
+          nrc.set(QType::CDS);
+        break;
+      }
     }
   }
 
@@ -559,16 +545,19 @@ void PacketHandler::emitNSEC3(std::unique_ptr<DNSPacket>& r, const SOAData& sd,
       n3rc.set(QType::SOA); // 1dfd8ad SOA can live outside the records table
       n3rc.set(QType::NSEC3PARAM);
       auto keyset = d_dk.getKeys(name);
-      if (!keyset.empty()) {
-        n3rc.set(QType::DNSKEY);
-        string publishCDNSKEY;
-        d_dk.getPublishCDNSKEY(name, publishCDNSKEY);
-        if (publishCDNSKEY == "1")
-          n3rc.set(QType::CDNSKEY);
-        string publishCDS;
-        d_dk.getPublishCDS(name, publishCDS);
-        if (! publishCDS.empty())
-          n3rc.set(QType::CDS);
+      for(const auto& value: keyset) {
+        if (value.second.published) {
+          n3rc.set(QType::DNSKEY);
+          string publishCDNSKEY;
+          d_dk.getPublishCDNSKEY(name, publishCDNSKEY);
+          if (publishCDNSKEY == "1")
+            n3rc.set(QType::CDNSKEY);
+          string publishCDS;
+          d_dk.getPublishCDS(name, publishCDS);
+          if (! publishCDS.empty())
+            n3rc.set(QType::CDS);
+          break;
+        }
       }
     }
 
@@ -847,7 +836,7 @@ int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName&
     return RCode::Refused;
   }
   try {
-    db->createSlaveDomain(p.getRemote().toString(), p.qdomain, nameserver, account);
+    db->createSlaveDomain(remote.toString(), p.qdomain, nameserver, account);
     if (tsigkeyname.empty() == false) {
       vector<string> meta;
       meta.push_back(tsigkeyname.toStringNoDot());
@@ -864,7 +853,7 @@ int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName&
 
 int PacketHandler::processNotify(const DNSPacket& p)
 {
-  /* now what? 
+  /* now what?
      was this notification from an approved address?
      was this notification approved by TSIG?
      We determine our internal SOA id (via UeberBackend)
@@ -942,7 +931,7 @@ int PacketHandler::processNotify(const DNSPacket& p)
   }
 
   if(::arg().mustDo("slave")) {
-    g_log<<Logger::Debug<<"Queueing slave check for "<<p.qdomain<<endl;
+    g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemote()<<" - queueing check"<<endl;
     Communicator.addSlaveCheckRequest(di, p.d_remote);
   }
   return 0;
@@ -1161,12 +1150,6 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
       return r;
     } else {
       getTSIGHashEnum(trc.d_algoName, p.d_tsig_algo);
-      if (p.d_tsig_algo == TSIG_GSS) {
-        GssContext gssctx(keyname);
-        if (!gssctx.getPeerPrincipal(p.d_peer_principal)) {
-          g_log<<Logger::Warning<<"Failed to extract peer principal from GSS context with keyname '"<<keyname<<"'"<<endl;
-        }
-      }
     }
     p.setTSIGDetails(trc, keyname, secret, trc.d_mac); // this will get copied by replyPacket()
     noCache=true;
@@ -1440,7 +1423,7 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
       bool doReferral = true;
       if(d_dk.doesDNSSEC()) {
         for(auto& loopRR: rrset) {
-          // In a dnssec capable backend auth=true means, there is no delagation at
+          // In a dnssec capable backend auth=true means, there is no delegation at
           // or above this qname in this zone (for DS queries). Without a delegation,
           // at or above this level, it is pointless to search for refferals.
           if(loopRR.auth) {
@@ -1546,9 +1529,7 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
     }
     
   sendit:;
-    if(doAdditionalProcessingAndDropAA(p, r, sd, retargetcount)<0) {
-      return 0;
-    }
+    doAdditionalProcessing(p, r, sd);
 
     for(const auto& loopRR: r->getRRS()) {
       if(loopRR.scopeMask) {
index 48cdfbabdad8adafa95bdcadb01b0ac0342c2070..36201d1047974be93041509725b940f231603c16 100644 (file)
@@ -28,7 +28,6 @@
 #include "packetcache.hh"
 #include "dnsseckeeper.hh"
 #include "lua-auth4.hh"
-#include "gss_context.hh"
 
 #include "namespaces.hh"
 
@@ -74,7 +73,7 @@ private:
   bool addCDNSKEY(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& sd);
   bool addCDS(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& sd);
   bool addNSEC3PARAM(const DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& sd);
-  int doAdditionalProcessingAndDropAA(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& sd, bool retargeted);
+  void doAdditionalProcessing(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const SOAData& sd);
   void addNSECX(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName &target, const DNSName &wildcard, const DNSName &auth, int mode);
   void addNSEC(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName &target, const DNSName &wildcard, const DNSName& auth, int mode);
   void addNSEC3(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName &target, const DNSName &wildcard, const DNSName& auth, const NSEC3PARAMRecordContent& nsec3param, bool narrow, int mode);
@@ -85,7 +84,7 @@ private:
   uint performUpdate(const string &msgPrefix, const DNSRecord *rr, DomainInfo *di, bool isPresigned, bool* narrow, bool* haveNSEC3, NSEC3PARAMRecordContent *ns3pr, bool *updatedSerial);
   int checkUpdatePrescan(const DNSRecord *rr);
   int checkUpdatePrerequisites(const DNSRecord *rr, DomainInfo *di);
-  void increaseSerial(const string &msgPrefix, const DomainInfo *di, bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr);
+  void increaseSerial(const string &msgPrefix, const DomainInfo *di, const string& soaEditSetting, bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr);
 
   void makeNXDomain(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, const SOAData& sd);
   void makeNOError(DNSPacket& p, std::unique_ptr<DNSPacket>& r, const DNSName& target, const DNSName& wildcard, const SOAData& sd, int mode);
@@ -102,9 +101,8 @@ private:
   void tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>& r); //<! process TKEY record, and adds TKEY record to (r)eply, or error code.
 
   static AtomicCounter s_count;
-  static pthread_mutex_t s_rfc2136lock;
+  static std::mutex s_rfc2136lock;
   bool d_logDNSDetails;
-  bool d_doIPv6AdditionalProcessing;
   bool d_doDNAME;
   bool d_doExpandALIAS;
   bool d_dnssec;
@@ -115,4 +113,3 @@ private:
   DNSSECKeeper d_dk; // B is shared with DNSSECKeeper
 };
 
-std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd);
index 1ce670228ab1eb346d927c62480e7c5d04e0ccc1..3c43b076dbed3ef0a9dc0b292887fdc2ff82bf8d 100644 (file)
@@ -7,6 +7,7 @@ After=network-online.target mysqld.service postgresql.service slapd.service mari
 
 [Service]
 ExecStart=@sbindir@/pdns_server --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no
+SyslogIdentifier=pdns_server
 User=@service_user@
 Group=@service_group@
 Type=notify
@@ -22,8 +23,12 @@ LockPersonality=true
 NoNewPrivileges=true
 PrivateDevices=true
 PrivateTmp=true
+# Setting PrivateUsers=true prevents us from opening our sockets
+ProtectClock=true
 ProtectControlGroups=true
 ProtectHome=true
+ProtectHostname=true
+ProtectKernelLogs=true
 ProtectKernelModules=true
 ProtectKernelTunables=true
 # ProtectSystem=full will disallow write access to /etc and /usr, possibly
@@ -32,6 +37,7 @@ ProtectSystem=full
 RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
 RestrictNamespaces=true
 RestrictRealtime=true
+RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
 
index 8ad11b56ac62011df7edd1ae2cc2fff0bec15cae..1a9f8d416dcac66cc453c98df59d8e7f29d3b8e7 100644 (file)
 #include "rec-lua-conf.hh"
 #include "ednsoptions.hh"
 #include "gettime.hh"
+#include "proxy-protocol.hh"
 #include "pubsuffix.hh"
+#include "shuffle.hh"
 #ifdef NOD_ENABLED
 #include "nod.hh"
 #endif /* NOD_ENABLED */
+#include "query-local-address.hh"
 
 #include "rec-protobuf.hh"
 #include "rec-snmp.hh"
@@ -127,8 +130,8 @@ static thread_local uint64_t t_frameStreamServersGeneration;
 #endif /* HAVE_FSTRM */
 
 thread_local std::unique_ptr<MT_t> MT; // the big MTasker
-std::unique_ptr<MemRecursorCache> s_RC;
-
+std::unique_ptr<MemRecursorCache> g_recCache;
+std::unique_ptr<NegCache> g_negCache;
 
 thread_local std::unique_ptr<RecursorPacketCache> t_packetCache;
 thread_local FDMultiplexer* t_fdm{nullptr};
@@ -170,6 +173,7 @@ struct RecThreadInfo
   std::thread thread;
   MT_t* mt{nullptr};
   uint64_t numberOfDistributedQueries{0};
+  int exitCode{0};
   /* handle the web server, carbon, statistics and the control channel */
   bool isHandler{false};
   /* accept incoming queries (and distributes them to the workers if pdns-distributes-queries is set) */
@@ -189,14 +193,16 @@ static deferredAdd_t g_deferredAdds;
 typedef vector<int> tcpListenSockets_t;
 typedef map<int, ComboAddress> listenSocketsAddresses_t; // is shared across all threads right now
 
-static const ComboAddress g_local4("0.0.0.0"), g_local6("::");
 static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now
 static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism
-static vector<ComboAddress> g_localQueryAddresses4, g_localQueryAddresses6;
 static AtomicCounter counter;
 static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
 static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
 static NetmaskGroup g_XPFAcl;
+static NetmaskGroup g_proxyProtocolACL;
+static boost::optional<ComboAddress> g_dns64Prefix{boost::none};
+static DNSName g_dns64PrefixReverse;
+static size_t g_proxyProtocolMaximumSize;
 static size_t g_tcpMaxQueriesPerConn;
 static size_t s_maxUDPQueriesPerRound;
 static uint64_t g_latencyStatSize;
@@ -268,7 +274,7 @@ struct DNSComboWriter {
   {
   }
 
-  DNSComboWriter(const std::string& query, const struct timeval& now, std::vector<std::string>&& policyTags, LuaContext::LuaObject&& data, std::vector<DNSRecord>&& records): d_mdp(true, query), d_now(now), d_query(query), d_policyTags(std::move(policyTags)), d_records(std::move(records)), d_data(std::move(data))
+  DNSComboWriter(const std::string& query, const struct timeval& now, std::unordered_set<std::string>&& policyTags, LuaContext::LuaObject&& data, std::vector<DNSRecord>&& records): d_mdp(true, query), d_now(now), d_query(query), d_policyTags(std::move(policyTags)), d_records(std::move(records)), d_data(std::move(data))
   {
   }
 
@@ -305,6 +311,7 @@ struct DNSComboWriter {
     return d_source.toStringWithPort() + " (proxied by " + d_remote.toStringWithPort() + ")";
   }
 
+  std::vector<ProxyProtocolValue> d_proxyProtocolValues;
   MOADNSParser d_mdp;
   struct timeval d_now;
   /* Remote client, might differ from d_source
@@ -327,7 +334,8 @@ struct DNSComboWriter {
   struct timeval d_kernelTimestamp{0,0};
 #endif
   std::string d_query;
-  std::vector<std::string> d_policyTags;
+  std::unordered_set<std::string> d_policyTags;
+  std::string d_routingTag;
   std::vector<DNSRecord> d_records;
   LuaContext::LuaObject d_data;
   EDNSSubnetOpts d_ednssubnet;
@@ -337,8 +345,6 @@ struct DNSComboWriter {
   unsigned int d_tag{0};
   uint32_t d_qhash{0};
   uint32_t d_ttlCap{std::numeric_limits<uint32_t>::max()};
-  uint16_t d_ecsBegin{0};
-  uint16_t d_ecsEnd{0};
   bool d_variable{false};
   bool d_ecsFound{false};
   bool d_ecsParsed{false};
@@ -363,11 +369,6 @@ unsigned int getRecursorThreadId()
   return t_id;
 }
 
-int getMTaskerTID()
-{
-  return MT->getTid();
-}
-
 static bool isDistributorThread()
 {
   if (t_id == 0) {
@@ -460,7 +461,7 @@ string GenUDPQueryResponse(const ComboAddress& dest, const string& query)
 {
   Socket s(dest.sin4.sin_family, SOCK_DGRAM);
   s.setNonBlocking();
-  ComboAddress local = getQueryLocalAddress(dest.sin4.sin_family, 0);
+  ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
   
   s.bind(local);
   s.connect(dest);
@@ -486,28 +487,6 @@ string GenUDPQueryResponse(const ComboAddress& dest, const string& query)
   return data;
 }
 
-//! pick a random query local address
-ComboAddress getQueryLocalAddress(int family, uint16_t port)
-{
-  ComboAddress ret;
-  if(family==AF_INET) {
-    if(g_localQueryAddresses4.empty())
-      ret = g_local4;
-    else
-      ret = g_localQueryAddresses4[dns_random(g_localQueryAddresses4.size())];
-    ret.sin4.sin_port = htons(port);
-  }
-  else {
-    if(g_localQueryAddresses6.empty())
-      ret = g_local6;
-    else
-      ret = g_localQueryAddresses6[dns_random(g_localQueryAddresses6.size())];
-
-    ret.sin6.sin6_port = htons(port);
-  }
-  return ret;
-}
-
 static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t&);
 
 static void setSocketBuffer(int fd, int optname, uint32_t size)
@@ -623,7 +602,7 @@ private:
         while (s_avoidUdpSourcePorts.count(port));
       }
 
-      sin=getQueryLocalAddress(family, port); // does htons for us
+      sin=pdns::getQueryLocalAddress(family, port); // does htons for us
 
       if (::bind(ret, (struct sockaddr *)&sin, sin.getSocklen()) >= 0)
         break;
@@ -697,7 +676,7 @@ int asendto(const char *data, size_t len, int flags,
 
 // -1 is error, 0 is timeout, 1 is success
 int arecvfrom(std::string& packet, int flags, const ComboAddress& fromaddr, size_t *d_len,
-              uint16_t id, const DNSName& domain, uint16_t qtype, int fd, struct timeval* now)
+              uint16_t id, const DNSName& domain, uint16_t qtype, int fd, const struct timeval* now)
 {
   static optional<unsigned int> nearMissLimit;
   if(!nearMissLimit)
@@ -809,7 +788,7 @@ catch(...)
 }
 
 #ifdef HAVE_PROTOBUF
-static void protobufLogQuery(uint8_t maskV4, uint8_t maskV6, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::vector<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName)
+static void protobufLogQuery(uint8_t maskV4, uint8_t maskV6, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName)
 {
   if (!t_protobufServers) {
     return;
@@ -900,6 +879,79 @@ static bool addRecordToPacket(DNSPacketWriter& pw, const DNSRecord& rec, uint32_
   return true;
 }
 
+enum class PolicyResult : uint8_t { NoAction, HaveAnswer, Drop };
+
+static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& dc, SyncRes& sr, int& res, vector<DNSRecord>& ret, DNSPacketWriter& pw)
+{
+  /* don't account truncate actions for TCP queries, since they are not applied */
+  if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !dc->d_tcp) {
+    ++g_stats.policyResults[appliedPolicy.d_kind];
+  }
+
+  if (sr.doLog() &&  appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+    g_log << Logger::Warning << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype).getName() << appliedPolicy.getLogString() << endl;
+  }
+
+  switch (appliedPolicy.d_kind) {
+
+  case DNSFilterEngine::PolicyKind::NoAction:
+      return PolicyResult::NoAction;
+
+  case DNSFilterEngine::PolicyKind::Drop:
+    ++g_stats.policyDrops;
+    return PolicyResult::Drop;
+
+  case DNSFilterEngine::PolicyKind::NXDOMAIN:
+    ret.clear();
+    res = RCode::NXDomain;
+    return PolicyResult::HaveAnswer;
+
+  case DNSFilterEngine::PolicyKind::NODATA:
+    ret.clear();
+    res = RCode::NoError;
+    return PolicyResult::HaveAnswer;
+
+  case DNSFilterEngine::PolicyKind::Truncate:
+    if (!dc->d_tcp) {
+      ret.clear();
+      res = RCode::NoError;
+      pw.getHeader()->tc = 1;
+      return PolicyResult::HaveAnswer;
+    }
+    return PolicyResult::NoAction;
+
+  case DNSFilterEngine::PolicyKind::Custom:
+    res = RCode::NoError;
+    {
+      auto spoofed = appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+      for (auto& dr : spoofed) {
+        ret.push_back(dr);
+        try {
+          handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+        }
+        catch (const ImmediateServFailException& e) {
+          if (g_logCommonErrors) {
+            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
+          }
+          res = RCode::ServFail;
+          break;
+        }
+        catch (const PolicyHitException& e) {
+          if (g_logCommonErrors) {
+            g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << dc->d_mdp.d_qname << "' because another RPZ policy was hit" << endl;
+          }
+          res = RCode::ServFail;
+          break;
+        }
+      }
+
+      return PolicyResult::HaveAnswer;
+    }
+  }
+
+  return PolicyResult::NoAction;
+}
+
 #ifdef HAVE_PROTOBUF
 static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobufServers(const ProtobufExportConfig& config)
 {
@@ -1058,23 +1110,14 @@ static bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLoc
 #ifdef NOD_ENABLED
 static bool nodCheckNewDomain(const DNSName& dname)
 {
-  static const QType qt(QType::A);
-  static const uint16_t qc(QClass::IN);
   bool ret = false;
   // First check the (sub)domain isn't whitelisted for NOD purposes
   if (!g_nodDomainWL.check(dname)) {
-    // Now check the NODDB (note this is probablistic so can have FNs/FPs)
+    // Now check the NODDB (note this is probabilistic so can have FNs/FPs)
     if (t_nodDBp && t_nodDBp->isNewDomain(dname)) {
       if (g_nodLog) {
         // This should probably log to a dedicated log file
-        g_log<<Logger::Notice<<"Newly observed domain nod="<<dname.toLogString()<<endl;
-      }
-      if (!(g_nodLookupDomain.isRoot())) {
-        // Send a DNS A query to <domain>.g_nodLookupDomain
-        DNSName qname = dname;
-        vector<DNSRecord> dummy;
-        qname += g_nodLookupDomain;
-        directResolve(qname, qt, qc, dummy);
+        g_log<<Logger::Notice<<"Newly observed domain nod="<<dname<<endl;
       }
       ret = true;
     }
@@ -1082,6 +1125,18 @@ static bool nodCheckNewDomain(const DNSName& dname)
   return ret;
 }
 
+static void sendNODLookup(const DNSName& dname)
+{
+  if (!(g_nodLookupDomain.isRoot())) {
+    // Send a DNS A query to <domain>.g_nodLookupDomain
+    static const QType qt(QType::A);
+    static const uint16_t qc(QClass::IN);
+    DNSName qname = dname + g_nodLookupDomain;
+    vector<DNSRecord> dummy;
+    directResolve(qname, qt, qc, dummy);
+  }
+}
+
 static bool udrCheckUniqueDNSRecord(const DNSName& dname, uint16_t qtype, const DNSRecord& record)
 {
   bool ret = false;
@@ -1093,7 +1148,7 @@ static bool udrCheckUniqueDNSRecord(const DNSName& dname, uint16_t qtype, const
     if (t_udrDBp && t_udrDBp->isUniqueResponse(ss.str())) {
       if (g_udrLog) {  
         // This should also probably log to a dedicated file. 
-        g_log<<Logger::Notice<<"Unique response observed: qname="<<dname.toLogString()<<" qtype="<<QType(qtype).getName()<< " rrtype=" << QType(record.d_type).getName() << " rrname=" << record.d_name.toLogString() << " rrcontent=" << record.d_content->getZoneRepresentation() << endl;
+        g_log<<Logger::Notice<<"Unique response observed: qname="<<dname<<" qtype="<<QType(qtype).getName()<< " rrtype=" << QType(record.d_type).getName() << " rrname=" << record.d_name << " rrcontent=" << record.d_content->getZoneRepresentation() << endl;
       }
       ret = true;
     }
@@ -1128,6 +1183,88 @@ int followCNAMERecords(vector<DNSRecord>& ret, const QType& qtype)
   return rcode;
 }
 
+int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret)
+{
+  int rcode = directResolve(qname, QType(QType::A), QClass::IN, ret);
+
+  // Remove double CNAME records
+  std::set<DNSName> seenCNAMEs;
+  ret.erase(std::remove_if(
+        ret.begin(),
+        ret.end(),
+        [&seenCNAMEs](DNSRecord& rr) {
+          if (rr.d_type == QType::CNAME) {
+            auto target = getRR<CNAMERecordContent>(rr);
+            if (target == nullptr) {
+              return false;
+            }
+            if (seenCNAMEs.count(target->getTarget()) > 0) {
+              // We've had this CNAME before, remove it
+              return true;
+            }
+            seenCNAMEs.insert(target->getTarget());
+          }
+          return false;
+        }),
+      ret.end());
+
+  bool seenA = false;
+  for (DNSRecord& rr : ret) {
+    if (rr.d_type == QType::A && rr.d_place == DNSResourceRecord::ANSWER) {
+      if (auto rec = getRR<ARecordContent>(rr)) {
+        ComboAddress ipv4(rec->getCA());
+        memcpy(&prefix.sin6.sin6_addr.s6_addr[12], &ipv4.sin4.sin_addr.s_addr, sizeof(ipv4.sin4.sin_addr.s_addr));
+        rr.d_content = std::make_shared<AAAARecordContent>(prefix);
+        rr.d_type = QType::AAAA;
+      }
+      seenA = true;
+    }
+  }
+
+  if (seenA) {
+    // We've seen an A in the ANSWER section, so there is no need to keep any
+    // SOA in the AUTHORITY section as this is not a NODATA response.
+    ret.erase(std::remove_if(
+          ret.begin(),
+          ret.end(),
+          [](DNSRecord& rr) {
+            return (rr.d_type == QType::SOA && rr.d_place == DNSResourceRecord::AUTHORITY);
+          }),
+        ret.end());
+  }
+  return rcode;
+}
+
+int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret)
+{
+  /* qname has a reverse ordered IPv6 address, need to extract the underlying IPv4 address from it
+     and turn it into an IPv4 in-addr.arpa query */
+  ret.clear();
+  vector<string> parts = qname.getRawLabels();
+
+  if (parts.size() < 8) {
+    return -1;
+  }
+
+  string newquery;
+  for (int n = 0; n < 4; ++n) {
+    newquery +=
+      std::to_string(stoll(parts[n*2], 0, 16) + 16*stoll(parts[n*2+1], 0, 16));
+    newquery.append(1, '.');
+  }
+  newquery += "in-addr.arpa.";
+
+  DNSRecord rr;
+  rr.d_name = qname;
+  rr.d_type = QType::CNAME;
+  rr.d_content = std::make_shared<CNAMERecordContent>(newquery);
+  ret.push_back(rr);
+
+  int rcode = directResolve(DNSName(newquery), QType(QType::PTR), QClass::IN, ret);
+
+  return rcode;
+}
+
 static void startDoResolve(void *p)
 {
   auto dc=std::unique_ptr<DNSComboWriter>(reinterpret_cast<DNSComboWriter*>(p));
@@ -1259,6 +1396,7 @@ static void startDoResolve(void *p)
     sr.setFrameStreamServers(t_frameStreamServers);
 #endif
     sr.setQuerySource(dc->d_remote, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
+    sr.setQueryReceivedOverTCP(dc->d_tcp);
 
     bool tracedQuery=false; // we could consider letting Lua know about this too
     bool shouldNotValidate = false;
@@ -1267,7 +1405,6 @@ static void startDoResolve(void *p)
     int res = RCode::NoError;
 
     DNSFilterEngine::Policy appliedPolicy;
-    std::vector<DNSRecord> spoofed;
     RecursorLua4::DNSQuestion dq(dc->d_source, dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ, dc->d_logResponse);
     dq.ednsFlags = &edo.d_extFlags;
     dq.ednsOptions = &ednsOpts;
@@ -1283,6 +1420,7 @@ static void startDoResolve(void *p)
     dq.deviceId = dc->d_deviceId;
     dq.deviceName = dc->d_deviceName;
 #endif
+    dq.proxyProtocolValues = &dc->d_proxyProtocolValues;
 
     if(ednsExtRCode != 0) {
       goto sendit;
@@ -1313,189 +1451,163 @@ static void startDoResolve(void *p)
       sr.setCacheOnly();
     }
 
-    if (dc->d_rcode != boost::none) {
-      /* we have a response ready to go, most likely from gettag_ffi */
-      ret = std::move(dc->d_records);
-      res = *dc->d_rcode;
-      if (res == RCode::NoError && dc->d_followCNAMERecords) {
-        res = followCNAMERecords(ret, QType(dc->d_mdp.d_qtype));
-      }
-      goto haveAnswer;
-    }
-
     if (t_pdl) {
       t_pdl->prerpz(dq, res);
     }
 
-    // Check if the query has a policy attached to it
-    if (wantsRPZ && (appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
-      luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies, appliedPolicy);
+    // Check if the client has a policy attached to it
+    if (wantsRPZ && !appliedPolicy.wasHit()) {
+
+      if (luaconfsLocal->dfe.getClientPolicy(dc->d_source, sr.d_discardedPolicies, appliedPolicy)) {
+        mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+      }
+    }
+
+    /* If we already have an answer generated from gettag_ffi, let's see if the filtering policies
+       should be applied to it */
+    if (dc->d_rcode != boost::none) {
+
+      bool policyOverride = false;
+      /* Unless we already matched on the client IP, time to check the qname.
+         We normally check it in beginResolve() but it will be bypassed since we already have an answer */
+      if (wantsRPZ && appliedPolicy.policyOverridesGettag()) {
+        if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+          // Client IP already matched
+        }
+        else {
+          // no match on the client IP, check the qname
+          if (luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, sr.d_discardedPolicies, appliedPolicy)) {
+            // got a match
+            mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+          }
+        }
+
+        if (appliedPolicy.wasHit()) {
+          policyOverride = true;
+        }
+      }
+
+      if (policyOverride) {
+        /* No RPZ or gettag overrides it anyway */
+        ret = std::move(dc->d_records);
+        res = *dc->d_rcode;
+        if (res == RCode::NoError && dc->d_followCNAMERecords) {
+          res = followCNAMERecords(ret, QType(dc->d_mdp.d_qtype));
+        }
+        goto haveAnswer;
+      }
     }
 
     // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
-    if(!t_pdl || !t_pdl->preresolve(dq, res)) {
+    if (!t_pdl || !t_pdl->preresolve(dq, res)) {
+
+      if (!g_dns64PrefixReverse.empty() && dq.qtype == QType::PTR && dq.qname.isPartOf(g_dns64PrefixReverse)) {
+        res = getFakePTRRecords(dq.qname, ret);
+        goto haveAnswer;
+      }
 
       sr.setWantsRPZ(wantsRPZ);
-      if(wantsRPZ) {
-        switch(appliedPolicy.d_kind) {
-          case DNSFilterEngine::PolicyKind::NoAction:
-            break;
-          case DNSFilterEngine::PolicyKind::Drop:
-            g_stats.policyDrops++;
-            g_stats.policyResults[appliedPolicy.d_kind]++;
-            return; 
-          case DNSFilterEngine::PolicyKind::NXDOMAIN:
-            g_stats.policyResults[appliedPolicy.d_kind]++;
-            res=RCode::NXDomain;
-            goto haveAnswer;
-          case DNSFilterEngine::PolicyKind::NODATA:
-            g_stats.policyResults[appliedPolicy.d_kind]++;
-            res=RCode::NoError;
-            goto haveAnswer;
-          case DNSFilterEngine::PolicyKind::Custom:
-            g_stats.policyResults[appliedPolicy.d_kind]++;
-            res=RCode::NoError;
-            spoofed=appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
-            for (const auto& dr : spoofed) {
-              ret.push_back(dr);
-              handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
-            }
+
+      if (wantsRPZ && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
+
+        if (t_pdl && t_pdl->policyHitEventFilter(dc->d_remote, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_tcp, appliedPolicy, dc->d_policyTags, sr.d_discardedPolicies)) {
+          /* reset to no match */
+          appliedPolicy = DNSFilterEngine::Policy();
+        }
+        else {
+          auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw);
+          if (policyResult == PolicyResult::HaveAnswer) {
             goto haveAnswer;
-          case DNSFilterEngine::PolicyKind::Truncate:
-            if(!dc->d_tcp) {
-              g_stats.policyResults[appliedPolicy.d_kind]++;
-              res=RCode::NoError;      
-              pw.getHeader()->tc=1;
-              goto haveAnswer;
-            }
-            break;
+          }
+          else if (policyResult == PolicyResult::Drop) {
+            return;
+          }
         }
       }
 
-      // Query got not handled for QNAME Policy reasons, now actually go out to find an answer
+      // Query did not get handled for Client IP or QNAME Policy reasons, now actually go out to find an answer
       try {
         sr.d_appliedPolicy = appliedPolicy;
+        sr.d_policyTags = std::move(dc->d_policyTags);
+
+        if (!dc->d_routingTag.empty()) {
+          sr.d_routingTag = dc->d_routingTag;
+        }
+
+        ret.clear(); // policy might have filled it with custom records but we decided not to use them
         res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
         shouldNotValidate = sr.wasOutOfBand();
       }
-      catch(const ImmediateServFailException &e) {
+      catch (const ImmediateQueryDropException& e) {
+        // XXX We need to export a protobuf message (and do a NOD lookup) if requested!
+        g_stats.policyDrops++;
+        g_log<<Logger::Debug<<"Dropping query because of a filtering policy "<<makeLoginfo(dc)<<endl;
+        return;
+      }
+      catch (const ImmediateServFailException &e) {
         if(g_logCommonErrors) {
           g_log<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during resolve of '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
         }
         res = RCode::ServFail;
       }
-      catch(const PolicyHitException& e) {
+      catch (const SendTruncatedAnswerException& e) {
+        ret.clear();
+        res = RCode::NoError;
+        pw.getHeader()->tc = 1;
+      }
+      catch (const PolicyHitException& e) {
         res = -2;
       }
       dq.validationState = sr.getValidationState();
       appliedPolicy = sr.d_appliedPolicy;
+      dc->d_policyTags = std::move(sr.d_policyTags);
 
       // During lookup, an NSDNAME or NSIP trigger was hit in RPZ
       if (res == -2) { // XXX This block should be macro'd, it is repeated post-resolve.
-        appliedPolicy = sr.d_appliedPolicy;
-        g_stats.policyResults[appliedPolicy.d_kind]++;
-        switch(appliedPolicy.d_kind) {
-          case DNSFilterEngine::PolicyKind::NoAction: // This can never happen
-            throw PDNSException("NoAction policy returned while a NSDNAME or NSIP trigger was hit");
-          case DNSFilterEngine::PolicyKind::Drop:
-            g_stats.policyDrops++;
-            return;
-          case DNSFilterEngine::PolicyKind::NXDOMAIN:
-            ret.clear();
-            res=RCode::NXDomain;
-            goto haveAnswer;
-
-          case DNSFilterEngine::PolicyKind::NODATA:
-            ret.clear();
-            res=RCode::NoError;
-            goto haveAnswer;
-
-          case DNSFilterEngine::PolicyKind::Truncate:
-            if(!dc->d_tcp) {
-              ret.clear();
-              res=RCode::NoError;
-              pw.getHeader()->tc=1;
-              goto haveAnswer;
-            }
-            break;
-
-          case DNSFilterEngine::PolicyKind::Custom:
-            ret.clear();
-            res=RCode::NoError;
-            spoofed=appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
-            for (const auto& dr : spoofed) {
-              ret.push_back(dr);
-              handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
-            }
-            goto haveAnswer;
+        if (appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction) {
+          throw PDNSException("NoAction policy returned while a NSDNAME or NSIP trigger was hit");
+        }
+        auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw);
+        if (policyResult == PolicyResult::HaveAnswer) {
+          goto haveAnswer;
+        }
+        else if (policyResult == PolicyResult::Drop) {
+          return;
         }
       }
 
-      if (wantsRPZ && (appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
-        luaconfsLocal->dfe.getPostPolicy(ret, sr.d_discardedPolicies, appliedPolicy);
-      }
+      if (t_pdl || (g_dns64Prefix && dq.qtype == QType::AAAA && dq.validationState != vState::Bogus)) {
+        if (res == RCode::NoError) {
+          auto i = ret.cbegin();
+          for(; i!= ret.cend(); ++i) {
+            if (i->d_type == dc->d_mdp.d_qtype && i->d_place == DNSResourceRecord::ANSWER) {
+              break;
+            }
+          }
 
-      if(t_pdl) {
-        if(res == RCode::NoError) {
-               auto i=ret.cbegin();
-                for(; i!= ret.cend(); ++i)
-                  if(i->d_type == dc->d_mdp.d_qtype && i->d_place == DNSResourceRecord::ANSWER)
-                          break;
-                if(i == ret.cend() && t_pdl->nodata(dq, res))
-                  shouldNotValidate = true;
+          if (i == ret.cend()) {
+            /* no record in the answer section, NODATA */
+            if (t_pdl && t_pdl->nodata(dq, res)) {
+              shouldNotValidate = true;
+            }
+            else if (g_dns64Prefix && dq.qtype == QType::AAAA && dq.validationState != vState::Bogus) {
+              res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
+              shouldNotValidate = true;
+            }
+          }
 
        }
-       else if(res == RCode::NXDomain && t_pdl->nxdomain(dq, res))
+       else if(res == RCode::NXDomain && t_pdl && t_pdl->nxdomain(dq, res)) {
           shouldNotValidate = true;
+        }
 
-       if(t_pdl->postresolve(dq, res))
+       if (t_pdl && t_pdl->postresolve(dq, res)) {
           shouldNotValidate = true;
-      }
-
-      if (wantsRPZ) { //XXX This block is repeated, see above
-        g_stats.policyResults[appliedPolicy.d_kind]++;
-        switch(appliedPolicy.d_kind) {
-          case DNSFilterEngine::PolicyKind::NoAction:
-            break;
-          case DNSFilterEngine::PolicyKind::Drop:
-            g_stats.policyDrops++;
-            return; 
-          case DNSFilterEngine::PolicyKind::NXDOMAIN:
-            ret.clear();
-            res=RCode::NXDomain;
-            goto haveAnswer;
-
-          case DNSFilterEngine::PolicyKind::NODATA:
-            ret.clear();
-            res=RCode::NoError;
-            goto haveAnswer;
-
-          case DNSFilterEngine::PolicyKind::Truncate:
-            if(!dc->d_tcp) {
-              ret.clear();
-              res=RCode::NoError;
-              pw.getHeader()->tc=1;
-              goto haveAnswer;
-            }
-            break;
-
-          case DNSFilterEngine::PolicyKind::Custom:
-            ret.clear();
-            res=RCode::NoError;
-            spoofed=appliedPolicy.getCustomRecords(dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
-            for (const auto& dr : spoofed) {
-              ret.push_back(dr);
-              handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
-            }
-            goto haveAnswer;
         }
       }
     }
+
   haveAnswer:;
-    if(res == PolicyDecision::DROP) {
-      g_stats.policyDrops++;
-      return;
-    }
     if(tracedQuery || res == -1 || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail)
     { 
       string trace(sr.getTrace());
@@ -1526,7 +1638,7 @@ static void startDoResolve(void *p)
 
           auto state = sr.getValidationState();
 
-          if(state == Secure) {
+          if(state == vState::Secure) {
             if(sr.doLog()) {
               g_log<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates correctly"<<endl;
             }
@@ -1535,14 +1647,14 @@ static void startDoResolve(void *p)
             if (dc->d_mdp.d_header.ad || DNSSECOK)
               pw.getHeader()->ad=1;
           }
-          else if(state == Insecure) {
+          else if(state == vState::Insecure) {
             if(sr.doLog()) {
               g_log<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates as Insecure"<<endl;
             }
             
             pw.getHeader()->ad=0;
           }
-          else if(state == Bogus) {
+          else if(state == vState::Bogus) {
             if(t_bogusremotes)
               t_bogusremotes->push_back(dc->d_source);
             if(t_bogusqueryring)
@@ -1575,7 +1687,7 @@ static void startDoResolve(void *p)
       }
 
       if(ret.size()) {
-        orderAndShuffle(ret);
+        pdns::orderAndShuffle(ret);
        if(auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_source)) {
          stable_sort(ret.begin(), ret.end(), *sl);
          variableAnswer=true;
@@ -1656,17 +1768,20 @@ static void startDoResolve(void *p)
 #ifdef NOD_ENABLED
     bool nod = false;
     if (g_nodEnabled) {
-      if (nodCheckNewDomain(dc->d_mdp.d_qname))
+      if (nodCheckNewDomain(dc->d_mdp.d_qname)) {
         nod = true;
+      }
     }
 #endif /* NOD_ENABLED */
 #ifdef HAVE_PROTOBUF
-    if (t_protobufServers && !(luaconfsLocal->protobufExportConfig.taggedOnly && (!appliedPolicy.d_name || appliedPolicy.d_name->empty()) && dc->d_policyTags.empty())) {
+    if (t_protobufServers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->d_policyTags.empty())) {
       pbMessage->setBytes(packet.size());
       pbMessage->setResponseCode(pw.getHeader()->rcode);
-      if (appliedPolicy.d_name) {
-        pbMessage->setAppliedPolicy(*appliedPolicy.d_name);
+      if (!appliedPolicy.getName().empty()) {
+        pbMessage->setAppliedPolicy(appliedPolicy.getName());
         pbMessage->setAppliedPolicyType(appliedPolicy.d_type);
+        pbMessage->setAppliedPolicyTrigger(appliedPolicy.d_trigger);
+        pbMessage->setAppliedPolicyHit(appliedPolicy.d_hit);
       }
       pbMessage->setPolicyTags(dc->d_policyTags);
       if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
@@ -1730,8 +1845,6 @@ static void startDoResolve(void *p)
                                             pw.getHeader()->rcode == RCode::ServFail ? SyncRes::s_packetcacheservfailttl :
                                             min(minTTL,SyncRes::s_packetcachettl),
                                             dq.validationState,
-                                            dc->d_ecsBegin,
-                                            dc->d_ecsEnd,
                                             std::move(pbMessage));
       }
       //      else cerr<<"Not putting in packet cache: "<<sr.wasVariable()<<endl;
@@ -1749,16 +1862,16 @@ static void startDoResolve(void *p)
       int wret=Utility::writev(dc->d_socket, iov, 2);
       bool hadError=true;
 
-      if(wret == 0)
-        g_log<<Logger::Error<<"EOF writing TCP answer to "<<dc->getRemote()<<endl;
-      else if(wret < 0 ) {
+      if (wret == 0) {
+        g_log<<Logger::Warning<<"EOF writing TCP answer to "<<dc->getRemote()<<endl;
+      } else if (wret < 0 ) {
         int err = errno;
-        g_log << Logger::Error << "Error writing TCP answer to " << dc->getRemote() << ": " << strerror(err) << endl;
-      } else if((unsigned int)wret != 2 + packet.size())
-        g_log<<Logger::Error<<"Oops, partial answer sent to "<<dc->getRemote()<<" for "<<dc->d_mdp.d_qname<<" (size="<< (2 + packet.size()) <<", sent "<<wret<<")"<<endl;
-      else
+        g_log << Logger::Warning << "Error writing TCP answer to " << dc->getRemote() << ": " << strerror(err) << endl;
+      } else if ((unsigned int)wret != 2 + packet.size()) {
+        g_log<<Logger::Warning<<"Oops, partial answer sent to "<<dc->getRemote()<<" for "<<dc->d_mdp.d_qname<<" (size="<< (2 + packet.size()) <<", sent "<<wret<<")"<<endl;
+      } else {
         hadError=false;
-
+      }
       // update tcp connection status, closing if needed and doing the fd multiplexer accounting
       if  (dc->d_tcpConnection->d_requestsInFlight > 0) {
         dc->d_tcpConnection->d_requestsInFlight--;
@@ -1797,36 +1910,39 @@ static void startDoResolve(void *p)
             ttd.tv_sec += g_tcpTimeout;
             t_fdm->addReadFD(dc->d_socket, handleRunningTCPQuestion, dc->d_tcpConnection, &ttd);
           } else {
-            // fd might have been removed by read error code, so expect an exception
+            // fd might have been removed by read error code, or a read timeout, so expect an exception
             try {
               t_fdm->setReadTTD(dc->d_socket, ttd, g_tcpTimeout);
             }
-            catch (FDMultiplexerException &) {
+            catch (const FDMultiplexerException &) {
+              // but if the FD was removed because of a timeout while we were sending a response,
+              // we need to re-arm it. If it was an error it will error again.
+              ttd.tv_sec += g_tcpTimeout;
+              t_fdm->addReadFD(dc->d_socket, handleRunningTCPQuestion, dc->d_tcpConnection, &ttd);
             }
           }
         }
       }
     }
+
     float spent=makeFloat(sr.getNow()-dc->d_now);
-    if(!g_quiet) {
+    if (!g_quiet) {
       g_log<<Logger::Error<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] answer to "<<(dc->d_mdp.d_header.rd?"":"non-rd ")<<"question '"<<dc->d_mdp.d_qname<<"|"<<DNSRecordContent::NumberToType(dc->d_mdp.d_qtype);
       g_log<<"': "<<ntohs(pw.getHeader()->ancount)<<" answers, "<<ntohs(pw.getHeader()->arcount)<<" additional, took "<<sr.d_outqueries<<" packets, "<<
        sr.d_totUsec/1000.0<<" netw ms, "<< spent*1000.0<<" tot ms, "<<
        sr.d_throttledqueries<<" throttled, "<<sr.d_timeouts<<" timeouts, "<<sr.d_tcpoutqueries<<" tcp connections, rcode="<< res;
 
       if(!shouldNotValidate && sr.isDNSSECValidationRequested()) {
-       g_log<< ", dnssec="<<vStates[sr.getValidationState()];
+       g_log<< ", dnssec="<<sr.getValidationState();
       }
-       
       g_log<<endl;
-
     }
 
     if (sr.d_outqueries || sr.d_authzonequeries) {
-      s_RC->cacheMisses++;
+      g_recCache->cacheMisses++;
     }
     else {
-      s_RC->cacheHits++;
+      g_recCache->cacheHits++;
     }
 
     if(spent < 0.001)
@@ -1866,15 +1982,22 @@ static void startDoResolve(void *p)
       newLat=ourtime*1000; // usec
       g_stats.avgLatencyOursUsec=(1-1.0/g_latencyStatSize)*g_stats.avgLatencyOursUsec + (float)newLat/g_latencyStatSize;
     }
+
+#ifdef NOD_ENABLED
+    if (nod) {
+      sendNODLookup(dc->d_mdp.d_qname);
+    }
+#endif /* NOD_ENABLED */
+
     //    cout<<dc->d_mdp.d_qname<<"\t"<<MT->getUsec()<<"\t"<<sr.d_outqueries<<endl;
   }
-  catch(PDNSException &ae) {
+  catch (const PDNSException &ae) {
     g_log<<Logger::Error<<"startDoResolve problem "<<makeLoginfo(dc)<<": "<<ae.reason<<endl;
   }
-  catch(const MOADNSException &mde) {
+  catch (const MOADNSException &mde) {
     g_log<<Logger::Error<<"DNS parser error "<<makeLoginfo(dc) <<": "<<dc->d_mdp.d_qname<<", "<<mde.what()<<endl;
   }
-  catch(std::exception& e) {
+  catch (const std::exception& e) {
     g_log<<Logger::Error<<"STL error "<< makeLoginfo(dc)<<": "<<e.what();
 
     // Luawrapper nests the exception from Lua, so we unnest it here
@@ -1995,11 +2118,93 @@ static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uin
   }
 }
 
+static bool handleTCPReadResult(int fd, ssize_t bytes)
+{
+  if (bytes == 0) {
+    /* EOF */
+    t_fdm->removeReadFD(fd);
+    return false;
+  }
+  else if (bytes < 0) {
+    if (errno != EAGAIN && errno != EWOULDBLOCK) {
+      t_fdm->removeReadFD(fd);
+      return false;
+    }
+  }
+
+  return true;
+}
+
 static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
 {
   shared_ptr<TCPConnection> conn=any_cast<shared_ptr<TCPConnection> >(var);
 
-  if(conn->state==TCPConnection::BYTE0) {
+  if (conn->state == TCPConnection::PROXYPROTOCOLHEADER) {
+    ssize_t bytes = recv(conn->getFD(), &conn->data.at(conn->proxyProtocolGot), conn->proxyProtocolNeed, 0);
+    if (bytes <= 0) {
+      handleTCPReadResult(fd, bytes);
+      return;
+    }
+
+    conn->proxyProtocolGot += bytes;
+    conn->data.resize(conn->proxyProtocolGot);
+    ssize_t remaining = isProxyHeaderComplete(conn->data);
+    if (remaining == 0) {
+      if (g_logCommonErrors) {
+        g_log<<Logger::Error<<"Unable to consume proxy protocol header in packet from TCP client "<< conn->d_remote.toStringWithPort() <<endl;
+      }
+      ++g_stats.proxyProtocolInvalidCount;
+      t_fdm->removeReadFD(fd);
+      return;
+    }
+    else if (remaining < 0) {
+      conn->proxyProtocolNeed = -remaining;
+      conn->data.resize(conn->proxyProtocolGot + conn->proxyProtocolNeed);
+      return;
+    }
+    else {
+      /* proxy header received */
+      /* we ignore the TCP field for now, but we could properly set whether
+         the connection was received over UDP or TCP if needed */
+      bool tcp;
+      bool proxy = false;
+      size_t used = parseProxyHeader(conn->data, proxy, conn->d_source, conn->d_destination, tcp, conn->proxyProtocolValues);
+      if (used <= 0) {
+        if (g_logCommonErrors) {
+          g_log<<Logger::Error<<"Unable to parse proxy protocol header in packet from TCP client "<< conn->d_remote.toStringWithPort() <<endl;
+        }
+        ++g_stats.proxyProtocolInvalidCount;
+        t_fdm->removeReadFD(fd);
+        return;
+      }
+      else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+        if (g_logCommonErrors) {
+          g_log<<Logger::Error<<"Proxy protocol header in packet from TCP client "<< conn->d_remote.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping"<< endl;
+        }
+        ++g_stats.proxyProtocolInvalidCount;
+        t_fdm->removeReadFD(fd);
+        return;
+      }
+
+      /* Now that we have retrieved the address of the client, as advertised by the proxy
+         via the proxy protocol header, check that it is allowed by our ACL */
+      /* note that if the proxy header used a 'LOCAL' command, the original source and destination are untouched so everything should be fine */
+      if (t_allowFrom && !t_allowFrom->match(&conn->d_source)) {
+        if (!g_quiet) {
+          g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping TCP query from "<<conn->d_source.toString()<<", address not matched by allow-from"<<endl;
+        }
+
+        ++g_stats.unauthorizedTCP;
+        t_fdm->removeReadFD(fd);
+        return;
+      }
+
+      conn->data.resize(2);
+      conn->state = TCPConnection::BYTE0;
+    }
+  }
+
+  if (conn->state==TCPConnection::BYTE0) {
     ssize_t bytes=recv(conn->getFD(), &conn->data[0], 2, 0);
     if(bytes==1)
       conn->state=TCPConnection::BYTE1;
@@ -2009,12 +2214,13 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       conn->bytesread=0;
       conn->state=TCPConnection::GETQUESTION;
     }
-    if(!bytes || bytes < 0) {
-      t_fdm->removeReadFD(fd);
+    if (bytes <= 0) {
+      handleTCPReadResult(fd, bytes);
       return;
     }
   }
-  else if(conn->state==TCPConnection::BYTE1) {
+
+  if (conn->state==TCPConnection::BYTE1) {
     ssize_t bytes=recv(conn->getFD(), &conn->data[1], 1, 0);
     if(bytes==1) {
       conn->state=TCPConnection::GETQUESTION;
@@ -2022,18 +2228,29 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       conn->data.resize(conn->qlen);
       conn->bytesread=0;
     }
-    if(!bytes || bytes < 0) {
-      if(g_logCommonErrors)
-        g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
-      t_fdm->removeReadFD(fd);
+    if (bytes <= 0) {
+      if (!handleTCPReadResult(fd, bytes)) {
+        if(g_logCommonErrors) {
+          g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
+        }
+      }
       return;
     }
   }
-  else if(conn->state==TCPConnection::GETQUESTION) {
+
+  if(conn->state==TCPConnection::GETQUESTION) {
     ssize_t bytes=recv(conn->getFD(), &conn->data[conn->bytesread], conn->qlen - conn->bytesread, 0);
-    if(!bytes || bytes < 0 || bytes > std::numeric_limits<std::uint16_t>::max()) {
+    if (bytes <= 0) {
+      if (!handleTCPReadResult(fd, bytes)) {
+        if(g_logCommonErrors) {
+          g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected while reading question body"<<endl;
+        }
+      }
+      return;
+    }
+    else if (bytes > std::numeric_limits<std::uint16_t>::max()) {
       if(g_logCommonErrors) {
-        g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected while reading question body"<<endl;
+        g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" sent an invalid question size while reading question body"<<endl;
       }
       t_fdm->removeReadFD(fd);
       return;
@@ -2055,14 +2272,17 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       dc->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
       dc->d_tcp=true;
       dc->setRemote(conn->d_remote);
-      dc->setSource(conn->d_remote);
+      dc->setSource(conn->d_source);
       ComboAddress dest;
       dest.reset();
       dest.sin4.sin_family = conn->d_remote.sin4.sin_family;
       socklen_t len = dest.getSocklen();
       getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
       dc->setLocal(dest);
-      dc->setDestination(dest);
+      dc->setDestination(conn->d_destination);
+      /* we can't move this if we want to be able to access the values in
+         all queries sent over this connection */
+      dc->d_proxyProtocolValues = conn->proxyProtocolValues;
       DNSName qname;
       uint16_t qtype=0;
       uint16_t qclass=0;
@@ -2099,10 +2319,10 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
           if(t_pdl) {
             try {
               if (t_pdl->d_gettag_ffi) {
-                dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords);
+                dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords);
               }
               else if (t_pdl->d_gettag) {
-                dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName);
+                dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_proxyProtocolValues);
               }
             }
             catch(const std::exception& e)  {
@@ -2189,6 +2409,11 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
   }
 }
 
+static bool expectProxyProtocol(const ComboAddress& from)
+{
+  return g_proxyProtocolACL.match(from);
+}
+
 //! Handle new incoming TCP connection
 static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& )
 {
@@ -2207,11 +2432,14 @@ static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& )
       return;
     }
 
-    if(t_remotes)
+    if(t_remotes) {
       t_remotes->push_back(addr);
-    if(t_allowFrom && !t_allowFrom->match(&addr)) {
+    }
+
+    bool fromProxyProtocolSource = expectProxyProtocol(addr);
+    if(t_allowFrom && !t_allowFrom->match(&addr) && !fromProxyProtocolSource) {
       if(!g_quiet)
-        g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping TCP query from "<<addr.toString()<<", address not matched by allow-from"<<endl;
+        g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping TCP query from "<<addr.toString()<<", address neither matched by allow-from nor proxy-protocol-from"<<endl;
 
       g_stats.unauthorizedTCP++;
       try {
@@ -2222,6 +2450,7 @@ static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& )
       }
       return;
     }
+
     if(g_maxTCPPerClient && t_tcpClientCounts->count(addr) && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
       g_stats.tcpClientOverflow++;
       try {
@@ -2235,7 +2464,20 @@ static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& )
 
     setNonBlocking(newsock);
     std::shared_ptr<TCPConnection> tc = std::make_shared<TCPConnection>(newsock, addr);
-    tc->state=TCPConnection::BYTE0;
+    tc->d_source = addr;
+    tc->d_destination.reset();
+    tc->d_destination.sin4.sin_family = addr.sin4.sin_family;
+    socklen_t len = tc->d_destination.getSocklen();
+    getsockname(tc->getFD(), reinterpret_cast<sockaddr*>(&tc->d_destination), &len); // if this fails, we're ok with it
+
+    if (fromProxyProtocolSource) {
+      tc->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+      tc->data.resize(tc->proxyProtocolNeed);
+      tc->state = TCPConnection::PROXYPROTOCOLHEADER;
+    }
+    else {
+      tc->state = TCPConnection::BYTE0;
+    }
 
     struct timeval ttd;
     Utility::gettimeofday(&ttd, 0);
@@ -2245,7 +2487,7 @@ static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& )
   }
 }
 
-static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, struct timeval tv, int fd)
+static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues)
 {
   gettimeofday(&g_now, 0);
   if (tv.tv_sec) {
@@ -2268,13 +2510,12 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   uint32_t qhash = 0;
   bool needECS = false;
   bool needXPF = g_XPFAcl.match(fromaddr);
-  std::vector<std::string> policyTags;
+  std::unordered_set<std::string> policyTags;
   LuaContext::LuaObject data;
-  ComboAddress source = fromaddr;
-  ComboAddress destination = destaddr;
   string requestorId;
   string deviceId;
   string deviceName;
+  string routingTag;
   bool logQuery = false;
   bool logResponse = false;
 #ifdef HAVE_PROTOBUF
@@ -2295,8 +2536,6 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   EDNSSubnetOpts ednssubnet;
   bool ecsFound = false;
   bool ecsParsed = false;
-  uint16_t ecsBegin = 0;
-  uint16_t ecsEnd = 0;
   std::vector<DNSRecord> records;
   boost::optional<int> rcode = boost::none;
   uint32_t ttlCap = std::numeric_limits<uint32_t>::max();
@@ -2337,10 +2576,10 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
         if(t_pdl) {
           try {
             if (t_pdl->d_gettag_ffi) {
-              ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, requestorId, deviceId, deviceName, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs);
+              ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs);
             }
             else if (t_pdl->d_gettag) {
-              ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName);
+              ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, routingTag, proxyProtocolValues);
             }
           }
           catch(const std::exception& e)  {
@@ -2373,14 +2612,14 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
        as cacheable we would cache it with a wrong tag, so better safe than sorry. */
     vState valState;
     if (qnameParsed) {
-      cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, qtype, qclass, g_now.tv_sec, &response, &age, &valState, &qhash, &ecsBegin, &ecsEnd, pbMessage ? &(*pbMessage) : nullptr));
+      cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, qtype, qclass, g_now.tv_sec, &response, &age, &valState, &qhash, pbMessage ? &(*pbMessage) : nullptr));
     }
     else {
-      cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, &qtype, &qclass, g_now.tv_sec, &response, &age, &valState, &qhash, &ecsBegin, &ecsEnd, pbMessage ? &(*pbMessage) : nullptr));
+      cacheHit = (!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, qname, &qtype, &qclass, g_now.tv_sec, &response, &age, &valState, &qhash, pbMessage ? &(*pbMessage) : nullptr));
     }
 
     if (cacheHit) {
-      if(valState == Bogus) {
+      if(valState == vState::Bogus) {
         if(t_bogusremotes)
           t_bogusremotes->push_back(source);
         if(t_bogusqueryring)
@@ -2471,8 +2710,6 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   dc->d_tcp=false;
   dc->d_ecsFound = ecsFound;
   dc->d_ecsParsed = ecsParsed;
-  dc->d_ecsBegin = ecsBegin;
-  dc->d_ecsEnd = ecsEnd;
   dc->d_ednssubnet = ednssubnet;
   dc->d_ttlCap = ttlCap;
   dc->d_variable = variable;
@@ -2488,6 +2725,8 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   dc->d_deviceName = deviceName;
   dc->d_kernelTimestamp = tv;
 #endif
+  dc->d_proxyProtocolValues = std::move(proxyProtocolValues);
+  dc->d_routingTag = std::move(routingTag);
 
   MT->makeThread(startDoResolve, (void*) dc.release()); // deletes dc
   return 0;
@@ -2497,15 +2736,19 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
 {
   ssize_t len;
-  static const size_t maxIncomingQuerySize = 512;
+  static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize);
   static thread_local std::string data;
   ComboAddress fromaddr;
+  ComboAddress source;
+  ComboAddress destination;
   struct msghdr msgh;
   struct iovec iov;
   cmsgbuf_aligned cbuf;
   bool firstQuery = true;
+  std::vector<ProxyProtocolValue> proxyProtocolValues;
 
   for(size_t queriesCounter = 0; queriesCounter < s_maxUDPQueriesPerRound; queriesCounter++) {
+    bool proxyProto = false;
     data.resize(maxIncomingQuerySize);
     fromaddr.sin6.sin6_family=AF_INET6; // this makes sure fromaddr is big enough
     fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), &data[0], data.size(), &fromaddr);
@@ -2514,34 +2757,70 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
 
       firstQuery = false;
 
-      if (static_cast<size_t>(len) < sizeof(dnsheader)) {
-        g_stats.ignoredCount++;
+      if (msgh.msg_flags & MSG_TRUNC) {
+        g_stats.truncatedDrops++;
         if (!g_quiet) {
-          g_log<<Logger::Error<<"Ignoring too-short ("<<std::to_string(len)<<") query from "<<fromaddr.toString()<<endl;
+          g_log<<Logger::Error<<"Ignoring truncated query from "<<fromaddr.toString()<<endl;
         }
         return;
       }
 
-      if (msgh.msg_flags & MSG_TRUNC) {
+      data.resize(static_cast<size_t>(len));
+
+      if (expectProxyProtocol(fromaddr)) {
+        bool tcp;
+        ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
+        if (used <= 0) {
+          ++g_stats.proxyProtocolInvalidCount;
+          if (!g_quiet) {
+            g_log<<Logger::Error<<"Ignoring invalid proxy protocol ("<<std::to_string(len)<<", "<<std::to_string(used)<<") query from "<<fromaddr.toStringWithPort()<<endl;
+          }
+          return;
+        }
+        else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+          if (g_quiet) {
+            g_log<<Logger::Error<<"Proxy protocol header in UDP packet from "<< fromaddr.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping"<< endl;
+          }
+          ++g_stats.proxyProtocolInvalidCount;
+          return;
+        }
+
+        data.erase(0, used);
+      }
+      else if (len > 512) {
+        /* we only allow UDP packets larger than 512 for those with a proxy protocol header */
         g_stats.truncatedDrops++;
         if (!g_quiet) {
-          g_log<<Logger::Error<<"Ignoring truncated query from "<<fromaddr.toString()<<endl;
+          g_log<<Logger::Error<<"Ignoring truncated query from "<<fromaddr.toStringWithPort()<<endl;
+        }
+        return;
+      }
+
+      if (data.size() < sizeof(dnsheader)) {
+        g_stats.ignoredCount++;
+        if (!g_quiet) {
+          g_log<<Logger::Error<<"Ignoring too-short ("<<std::to_string(data.size())<<") query from "<<fromaddr.toString()<<endl;
         }
         return;
       }
 
+      if (!proxyProto) {
+        source = fromaddr;
+      }
+
       if(t_remotes) {
         t_remotes->push_back(fromaddr);
       }
 
-      if(t_allowFrom && !t_allowFrom->match(&fromaddr)) {
+      if(t_allowFrom && !t_allowFrom->match(&source)) {
         if(!g_quiet) {
-          g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping UDP query from "<<fromaddr.toString()<<", address not matched by allow-from"<<endl;
+          g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping UDP query from "<<source.toString()<<", address not matched by allow-from"<<endl;
         }
 
         g_stats.unauthorizedUDP++;
         return;
       }
+
       BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port));
       if(!fromaddr.sin4.sin_port) { // also works for IPv6
         if(!g_quiet) {
@@ -2553,7 +2832,6 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       }
 
       try {
-        data.resize(static_cast<size_t>(len));
         dnsheader* dh=(dnsheader*)&data[0];
 
         if(dh->qr) {
@@ -2596,13 +2874,18 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
               getsockname(fd, (sockaddr*)&dest, &slen); // if this fails, we're ok with it
             }
           }
+          if (!proxyProto) {
+            destination = dest;
+          }
 
           if(g_weDistributeQueries) {
-            distributeAsyncFunction(data, boost::bind(doProcessUDPQuestion, data, fromaddr, dest, tv, fd));
+            std::string localdata = data;
+            distributeAsyncFunction(data, [localdata, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues]() mutable
+              { return doProcessUDPQuestion(localdata, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues); });
           }
           else {
             ++s_threadInfos[t_id].numberOfDistributedQueries;
-            doProcessUDPQuestion(data, fromaddr, dest, tv, fd);
+            doProcessUDPQuestion(data, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues);
           }
         }
       }
@@ -2680,12 +2963,23 @@ static void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcp
     if( ::arg().mustDo("non-local-bind") )
        Utility::setBindAny(AF_INET, fd);
 
-#ifdef SO_REUSEPORT
-    if(g_reusePort) {
-      if(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &tmp, sizeof(tmp)) < 0)
-        throw PDNSException("SO_REUSEPORT: "+stringerror());
-    }
+    if (g_reusePort) {
+#if defined(SO_REUSEPORT_LB)
+      try {
+        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
+      }
+      catch (const std::exception& e) {
+        throw PDNSException(std::string("SO_REUSEPORT_LB: ") + e.what());
+      }
+#elif defined(SO_REUSEPORT)
+      try {
+        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT, 1);
+      }
+      catch (const std::exception& e) {
+        throw PDNSException(std::string("SO_REUSEPORT: ") + e.what());
+      }
 #endif
+    }
 
     if (::arg().asNum("tcp-fast-open") > 0) {
 #ifdef TCP_FASTOPEN
@@ -2773,12 +3067,23 @@ static void makeUDPServerSockets(deferredAdd_t& deferredAdds)
     sin.sin4.sin_port = htons(st.port);
 
   
-#ifdef SO_REUSEPORT
-    if(g_reusePort) {
-      if(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0)
-        throw PDNSException("SO_REUSEPORT: "+stringerror());
-    }
+    if (g_reusePort) {
+#if defined(SO_REUSEPORT_LB)
+      try {
+        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, 1);
+      }
+      catch (const std::exception& e) {
+        throw PDNSException(std::string("SO_REUSEPORT_LB: ") + e.what());
+      }
+#elif defined(SO_REUSEPORT)
+      try {
+        SSetsockopt(fd, SOL_SOCKET, SO_REUSEPORT, 1);
+      }
+      catch (const std::exception& e) {
+        throw PDNSException(std::string("SO_REUSEPORT: ") + e.what());
+      }
 #endif
+    }
 
     if (sin.isIPv4()) {
       try {
@@ -2844,10 +3149,10 @@ static void doStats(void)
   static time_t lastOutputTime;
   static uint64_t lastQueryCount;
 
-  uint64_t cacheHits = s_RC->cacheHits;
-  uint64_t cacheMisses = s_RC->cacheMisses;
-  uint64_t cacheSize = s_RC->size();
-  auto rc_stats = s_RC->stats();
+  uint64_t cacheHits = g_recCache->cacheHits;
+  uint64_t cacheMisses = g_recCache->cacheMisses;
+  uint64_t cacheSize = g_recCache->size();
+  auto rc_stats = g_recCache->stats();
   double r = rc_stats.second == 0 ? 0.0 : (100.0 * rc_stats.first / rc_stats.second);
   
   if(g_stats.qcounter && (cacheHits + cacheMisses) && SyncRes::s_queries && SyncRes::s_outqueries) {
@@ -2921,7 +3226,6 @@ static void houseKeeping(void *)
     past.tv_sec -= 5;
     if (last_prune < past) {
       t_packetCache->doPruneTo(g_maxPacketCacheEntries / g_numWorkerThreads);
-      SyncRes::pruneNegCache(g_maxCacheEntries / (g_numWorkerThreads * 10));
 
       time_t limit;
       if(!((cleanCounter++)%40)) {  // this is a full scan!
@@ -2938,15 +3242,34 @@ static void houseKeeping(void *)
 
     if(isHandlerThread()) {
       if (now.tv_sec - last_RC_prune > 5) {
-        s_RC->doPrune(g_maxCacheEntries);
+        g_recCache->doPrune(g_maxCacheEntries);
+        g_negCache->prune(g_maxCacheEntries / 10);
         last_RC_prune = now.tv_sec;
       }
       // XXX !!! global
-      if(now.tv_sec - last_rootupdate > 7200) {
-        int res = SyncRes::getRootNS(g_now, nullptr);
+      if (now.tv_sec - last_rootupdate > 7200) {
+        int res = SyncRes::getRootNS(g_now, nullptr, 0);
         if (!res) {
           last_rootupdate=now.tv_sec;
-          primeRootNSZones(g_dnssecmode != DNSSECMode::Off);
+          try {
+            primeRootNSZones(g_dnssecmode != DNSSECMode::Off, 0);
+          }
+          catch (const std::exception& e) {
+            g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.what()<<endl;
+          }
+          catch (const PDNSException& e) {
+            g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.reason<<endl;
+          }
+          catch (const ImmediateServFailException& e) {
+            g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.reason<<endl;
+          }
+          catch (const PolicyHitException& e) {
+            g_log<<Logger::Error<<"Policy hit while priming the root NS zones"<<endl;
+          }
+          catch (...)
+          {
+            g_log<<Logger::Error<<"Exception while priming the root NS zones"<<endl;
+          }
         }
       }
 
@@ -2954,22 +3277,22 @@ static void houseKeeping(void *)
        try {
          doSecPoll(&last_secpoll);
        }
-       catch(const std::exception& e)
+       catch (const std::exception& e)
         {
           g_log<<Logger::Error<<"Exception while performing security poll: "<<e.what()<<endl;
         }
-        catch(const PDNSException& e)
+        catch (const PDNSException& e)
         {
           g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
         }
-        catch(const ImmediateServFailException &e)
+        catch (const ImmediateServFailException &e)
         {
           g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
         }
-        catch(const PolicyHitException& e) {
+        catch (const PolicyHitException& e) {
           g_log<<Logger::Error<<"Policy hit while performing security poll"<<endl;
         }
-        catch(...)
+        catch (...)
         {
           g_log<<Logger::Error<<"Exception while performing security poll"<<endl;
         }
@@ -2991,14 +3314,20 @@ static void houseKeeping(void *)
         }
       }
     }
-    s_running=false;
+    s_running = false;
+  }
+  catch (const PDNSException& ae)
+  {
+    s_running = false;
+    g_log<<Logger::Error<<"Fatal error in housekeeping thread: "<<ae.reason<<endl;
+    throw;
+  }
+  catch (...)
+  {
+    s_running = false;
+    g_log<<Logger::Error<<"Uncaught exception in housekeeping thread"<<endl;
+    throw;
   }
-  catch(PDNSException& ae)
-    {
-      s_running=false;
-      g_log<<Logger::Error<<"Fatal error in housekeeping thread: "<<ae.reason<<endl;
-      throw;
-    }
 }
 
 static void makeThreadPipes()
@@ -3235,19 +3564,13 @@ template<class T> void *voider(const boost::function<T*()>& func)
   return func();
 }
 
-vector<ComboAddress>& operator+=(vector<ComboAddress>&a, const vector<ComboAddress>& b)
-{
-  a.insert(a.end(), b.begin(), b.end());
-  return a;
-}
-
-vector<pair<string, uint16_t> >& operator+=(vector<pair<string, uint16_t> >&a, const vector<pair<string, uint16_t> >& b)
+static vector<ComboAddress>& operator+=(vector<ComboAddress>&a, const vector<ComboAddress>& b)
 {
   a.insert(a.end(), b.begin(), b.end());
   return a;
 }
 
-vector<pair<DNSName, uint16_t> >& operator+=(vector<pair<DNSName, uint16_t> >&a, const vector<pair<DNSName, uint16_t> >& b)
+static vector<pair<DNSName, uint16_t> >& operator+=(vector<pair<DNSName, uint16_t> >&a, const vector<pair<DNSName, uint16_t> >& b)
 {
   a.insert(a.end(), b.begin(), b.end());
   return a;
@@ -3274,7 +3597,7 @@ template<class T> T broadcastAccFunction(const boost::function<T*()>& func)
 
     const auto& tps = threadInfo.pipes;
     ThreadMSG* tmsg = new ThreadMSG();
-    tmsg->func = boost::bind(voider<T>, func);
+    tmsg->func = [func]{ return voider<T>(func); };
     tmsg->wantAnswer = true;
 
     if(write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
@@ -3309,6 +3632,7 @@ static void handleRCC(int fd, FDMultiplexer::funcparam_t& var)
     RecursorControlParser rcp;
     RecursorControlParser::func_t* command;
 
+    g_log << Logger::Notice << "Received rec_control command '" << msg << "' from control socket" << endl;
     string answer=rcp.getAnswer(msg, &command);
 
     // If we are inside a chroot, we need to strip
@@ -3494,7 +3818,7 @@ retryWithName:
   }
 }
 
-FDMultiplexer* getMultiplexer()
+static FDMultiplexer* getMultiplexer()
 {
   FDMultiplexer* ret;
   for(const auto& i : FDMultiplexer::getMultiplexerMap()) {
@@ -3564,7 +3888,7 @@ catch(PDNSException& ae)
 
 string doTraceRegex(vector<string>::const_iterator begin, vector<string>::const_iterator end)
 {
-  return broadcastAccFunction<string>(boost::bind(pleaseUseNewTraceRegex, begin!=end ? *begin : ""));
+  return broadcastAccFunction<string>([=]{ return pleaseUseNewTraceRegex(begin!=end ? *begin : ""); });
 }
 
 static void checkLinuxIPv6Limits()
@@ -3685,7 +4009,7 @@ void parseACLs()
   }
 
   g_initialAllowFrom = allowFrom;
-  broadcastFunction(boost::bind(pleaseSupplantACLs, allowFrom));
+  broadcastFunction([=]{ return pleaseSupplantACLs(allowFrom); });
   oldAllowFrom = nullptr;
 
   l_initialized = true;
@@ -3807,7 +4131,7 @@ static void setupNODThread()
   }
 }
 
-void parseNODWhitelist(const std::string& wlist)
+static void parseNODWhitelist(const std::string& wlist)
 {
   vector<string> parts;
   stringtok(parts, wlist, ",; ");
@@ -3853,6 +4177,8 @@ static void checkSocketDir(void)
 
 static int serviceMain(int argc, char*argv[])
 {
+  int ret = EXIT_SUCCESS;
+
   g_log.setName(s_programname);
   g_log.disableSyslog(::arg().mustDo("disable-syslog"));
   g_log.setTimestamps(::arg().mustDo("log-timestamp"));
@@ -3871,23 +4197,11 @@ static int serviceMain(int argc, char*argv[])
 
   checkLinuxIPv6Limits();
   try {
-    vector<string> addrs;
-    if(!::arg()["query-local-address6"].empty()) {
-      SyncRes::s_doIPv6=true;
-      g_log<<Logger::Warning<<"Enabling IPv6 transport for outgoing queries"<<endl;
-
-      stringtok(addrs, ::arg()["query-local-address6"], ", ;");
-      for(const string& addr : addrs) {
-        g_localQueryAddresses6.push_back(ComboAddress(addr));
-      }
-    }
-    else {
-      g_log<<Logger::Warning<<"NOT using IPv6 for outgoing queries - set 'query-local-address6=::' to enable"<<endl;
-    }
-    addrs.clear();
-    stringtok(addrs, ::arg()["query-local-address"], ", ;");
-    for(const string& addr : addrs) {
-      g_localQueryAddresses4.push_back(ComboAddress(addr));
+    pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
+    if (!::arg()["query-local-address6"].empty()) {
+      // TODO remove in 4.5.0
+      g_log<<Logger::Warning<<"query-local-address6 is deprecated and will be removed in a future version. Please use query-local-address for IPv6 addresses as well"<<endl;
+      pdns::parseQueryLocalAddress(::arg()["query-local-address6"]);
     }
   }
   catch(std::exception& e) {
@@ -3895,6 +4209,28 @@ static int serviceMain(int argc, char*argv[])
     exit(99);
   }
 
+  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+    SyncRes::s_doIPv4=true;
+    g_log<<Logger::Warning<<"Enabling IPv4 transport for outgoing queries"<<endl;
+  }
+  else {
+    g_log<<Logger::Warning<<"NOT using IPv4 for outgoing queries - add an IPv4 address (like '0.0.0.0') to query-local-address to enable"<<endl;
+  }
+
+
+  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+    SyncRes::s_doIPv6=true;
+    g_log<<Logger::Warning<<"Enabling IPv6 transport for outgoing queries"<<endl;
+  }
+  else {
+    g_log<<Logger::Warning<<"NOT using IPv6 for outgoing queries - add an IPv6 address (like '::') to query-local-address to enable"<<endl;
+  }
+
+  if (!SyncRes::s_doIPv6 && !SyncRes::s_doIPv4) {
+    g_log<<Logger::Error<<"No outgoing addresses configured! Can not continue"<<endl;
+    exit(99);
+  }
+
   // keep this ABOVE loadRecursorLuaConfig!
   if(::arg()["dnssec"]=="off")
     g_dnssecmode=DNSSECMode::Off;
@@ -3993,6 +4329,7 @@ static int serviceMain(int argc, char*argv[])
   SyncRes::s_serverdownthrottletime=::arg().asNum("server-down-throttle-time");
   SyncRes::s_serverID=::arg()["server-id"];
   SyncRes::s_maxqperq=::arg().asNum("max-qperq");
+  SyncRes::s_maxnsaddressqperq=::arg().asNum("max-ns-address-qperq");
   SyncRes::s_maxtotusec=1000*::arg().asNum("max-total-msec");
   SyncRes::s_maxdepth=::arg().asNum("max-recursion-depth");
   SyncRes::s_rootNXTrust = ::arg().mustDo( "root-nx-trust");
@@ -4031,26 +4368,25 @@ static int serviceMain(int argc, char*argv[])
     SyncRes::setECSScopeZeroAddress(Netmask(scopeZero, scopeZero.isIPv4() ? 32 : 128));
   }
   else {
-    bool found = false;
-    for (const auto& addr : g_localQueryAddresses4) {
-      if (!IsAnyAddress(addr)) {
-        SyncRes::setECSScopeZeroAddress(Netmask(addr, 32));
-        found = true;
-        break;
+    Netmask nm;
+    bool done = false;
+
+    auto addr = pdns::getNonAnyQueryLocalAddress(AF_INET);
+    if (addr.sin4.sin_family != 0) {
+      nm = Netmask(addr, 32);
+      done = true;
+    }
+    if (!done) {
+      addr = pdns::getNonAnyQueryLocalAddress(AF_INET6);
+      if (addr.sin4.sin_family != 0) {
+        nm = Netmask(addr, 128);
+        done = true;
       }
     }
-    if (!found) {
-      for (const auto& addr : g_localQueryAddresses6) {
-        if (!IsAnyAddress(addr)) {
-          SyncRes::setECSScopeZeroAddress(Netmask(addr, 128));
-          found = true;
-          break;
-        }
-      }
-      if (!found) {
-        SyncRes::setECSScopeZeroAddress(Netmask("127.0.0.1/32"));
-      }
+    if (!done) {
+      nm = Netmask(ComboAddress("127.0.0.1"), 32);
     }
+    SyncRes::setECSScopeZeroAddress(nm);
   }
 
   SyncRes::parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]);
@@ -4060,6 +4396,29 @@ static int serviceMain(int argc, char*argv[])
   g_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
   g_xpfRRCode = ::arg().asNum("xpf-rr-code");
 
+  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+
+  if (!::arg()["dns64-prefix"].empty()) {
+    try {
+      auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
+      if (dns64Prefix.getBits() != 96) {
+        g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl;
+        exit(1);
+      }
+      g_dns64Prefix = dns64Prefix.getNetwork();
+      g_dns64PrefixReverse = reverseNameFromIP(*g_dns64Prefix);
+      /* /96 is 24 nibbles + 2 for "ip6.arpa." */
+      while (g_dns64PrefixReverse.countLabels() > 26) {
+        g_dns64PrefixReverse.chopOff();
+      }
+    }
+    catch (const NetmaskException& ne) {
+      g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl;
+      exit(1);
+    }
+  }
+
   g_networkTimeoutMsec = ::arg().asNum("network-timeout");
 
   g_initialDomainMap = parseAuthAndForwards();
@@ -4107,6 +4466,7 @@ static int serviceMain(int argc, char*argv[])
     }
     g_dontThrottleNames.setState(std::move(dontThrottleNames));
 
+    parts.clear();
     NetmaskGroup dontThrottleNetmasks;
     stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
     for (const auto &p : parts) {
@@ -4194,7 +4554,7 @@ static int serviceMain(int argc, char*argv[])
       For years, this was a safe assumption, but containers change that: in
       most (all?) container implementations, the application itself is running
       as pid 1. This means that sending signals to those applications, will not
-      be handled by default. Results might be "your container not responsing
+      be handled by default. Results might be "your container not responding
       when asking it to stop", or "ctrl-c not working even when the app is
       running in the foreground inside a container".
 
@@ -4341,6 +4701,9 @@ static int serviceMain(int argc, char*argv[])
     recursorThread(currentThreadId++, "worker");
     
     handlerInfos.thread.join();
+    if (handlerInfos.exitCode != 0) {
+      ret = handlerInfos.exitCode;
+    }
   }
   else {
 
@@ -4385,13 +4748,16 @@ static int serviceMain(int argc, char*argv[])
 
     for (auto & ti : s_threadInfos) {
       ti.thread.join();
+      if (ti.exitCode != 0) {
+        ret = ti.exitCode;
+      }
     }
   }
 
 #ifdef HAVE_PROTOBUF
   google::protobuf::ShutdownProtobufLibrary();
 #endif /* HAVE_PROTOBUF */
-  return 0;
+  return ret;
 }
 
 static void* recursorThread(unsigned int n, const string& threadName)
@@ -4408,11 +4774,19 @@ try
   t_allowFrom = g_initialAllowFrom;
   t_udpclientsocks = std::unique_ptr<UDPClientSocks>(new UDPClientSocks());
   t_tcpClientCounts = std::unique_ptr<tcpClientCounts_t>(new tcpClientCounts_t());
-  primeHints();
+
+  if (threadInfo.isHandler) {
+    if (!primeHints()) {
+      threadInfo.exitCode = EXIT_FAILURE;
+      RecursorControlChannel::stop = 1;
+      g_log<<Logger::Critical<<"Priming cache failed, stopping"<<endl;
+      return nullptr;
+    }
+    g_log<<Logger::Warning<<"Done priming cache with root hints"<<endl;
+  }
 
   t_packetCache = std::unique_ptr<RecursorPacketCache>(new RecursorPacketCache());
 
-  g_log<<Logger::Warning<<"Done priming cache with root hints"<<endl;
 
 #ifdef NOD_ENABLED
   if (threadInfo.isWorker)
@@ -4457,7 +4831,6 @@ try
     t_bogusqueryring = std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t> > >(new boost::circular_buffer<pair<DNSName, uint16_t> >());
     t_bogusqueryring->set_capacity(ringsize);
   }
-
   MT=std::unique_ptr<MTasker<PacketID,string> >(new MTasker<PacketID,string>(::arg().asNum("stack-size")));
   threadInfo.mt = MT.get();
 
@@ -4498,13 +4871,13 @@ try
     if (threadInfo.isListener) {
       if (g_reusePort) {
         /* then every listener has its own FDs */
-        for(const auto deferred : threadInfo.deferredAdds) {
+        for(const auto& deferred : threadInfo.deferredAdds) {
           t_fdm->addReadFD(deferred.first, deferred.second);
         }
       }
       else {
         /* otherwise all listeners are listening on the same ones */
-        for(const auto deferred : g_deferredAdds) {
+        for(const auto& deferred : g_deferredAdds) {
           t_fdm->addReadFD(deferred.first, deferred.second);
         }
       }
@@ -4530,7 +4903,9 @@ try
   while (!RecursorControlChannel::stop) {
     while(MT->schedule(&g_now)); // MTasker letting the mthreads do their thing
 
-    if(!(counter%500)) {
+    // Use primes, it avoid not being scheduled in cases where the counter has a regular pattern.
+    // We want to call handler thread often, it gets scheduled about 2 times per second
+    if ((threadInfo.isHandler && counter % 11 == 0) || counter % 499 == 0) {
       MT->makeThread(houseKeeping, 0);
     }
 
@@ -4681,10 +5056,19 @@ int main(int argc, char **argv)
     ::arg().set("socket-group","Group of socket")="";
     ::arg().set("socket-mode", "Permissions for socket")="";
 
-    ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns-recursor when unset and not chrooted" )="";
+    ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns-recursor when unset and not chrooted"
+#ifdef HAVE_SYSTEMD
+      + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")="";
+   auto runtimeDir = getenv("RUNTIME_DIRECTORY");
+   if (runtimeDir != nullptr) {
+     ::arg().set("socket-dir") = runtimeDir;
+   }
+#else
+      )="";
+#endif
     ::arg().set("delegation-only","Which domains we only accept delegations from")="";
     ::arg().set("query-local-address","Source IP address for sending queries")="0.0.0.0";
-    ::arg().set("query-local-address6","Source IPv6 address for sending queries. IF UNSET, IPv6 WILL NOT BE USED FOR OUTGOING QUERIES")="";
+    ::arg().set("query-local-address6","DEPRECATED: Use query-local-address for IPv6 as well. Source IPv6 address for sending queries. IF UNSET, IPv6 WILL NOT BE USED FOR OUTGOING QUERIES")="";
     ::arg().set("client-tcp-timeout","Timeout in seconds when talking to TCP clients")="2";
     ::arg().set("max-mthreads", "Maximum number of simultaneous Mtasker threads")="2048";
     ::arg().set("max-tcp-clients","Maximum number of simultaneous TCP clients")="128";
@@ -4746,6 +5130,7 @@ int main(int argc, char **argv)
     ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1232";
     ::arg().set("minimum-ttl-override", "Set under adverse conditions, a minimum TTL")="0";
     ::arg().set("max-qperq", "Maximum outgoing queries per query")="60";
+    ::arg().set("max-ns-address-qperq", "Maximum outgoing NS address queries per query")="10";
     ::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited")="7000";
     ::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited")="40";
     ::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing")="10000";
@@ -4782,6 +5167,11 @@ int main(int argc, char **argv)
     ::arg().set("xpf-allow-from","XPF information is only processed from these subnets")="";
     ::arg().set("xpf-rr-code","XPF option code to use")="0";
 
+    ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets")="";
+    ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values")="512";
+
+    ::arg().set("dns64-prefix", "DNS64 prefix")="";
+
     ::arg().set("udp-source-port-min", "Minimum UDP port to bind on")="1024";
     ::arg().set("udp-source-port-max", "Maximum UDP port to bind on")="65535";
     ::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid")="11211";
@@ -4792,7 +5182,7 @@ int main(int argc, char **argv)
     ::arg().setSwitch("qname-minimization", "Use Query Name Minimization")="yes";
     ::arg().setSwitch("nothing-below-nxdomain", "When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)")="dnssec";
     ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
-    ::arg().set("cache-shards", "Number of shards in the record cache")="1024";
+    ::arg().set("record-cache-shards", "Number of shards in the record cache")="1024";
 
 #ifdef NOD_ENABLED
     ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).")="no";
@@ -4829,7 +5219,7 @@ int main(int argc, char **argv)
       }
       cerr<<" (";
       bool first = true;
-      for (auto const c : ::arg().getCommands()) {
+      for (const auto& c : ::arg().getCommands()) {
         if (!first) {
           cerr<<", ";
         }
@@ -4891,7 +5281,8 @@ int main(int argc, char **argv)
       exit(0);
     }
 
-    s_RC = std::unique_ptr<MemRecursorCache>(new MemRecursorCache(::arg().asNum("cache-shards")));
+    g_recCache = std::unique_ptr<MemRecursorCache>(new MemRecursorCache(::arg().asNum("record-cache-shards")));
+    g_negCache = std::unique_ptr<NegCache>(new NegCache(::arg().asNum("record-cache-shards")));
 
     Logger::Urgency logUrgency = (Logger::Urgency)::arg().asNum("loglevel");
 
@@ -4903,7 +5294,7 @@ int main(int argc, char **argv)
     g_log.setLoglevel(logUrgency);
     g_log.toConsole(logUrgency);
 
-    serviceMain(argc, argv);
+    ret = serviceMain(argc, argv);
   }
   catch(PDNSException &ae) {
     g_log<<Logger::Error<<"Exception: "<<ae.reason<<endl;
index cf2a2b63b03d81f305734243931cc0a544dc7843..d824af6e81c2118b5c9bf50ae5cac51deb42a0fd 100644 (file)
@@ -2,6 +2,9 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+
+#include <fcntl.h>
+
 #include "dnsseckeeper.hh"
 #include "dnssecinfra.hh"
 #include "statbag.hh"
@@ -51,7 +54,15 @@ ArgvMap &arg()
   return arg;
 }
 
-void loadMainConfig(const std::string& configdir)
+static std::string comboAddressVecToString(const std::vector<ComboAddress>& vec) {
+  vector<string> strs;
+  for (const auto& ca : vec) {
+    strs.push_back(ca.toStringWithPortExcept(53));
+  }
+  return boost::join(strs, ",");
+}
+
+static void loadMainConfig(const std::string& configdir)
 {
   ::arg().set("config-dir","Location of configuration directory (pdns.conf)")=configdir;
   ::arg().set("default-ttl","Seconds a result is valid if not set otherwise")="3600";
@@ -149,7 +160,7 @@ void loadMainConfig(const std::string& configdir)
   UeberBackend::go();
 }
 
-bool rectifyZone(DNSSECKeeper& dk, const DNSName& zone, bool quiet = false, bool rectifyTransaction = true)
+static bool rectifyZone(DNSSECKeeper& dk, const DNSName& zone, bool quiet = false, bool rectifyTransaction = true)
 {
   string output;
   string error;
@@ -166,7 +177,7 @@ bool rectifyZone(DNSSECKeeper& dk, const DNSName& zone, bool quiet = false, bool
   return ret;
 }
 
-void dbBench(const std::string& fname)
+static void dbBench(const std::string& fname)
 {
   ::arg().set("query-cache-ttl")="0";
   ::arg().set("negquery-cache-ttl")="0";
@@ -209,7 +220,7 @@ void dbBench(const std::string& fname)
   cout<<"Packet cache reports: "<<S.read("query-cache-hit")<<" hits (should be 0) and "<<S.read("query-cache-miss") <<" misses"<<endl;
 }
 
-bool rectifyAllZones(DNSSECKeeper &dk, bool quiet = false)
+static bool rectifyAllZones(DNSSECKeeper &dk, bool quiet = false)
 {
   UeberBackend B("default");
   vector<DomainInfo> domainInfo;
@@ -230,7 +241,7 @@ bool rectifyAllZones(DNSSECKeeper &dk, bool quiet = false)
   return result;
 }
 
-int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, const vector<DNSResourceRecord>* suppliedrecords=0)
+static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, const vector<DNSResourceRecord>* suppliedrecords=0)
 {
   uint64_t numerrors=0, numwarnings=0;
 
@@ -355,7 +366,7 @@ int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, const vect
       rr.content = "\""+rr.content+"\"";
 
     try {
-      shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
+      shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
       string tmp=drc->serialize(rr.qname);
       tmp = drc->getZoneRepresentation(true);
       if (rr.qtype.getCode() != QType::AAAA) {
@@ -604,7 +615,7 @@ int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, const vect
   return EXIT_FAILURE;
 }
 
-int checkAllZones(DNSSECKeeper &dk, bool exitOnError)
+static int checkAllZones(DNSSECKeeper &dk, bool exitOnError)
 {
   UeberBackend B("default");
   vector<DomainInfo> domainInfo;
@@ -648,7 +659,7 @@ int checkAllZones(DNSSECKeeper &dk, bool exitOnError)
   return EXIT_FAILURE;
 }
 
-int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
+static int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
 {
   UeberBackend B("default");
   SOAData sd;
@@ -698,7 +709,7 @@ int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
   return 0;
 }
 
-int deleteZone(const DNSName &zone) {
+static int deleteZone(const DNSName &zone) {
   UeberBackend B;
   DomainInfo di;
   if (! B.getDomainInfo(zone, di)) {
@@ -713,7 +724,7 @@ int deleteZone(const DNSName &zone) {
   return EXIT_FAILURE;
 }
 
-void listKey(DomainInfo const &di, DNSSECKeeper& dk, bool printHeader = true) {
+static void listKey(DomainInfo const &di, DNSSECKeeper& dk, bool printHeader = true) {
   if (printHeader) {
     cout<<"Zone                          Type    Size    Algorithm    ID   Location    Keytag"<<endl;
     cout<<"----------------------------------------------------------------------------------"<<endl;
@@ -768,7 +779,7 @@ void listKey(DomainInfo const &di, DNSSECKeeper& dk, bool printHeader = true) {
   }
 }
 
-int listKeys(const string &zname, DNSSECKeeper& dk){
+static int listKeys(const string &zname, DNSSECKeeper& dk){
   UeberBackend B("default");
 
   if (zname != "all") {
@@ -782,7 +793,7 @@ int listKeys(const string &zname, DNSSECKeeper& dk){
     vector<DomainInfo> domainInfo;
     B.getAllDomains(&domainInfo);
     bool printHeader = true;
-    for (auto const di : domainInfo) {
+    for (const auto& di : domainInfo) {
       listKey(di, dk, printHeader);
       printHeader = false;
     }
@@ -790,7 +801,7 @@ int listKeys(const string &zname, DNSSECKeeper& dk){
   return EXIT_SUCCESS;
 }
 
-int listZone(const DNSName &zone) {
+static int listZone(const DNSName &zone) {
   UeberBackend B;
   DomainInfo di;
 
@@ -816,7 +827,7 @@ int listZone(const DNSName &zone) {
 }
 
 // lovingly copied from http://stackoverflow.com/questions/1798511/how-to-avoid-press-enter-with-any-getchar
-int read1char(){
+static int read1char(){
     int c;
     static struct termios oldt, newt;
 
@@ -843,7 +854,7 @@ int read1char(){
     return c;
 }
 
-int clearZone(DNSSECKeeper& dk, const DNSName &zone) {
+static int clearZone(DNSSECKeeper& dk, const DNSName &zone) {
   UeberBackend B;
   DomainInfo di;
 
@@ -859,7 +870,7 @@ int clearZone(DNSSECKeeper& dk, const DNSName &zone) {
   return EXIT_SUCCESS;
 }
 
-int editZone(const DNSName &zone) {
+static int editZone(const DNSName &zone) {
   UeberBackend B;
   DomainInfo di;
   DNSSECKeeper dk(&B);
@@ -1076,7 +1087,7 @@ static int xcryptIP(const std::string& cmd, const std::string& ip, const std::st
 }
 
 
-int loadZone(DNSName zone, const string& fname) {
+static int loadZone(DNSName zone, const string& fname) {
   UeberBackend B;
   DomainInfo di;
 
@@ -1085,7 +1096,7 @@ int loadZone(DNSName zone, const string& fname) {
   }
   else {
     cerr<<"Creating '"<<zone<<"'"<<endl;
-    B.createDomain(zone);
+    B.createDomain(zone, DomainInfo::Native, vector<ComboAddress>(), "");
 
     if(!B.getDomainInfo(zone, di)) {
       cerr<<"Domain '"<<zone<<"' was not created - perhaps backend ("<<::arg()["launch"]<<") does not support storing new zones."<<endl;
@@ -1120,7 +1131,7 @@ int loadZone(DNSName zone, const string& fname) {
   return EXIT_SUCCESS;
 }
 
-int createZone(const DNSName &zone, const DNSName& nsname) {
+static int createZone(const DNSName &zone, const DNSName& nsname) {
   UeberBackend B;
   DomainInfo di;
   if (B.getDomainInfo(zone, di)) {
@@ -1128,7 +1139,7 @@ int createZone(const DNSName &zone, const DNSName& nsname) {
     return EXIT_FAILURE;
   }
   cerr<<"Creating empty zone '"<<zone<<"'"<<endl;
-  B.createDomain(zone);
+  B.createDomain(zone, DomainInfo::Native, vector<ComboAddress>(), "");
   if(!B.getDomainInfo(zone, di)) {
     cerr<<"Domain '"<<zone<<"' was not created!"<<endl;
     return EXIT_FAILURE;
@@ -1162,7 +1173,7 @@ int createZone(const DNSName &zone, const DNSName& nsname) {
   return EXIT_SUCCESS;
 }
 
-int createSlaveZone(const vector<string>& cmds) {
+static int createSlaveZone(const vector<string>& cmds) {
   UeberBackend B;
   DomainInfo di;
   DNSName zone(cmds[1]);
@@ -1170,23 +1181,20 @@ int createSlaveZone(const vector<string>& cmds) {
     cerr<<"Domain '"<<zone<<"' exists already"<<endl;
     return EXIT_FAILURE;
   }
-  vector<string> masters;
+  vector<ComboAddress> masters;
   for (unsigned i=2; i < cmds.size(); i++) {
-    ComboAddress master(cmds[i], 53);
-    masters.push_back(master.toStringWithPort());
+    masters.emplace_back(cmds[i], 53);
   }
-  cerr<<"Creating slave zone '"<<zone<<"', with master(s) '"<<boost::join(masters, ",")<<"'"<<endl;
-  B.createDomain(zone);
+  cerr<<"Creating slave zone '"<<zone<<"', with master(s) '"<<comboAddressVecToString(masters)<<"'"<<endl;
+  B.createDomain(zone, DomainInfo::Slave, masters, "");
   if(!B.getDomainInfo(zone, di)) {
     cerr<<"Domain '"<<zone<<"' was not created!"<<endl;
     return EXIT_FAILURE;
   }
-  di.backend->setKind(zone, DomainInfo::Slave);
-  di.backend->setMaster(zone, boost::join(masters, ","));
   return EXIT_SUCCESS;
 }
 
-int changeSlaveZoneMaster(const vector<string>& cmds) {
+static int changeSlaveZoneMaster(const vector<string>& cmds) {
   UeberBackend B;
   DomainInfo di;
   DNSName zone(cmds[1]);
@@ -1194,14 +1202,13 @@ int changeSlaveZoneMaster(const vector<string>& cmds) {
     cerr<<"Domain '"<<zone<<"' doesn't exist"<<endl;
     return EXIT_FAILURE;
   }
-  vector<string> masters;
+  vector<ComboAddress> masters;
   for (unsigned i=2; i < cmds.size(); i++) {
-    ComboAddress master(cmds[i], 53);
-    masters.push_back(master.toStringWithPort());
+    masters.emplace_back(cmds[i], 53);
   }
-  cerr<<"Updating slave zone '"<<zone<<"', master(s) to '"<<boost::join(masters, ",")<<"'"<<endl;
+  cerr<<"Updating slave zone '"<<zone<<"', master(s) to '"<<comboAddressVecToString(masters)<<"'"<<endl;
   try {
-    di.backend->setMaster(zone, boost::join(masters, ","));
+    di.backend->setMasters(zone, masters);
     return EXIT_SUCCESS;
   }
   catch (PDNSException& e) {
@@ -1211,7 +1218,7 @@ int changeSlaveZoneMaster(const vector<string>& cmds) {
 }
 
 // add-record ZONE name type [ttl] "content" ["content"]
-int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
+static int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
   DNSResourceRecord rr;
   vector<DNSResourceRecord> newrrs;
   DNSName zone(cmds[1]);
@@ -1301,8 +1308,19 @@ int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
   return EXIT_SUCCESS;
 }
 
+// addSuperMaster add anew super master
+static int addSuperMaster(const std::string &IP, const std::string &nameserver, const std::string &account)
+{
+  UeberBackend B("default");
+
+  if ( B.superMasterAdd(IP, nameserver, account) ){ 
+    return EXIT_SUCCESS; 
+  }
+  return EXIT_FAILURE;
+}
+
 // delete-rrset zone name type
-int deleteRRSet(const std::string& zone_, const std::string& name_, const std::string& type_)
+static int deleteRRSet(const std::string& zone_, const std::string& name_, const std::string& type_)
 {
   UeberBackend B;
   DomainInfo di;
@@ -1325,7 +1343,7 @@ int deleteRRSet(const std::string& zone_, const std::string& name_, const std::s
   return EXIT_SUCCESS;
 }
 
-int listAllZones(const string &type="") {
+static int listAllZones(const string &type="") {
 
   int kindFilter = -1;
   if (type.size()) {
@@ -1364,17 +1382,17 @@ int listAllZones(const string &type="") {
   return 0;
 }
 
-bool testAlgorithm(int algo)
+static bool testAlgorithm(int algo)
 {
   return DNSCryptoKeyEngine::testOne(algo);
 }
 
-bool testAlgorithms()
+static bool testAlgorithms()
 {
   return DNSCryptoKeyEngine::testAll();
 }
 
-void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remote, int cores)
+static void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remote, int cores)
 {
   DNSResourceRecord rr;
   rr.qname=DNSName("blah")+zone;
@@ -1420,7 +1438,7 @@ void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remote, int
   cerr<<"Net speed: "<<csp.d_signed/ (dt.udiff()/1000000.0) << " sigs/s"<<endl;
 }
 
-void verifyCrypto(const string& zone)
+static void verifyCrypto(const string& zone)
 {
   ZoneParserTNG zpt(zone);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
@@ -1458,7 +1476,8 @@ void verifyCrypto(const string& zone)
     cerr<<"Original DS:   "<<apex.toString()<<" IN DS "<<dsrc.getZoneRepresentation()<<endl;
   }
 }
-bool disableDNSSECOnZone(DNSSECKeeper& dk, const DNSName& zone)
+
+static bool disableDNSSECOnZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -1476,7 +1495,7 @@ bool disableDNSSECOnZone(DNSSECKeeper& dk, const DNSName& zone)
   return ret;
 }
 
-int setZoneAccount(const DNSName& zone, const string &account)
+static int setZoneAccount(const DNSName& zone, const string &account)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -1492,7 +1511,7 @@ int setZoneAccount(const DNSName& zone, const string &account)
   return EXIT_SUCCESS;
 }
 
-int setZoneKind(const DNSName& zone, const DomainInfo::DomainKind kind)
+static int setZoneKind(const DNSName& zone, const DomainInfo::DomainKind kind)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -1508,7 +1527,7 @@ int setZoneKind(const DNSName& zone, const DomainInfo::DomainKind kind)
   return EXIT_SUCCESS;
 }
 
-bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
+static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -1594,7 +1613,7 @@ bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
       cout<<endl;
 
       for(const auto& m : metamap) {
-        for(const auto i : m.second)
+        for(const auto& i : m.second)
           cout << '\t' << m.first<<'\t' << i <<endl;
       }
     }
@@ -1681,7 +1700,7 @@ bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
         cout<<"ID = "<<value.second.id<<" ("<<DNSSECKeeper::keyTypeToString(value.second.keyType)<<")";
       }
       if (value.first.getKey()->getBits() < 1) {
-        cerr<<" <key missing or defunct>" <<endl;
+        cout<<" <key missing or defunct, perhaps you should run pdnsutil hsm create-key>" <<endl;
         continue;
       }
       if (!exportDS) {
@@ -1718,7 +1737,7 @@ bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
   return true;
 }
 
-bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
+static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   // parse attribute
   int k_size;
@@ -1809,7 +1828,7 @@ bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
   return true;
 }
 
-void testSchema(DNSSECKeeper& dk, const DNSName& zone)
+static int testSchema(DNSSECKeeper& dk, const DNSName& zone)
 {
   cout<<"Note: test-schema will try to create the zone, but it will not remove it."<<endl;
   cout<<"Please clean up after this."<<endl;
@@ -1826,7 +1845,7 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
   DomainInfo di;
   if(!B.getDomainInfo(zone, di) || !di.backend) { // di.backend and B are mostly identical
     cout<<"Can't find domain we just created, aborting"<<endl;
-    return;
+    return EXIT_FAILURE;
   }
   db=di.backend;
   DNSResourceRecord rr, rrget;
@@ -1856,13 +1875,13 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
     if(db->get(rrthrowaway)) // should not touch rr but don't assume anything
     {
       cout<<"Expected one record, got multiple, aborting"<<endl;
-      exit(EXIT_FAILURE);
+      return EXIT_FAILURE;
     }
     int size=rrget.content.size();
     if(size != 302)
     {
       cout<<"Expected 302 bytes, got "<<size<<", aborting"<<endl;
-      exit(EXIT_FAILURE);
+      return EXIT_FAILURE;
     }
   }
   cout<<"[+] content field is over 255 bytes"<<endl;
@@ -1899,12 +1918,12 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
   if(before != DNSName("_underscore")+zone)
   {
     cout<<"before is wrong, got '"<<before.toString()<<"', expected '_underscore."<<zone.toString()<<"', aborting"<<endl;
-    exit(EXIT_FAILURE);
+    return EXIT_FAILURE;
   }
   if(after != zone)
   {
     cout<<"after is wrong, got '"<<after.toString()<<"', expected '"<<zone.toString()<<"', aborting"<<endl;
-    exit(EXIT_FAILURE);
+    return EXIT_FAILURE;
   }
   cout<<"[+] ordername sorting is correct for names starting with _"<<endl;
   cout<<"Setting low notified serial"<<endl;
@@ -1912,7 +1931,7 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
   db->getDomainInfo(zone, di);
   if(di.notified_serial != 500) {
     cout<<"[-] Set serial 500, got back "<<di.notified_serial<<", aborting"<<endl;
-    exit(EXIT_FAILURE);
+    return EXIT_FAILURE;
   }
   cout<<"Setting serial that needs 32 bits"<<endl;
   try {
@@ -1920,20 +1939,22 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
   } catch(const PDNSException &pe) {
     cout<<"While setting serial, got error: "<<pe.reason<<endl;
     cout<<"aborting"<<endl;
-    exit(EXIT_FAILURE);
+    return EXIT_FAILURE;
   }
   db->getDomainInfo(zone, di);
   if(di.notified_serial != 2147484148) {
     cout<<"[-] Set serial 2147484148, got back "<<di.notified_serial<<", aborting"<<endl;
-    exit(EXIT_FAILURE);
+    return EXIT_FAILURE;
   } else {
     cout<<"[+] Big serials work correctly"<<endl;
   }
   cout<<endl;
   cout<<"End of tests, please remove "<<zone<<" from domains+records"<<endl;
+
+  return EXIT_SUCCESS;
 }
 
-int addOrSetMeta(const DNSName& zone, const string& kind, const vector<string>& values, bool clobber) {
+static int addOrSetMeta(const DNSName& zone, const string& kind, const vector<string>& values, bool clobber) {
   UeberBackend B("default");
   DomainInfo di;
 
@@ -1997,6 +2018,8 @@ try
     cout<<"activate-zone-key ZONE KEY-ID      Activate the key with key id KEY-ID in ZONE"<<endl;
     cout<<"add-record ZONE NAME TYPE [ttl] content"<<endl;
     cout<<"             [content..]           Add one or more records to ZONE"<<endl;
+    cout<<"add-supermaster IP NAMESERVER [account]"<<endl;
+    cout<<"                                   Add a new super-master "<<endl;
     cout<<"add-zone-key ZONE {zsk|ksk} [BITS] [active|inactive] [published|unpublished]"<<endl;
     cout<<"             [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
 #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
@@ -2046,7 +2069,7 @@ try
     cout<<"import-tsig-key NAME ALGORITHM KEY Import TSIG key"<<endl;
     cout<<"import-zone-key ZONE FILE          Import from a file a private key, ZSK or KSK"<<endl;
     cout<<"       [active|inactive] [ksk|zsk] [published|unpublished] Defaults to KSK, active and published"<<endl;
-    cout<<"ipdecrypt IP passphrase/key [key]  Encrypt IP address using passphrase or base64 key"<<endl;
+    cout<<"ipdecrypt IP passphrase/key [key]  Decrypt IP address using passphrase or base64 key"<<endl;
     cout<<"ipencrypt IP passphrase/key [key]  Encrypt IP address using passphrase or base64 key"<<endl;
     cout<<"load-zone ZONE FILE                Load ZONE from FILE, possibly creating zone or atomically"<<endl;
     cout<<"                                   replacing contents"<<endl;
@@ -2132,7 +2155,7 @@ try
     cout<<"DNSKEY algorithms supported by this installation of PowerDNS:"<<endl;
 
     auto algosWithBackend = DNSCryptoKeyEngine::listAllAlgosWithBackend();
-    for (auto const algoWithBackend : algosWithBackend){
+    for (const auto& algoWithBackend : algosWithBackend){
       string algoName = DNSSECKeeper::algorithm2name(algoWithBackend.first);
       cout<<std::to_string(algoWithBackend.first)<<" - "<<algoName;
       if (cmds.size() == 2 && cmds[1] == "with-backend")
@@ -2175,8 +2198,7 @@ try
       cerr << "Syntax: pdnsutil test-schema ZONE"<<endl;
       return 0;
     }
-    testSchema(dk, DNSName(cmds[1]));
-    return 0;
+    return testSchema(dk, DNSName(cmds[1]));
   }
   if(cmds[0] == "rectify-zone") {
     if(cmds.size() < 2) {
@@ -2201,14 +2223,14 @@ try
       return 0;
     }
     UeberBackend B("default");
-    exit(checkZone(dk, B, DNSName(cmds[1])));
+    return checkZone(dk, B, DNSName(cmds[1]));
   }
   else if(cmds[0] == "bench-db") {
     dbBench(cmds.size() > 1 ? cmds[1] : "");
   }
   else if (cmds[0] == "check-all-zones") {
     bool exitOnError = ((cmds.size() >= 2 ? cmds[1] : "") == "exit-on-error");
-    exit(checkAllZones(dk, exitOnError));
+    return checkAllZones(dk, exitOnError);
   }
   else if (cmds[0] == "list-all-zones") {
     if (cmds.size() > 2) {
@@ -2397,13 +2419,13 @@ try
         bits = pdns_stou(cmds[n]);
       } else {
         cerr<<"Unknown algorithm, key flag or size '"<<cmds[n]<<"'"<<endl;
-        exit(EXIT_FAILURE);;
+        return EXIT_FAILURE;
       }
     }
     int64_t id;
     if (!dk.addKey(zone, keyOrZone, algorithm, id, bits, active, published)) {
       cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
-      exit(1);
+      return 1;
     } else {
       cerr<<"Added a " << (keyOrZone ? "KSK" : "ZSK")<<" with algorithm = "<<algorithm<<", active="<<active<<endl;
       if (bits)
@@ -2435,49 +2457,56 @@ try
       cerr<<"Syntax: pdnsutil delete-zone ZONE"<<endl;
       return 0;
     }
-    exit(deleteZone(DNSName(cmds[1])));
+    return deleteZone(DNSName(cmds[1]));
   }
   else if(cmds[0] == "create-zone") {
     if(cmds.size() != 2 && cmds.size()!=3 ) {
       cerr<<"Syntax: pdnsutil create-zone ZONE [nsname]"<<endl;
       return 0;
     }
-    exit(createZone(DNSName(cmds[1]), cmds.size() > 2 ? DNSName(cmds[2]): DNSName()));
+    return createZone(DNSName(cmds[1]), cmds.size() > 2 ? DNSName(cmds[2]): DNSName());
   }
   else if(cmds[0] == "create-slave-zone") {
     if(cmds.size() < 3 ) {
       cerr<<"Syntax: pdnsutil create-slave-zone ZONE master-ip [master-ip..]"<<endl;
       return 0;
     }
-    exit(createSlaveZone(cmds));
+    return createSlaveZone(cmds);
   }
   else if(cmds[0] == "change-slave-zone-master") {
     if(cmds.size() < 3 ) {
       cerr<<"Syntax: pdnsutil change-slave-zone-master ZONE master-ip [master-ip..]"<<endl;
       return 0;
     }
-    exit(changeSlaveZoneMaster(cmds));
+    return changeSlaveZoneMaster(cmds);
   }
   else if(cmds[0] == "add-record") {
     if(cmds.size() < 5) {
       cerr<<"Syntax: pdnsutil add-record ZONE name type [ttl] \"content\" [\"content\"...]"<<endl;
       return 0;
     }
-    exit(addOrReplaceRecord(true, cmds));
+    return addOrReplaceRecord(true, cmds);
+  }
+  else if(cmds[0] == "add-supermaster") {
+    if(cmds.size() < 3) {
+      cerr<<"Syntax: pdnsutil add-supermaster IP NAMESERVER [account]"<<endl;
+      return 0;
+    }
+    exit(addSuperMaster(cmds[1], cmds[2], cmds.size() > 3 ? cmds[3] : "" ));
   }
   else if(cmds[0] == "replace-rrset") {
     if(cmds.size() < 5) {
       cerr<<"Syntax: pdnsutil replace-rrset ZONE name type [ttl] \"content\" [\"content\"...]"<<endl;
       return 0;
     }
-    exit(addOrReplaceRecord(false , cmds));
+    return addOrReplaceRecord(false , cmds);
   }
   else if(cmds[0] == "delete-rrset") {
     if(cmds.size() != 4) {
       cerr<<"Syntax: pdnsutil delete-rrset ZONE name type"<<endl;
       return 0;
     }
-    exit(deleteRRSet(cmds[1], cmds[2], cmds[3]));
+    return deleteRRSet(cmds[1], cmds[2], cmds[3]);
   }
   else if(cmds[0] == "list-zone") {
     if(cmds.size() != 2) {
@@ -2487,7 +2516,7 @@ try
     if(cmds[1]==".")
       cmds[1].clear();
 
-    exit(listZone(DNSName(cmds[1])));
+    return listZone(DNSName(cmds[1]));
   }
   else if(cmds[0] == "edit-zone") {
     if(cmds.size() != 2) {
@@ -2497,7 +2526,7 @@ try
     if(cmds[1]==".")
       cmds[1].clear();
 
-    exit(editZone(DNSName(cmds[1])));
+    return editZone(DNSName(cmds[1]));
   }
   else if(cmds[0] == "clear-zone") {
     if(cmds.size() != 2) {
@@ -2507,7 +2536,7 @@ try
     if(cmds[1]==".")
       cmds[1].clear();
 
-    exit(clearZone(dk, DNSName(cmds[1])));
+    return clearZone(dk, DNSName(cmds[1]));
   }
   else if(cmds[0] == "list-keys") {
     if(cmds.size() > 2) {
@@ -2515,7 +2544,7 @@ try
       return 0;
     }
     string zname = (cmds.size() == 2) ? cmds[1] : "all";
-    exit(listKeys(zname, dk));
+    return listKeys(zname, dk);
   }
   else if(cmds[0] == "load-zone") {
     if(cmds.size() < 3) {
@@ -2596,7 +2625,7 @@ try
     }
     DNSName zone(cmds[1]);
     auto kind=DomainInfo::stringToKind(cmds[2]);
-    exit(setZoneKind(zone, kind));
+    return setZoneKind(zone, kind);
   }
   else if(cmds[0]=="set-account") {
     if(cmds.size() != 3) {
@@ -2604,7 +2633,7 @@ try
       return 0;
     }
     DNSName zone(cmds[1]);
-    exit(setZoneAccount(zone, cmds[2]));
+    return setZoneAccount(zone, cmds[2]);
   }
   else if(cmds[0]=="set-nsec3") {
     if(cmds.size() < 2) {
@@ -2745,7 +2774,7 @@ try
   else if(cmds[0]=="export-zone-key") {
     if(cmds.size() < 3) {
       cerr<<"Syntax: pdnsutil export-zone-key ZONE KEY-ID"<<endl;
-      return 0;
+      return 1;
     }
 
     string zone=cmds[1];
@@ -2756,14 +2785,14 @@ try
   else if(cmds[0]=="increase-serial") {
     if (cmds.size() < 2) {
       cerr<<"Syntax: pdnsutil increase-serial ZONE"<<endl;
-      return 0;
+      return 1;
     }
     return increaseSerial(DNSName(cmds[1]), dk);
   }
   else if(cmds[0]=="import-zone-key-pem") {
     if(cmds.size() < 4) {
       cerr<<"Syntax: pdnsutil import-zone-key-pem ZONE FILE ALGORITHM {ksk|zsk}"<<endl;
-      exit(1);
+      return 1;
     }
     string zone=cmds[1];
     string fname=cmds[2];
@@ -2796,7 +2825,7 @@ try
         dpk.d_flags = 257;
       else {
         cerr<<"Unknown key flag '"<<cmds[4]<<"'"<<endl;
-        exit(1);
+        return 1;
       }
     }
     else
@@ -2805,7 +2834,7 @@ try
     int64_t id;
     if (!dk.addKey(DNSName(zone), dpk, id)) {
       cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
-      exit(1);
+      return 1;
     }
     if (id == -1) {
       cerr<<std::to_string(id)<<"Key was added, but backend does not support returning of key id"<<endl;
@@ -2819,7 +2848,7 @@ try
   else if(cmds[0]=="import-zone-key") {
     if(cmds.size() < 3) {
       cerr<<"Syntax: pdnsutil import-zone-key ZONE FILE [ksk|zsk] [active|inactive]"<<endl;
-      exit(1);
+      return 1;
     }
     string zone=cmds[1];
     string fname=cmds[2];
@@ -2851,13 +2880,13 @@ try
         published = 0;
       else {
         cerr<<"Unknown key flag '"<<cmds[n]<<"'"<<endl;
-        exit(1);
+        return 1;
       }
     }
     int64_t id;
     if (!dk.addKey(DNSName(zone), dpk, id, active, published)) {
       cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
-      exit(1);
+      return 1;
     }
     if (id == -1) {
       cerr<<std::to_string(id)<<"Key was added, but backend does not support returning of key id"<<endl;
@@ -2870,7 +2899,7 @@ try
   else if(cmds[0]=="export-zone-dnskey") {
     if(cmds.size() < 3) {
       cerr<<"Syntax: pdnsutil export-zone-dnskey ZONE KEY-ID"<<endl;
-      exit(1);
+      return 1;
     }
 
     DNSName zone(cmds[1]);
@@ -3088,7 +3117,7 @@ try
     if (cmds.size() > 2) {
       keys.assign(cmds.begin() + 2, cmds.end());
       std::cout << "Metadata for '" << zone << "'" << endl;
-      for(const string kind :  keys) {
+      for(const auto& kind :  keys) {
         vector<string> meta;
         meta.clear();
         if (B.getDomainMetadata(zone, kind, meta)) {
@@ -3113,7 +3142,7 @@ try
     DNSName zone(cmds[1]);
     string kind = cmds[2];
     static vector<string> multiMetaWhitelist = {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
-      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE", "GSS-ALLOW-AXFR-PRINCIPAL",
+      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE",
       "PUBLISH-CDS"};
     bool clobber = true;
     if (cmds[0] == "add-meta") {
@@ -3263,7 +3292,7 @@ try
          return 1;
       }
 
-      cerr << "Key of size " << bits << " created" << std::endl;
+      cerr << "Key of size " << dke->getBits() << " created" << std::endl;
       return 0;
     }
 #else
@@ -3308,19 +3337,8 @@ try
       DNSResourceRecord rr;
       cout<<"Processing '"<<di.zone<<"'"<<endl;
       // create zone
-      if (!tgt->createDomain(di.zone)) throw PDNSException("Failed to create zone");
+      if (!tgt->createDomain(di.zone, di.kind, di.masters, di.account)) throw PDNSException("Failed to create zone");
       if (!tgt->getDomainInfo(di.zone, di_new)) throw PDNSException("Failed to create zone");
-      tgt->setKind(di_new.zone, di.kind);
-      tgt->setAccount(di_new.zone,di.account);
-      string masters="";
-      bool first = true;
-      for(const auto& master: di.masters) {
-        if (!first)
-          masters += ", ";
-        first = false;
-        masters += master.toStringWithPortExcept(53);
-      }
-      tgt->setMaster(di_new.zone, masters);
       // move records
       if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
       nr=0;
index 231ad9afb0bc44a64b930ab78b3d78068e619f1f..d6073b865b182ad7ad18042fb8fb197a72c49993 100644 (file)
@@ -8,6 +8,8 @@
 #include <boost/format.hpp>
 #include <p11-kit/p11-kit.h>
 
+#include <mutex>
+
 #include "pdns/dnssecinfra.hh"
 #include "pdns/logger.hh"
 #include "pdns/pdnsexception.hh"
@@ -15,6 +17,7 @@
 #include "pdns/lock.hh"
 
 #ifdef HAVE_LIBCRYPTO_ECDSA
+#include <openssl/bn.h>
 #include <openssl/ec.h>
 #endif
 
@@ -42,6 +45,9 @@ in it. you need to use softhsm tools to manage this all.
 static CK_FUNCTION_LIST** p11_modules;
 #endif
 
+#define ECDSA256_PARAMS "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07"
+#define ECDSA384_PARAMS "\x06\x05\x2b\x81\x04\x00\x22"
+
 // map for signing algorithms
 static std::map<unsigned int,CK_MECHANISM_TYPE> dnssec2smech = boost::assign::map_list_of
 (5, CKM_SHA1_RSA_PKCS)
@@ -60,6 +66,14 @@ static std::map<unsigned int,CK_MECHANISM_TYPE> dnssec2hmech = boost::assign::ma
 (13, CKM_SHA256)
 (14, CKM_SHA384);
 
+static std::map<unsigned int,CK_MECHANISM_TYPE> dnssec2cmech = boost::assign::map_list_of
+(5, CKM_RSA_PKCS_KEY_PAIR_GEN)
+(7, CKM_RSA_PKCS_KEY_PAIR_GEN)
+(8, CKM_RSA_PKCS_KEY_PAIR_GEN)
+(10, CKM_RSA_PKCS_KEY_PAIR_GEN)
+(13, CKM_ECDSA_KEY_PAIR_GEN)
+(14, CKM_ECDSA_KEY_PAIR_GEN);
+
 typedef enum { Attribute_Byte, Attribute_Long, Attribute_String } CkaValueType;
 
 // Attribute handling
@@ -205,11 +219,11 @@ class Pkcs11Slot {
     CK_SESSION_HANDLE d_session;
     CK_SLOT_ID d_slot;
     CK_RV d_err;
-    pthread_mutex_t d_m;
+    std::mutex d_m;
 
     void logError(const std::string& operation) const {
       if (d_err) {
-        std::string msg = boost::str( boost::format("PKCS#11 operation %s failed: %s (0x%X)") % operation % p11_kit_strerror(d_err) % d_err );
+        std::string msg = boost::str( boost::format("PKCS#11 operation %s failed: %s (0x%X) (%s)") % operation % p11_kit_strerror(d_err) % d_err % p11_kit_message() );
         g_log<<Logger::Error<< msg << endl;
       }
     }
@@ -222,8 +236,7 @@ class Pkcs11Slot {
     d_err(0)
   {
       CK_TOKEN_INFO tokenInfo;
-      pthread_mutex_init(&(this->d_m), NULL);
-      Lock l(&d_m);
+      std::lock_guard<std::mutex> l(d_m);
 
       if ((d_err = d_functions->C_OpenSession(this->d_slot, CKF_SERIAL_SESSION|CKF_RW_SESSION, 0, 0, &(this->d_session)))) {
         logError("C_OpenSession");
@@ -260,7 +273,7 @@ class Pkcs11Slot {
 
     CK_FUNCTION_LIST* f() { return d_functions; }
 
-    pthread_mutex_t *m() { return &d_m; }
+    std::mutex& m() { return d_m; }
 
     static std::shared_ptr<Pkcs11Slot> GetSlot(const std::string& module, const string& tokenId);
     static CK_RV HuntSlot(const string& tokenId, CK_SLOT_ID &slotId, _CK_SLOT_INFO* info, CK_FUNCTION_LIST* functions);
@@ -288,7 +301,7 @@ class Pkcs11Token {
 
     void logError(const std::string& operation) const {
       if (d_err) {
-        std::string msg = boost::str( boost::format("PKCS#11 operation %s failed: %s (0x%X)") % operation % p11_kit_strerror(d_err) % d_err );
+        std::string msg = boost::str( boost::format("PKCS#11 operation %s failed: %s (0x%X) (%s)") % operation % p11_kit_strerror(d_err) % d_err % p11_kit_message());
         g_log<<Logger::Error<< msg << endl;
       }
     }
@@ -314,8 +327,8 @@ class Pkcs11Token {
 
       return bits;
 #else
-      if (d_ecdsa_params == "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07") return 256;
-      else if (d_ecdsa_params == "\x06\x05\x2b\x81\x04\x00\x22") return 384;
+      if (d_ecdsa_params == ECDSA256_PARAMS) return 256;
+      else if (d_ecdsa_params == ECDSA384_PARAMS) return 384;
       else throw PDNSException("Unsupported EC key");
 #endif
     }
@@ -341,7 +354,7 @@ class Pkcs11Token {
     }
 
     void LoadAttributes() {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
       std::vector<P11KitAttribute> attr;
       std::vector<CK_OBJECT_HANDLE> key;
       attr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY));
@@ -406,7 +419,7 @@ class Pkcs11Token {
 
     int GenerateKeyPair(CK_MECHANISM_PTR mechanism, std::vector<P11KitAttribute>& pubAttributes, std::vector<P11KitAttribute>& privAttributes, CK_OBJECT_HANDLE_PTR pubKey, CK_OBJECT_HANDLE_PTR privKey) {
       {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
 
       size_t k;
       std::unique_ptr<CK_ATTRIBUTE[]> pubAttr(new CK_ATTRIBUTE[pubAttributes.size()]);
@@ -434,7 +447,7 @@ class Pkcs11Token {
     }
 
     int Sign(const std::string& data, std::string& result, CK_MECHANISM_PTR mechanism) {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
 
       CK_BYTE buffer[1024];
       CK_ULONG buflen = sizeof buffer; // should be enough for most signatures.
@@ -453,7 +466,7 @@ class Pkcs11Token {
     }
 
     int Verify(const std::string& data, const std::string& signature, CK_MECHANISM_PTR mechanism) {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
 
       if ((d_err = this->d_slot->f()->C_VerifyInit(d_slot->Session(), mechanism, d_public_key))) { logError("C_VerifyInit"); return d_err; }
       d_err = this->d_slot->f()->C_Verify(d_slot->Session(), (unsigned char*)data.c_str(), data.size(), (unsigned char*)signature.c_str(), signature.size());
@@ -462,7 +475,7 @@ class Pkcs11Token {
     }
 
     int Digest(const std::string& data, std::string& result, CK_MECHANISM_PTR mechanism) {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
 
       CK_BYTE buffer[1024];
       CK_ULONG buflen = sizeof buffer; // should be enough for most digests
@@ -489,7 +502,7 @@ class Pkcs11Token {
     }
 
     int DigestKey(std::string& result) {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
       CK_MECHANISM mech;
       mech.mechanism = CKM_SHA_1;
 
@@ -520,7 +533,7 @@ class Pkcs11Token {
     }
 
     int FindObjects(const std::vector<P11KitAttribute>& attributes, std::vector<CK_OBJECT_HANDLE>& objects, int maxobjects) {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
       return FindObjects2(attributes, objects, maxobjects);
     }
 
@@ -566,7 +579,7 @@ class Pkcs11Token {
 
     int GetAttributeValue(const CK_OBJECT_HANDLE& object, std::vector<P11KitAttribute>& attributes) 
     {
-      Lock l(d_slot->m());
+      std::lock_guard<std::mutex> l(d_slot->m());
       return GetAttributeValue2(object, attributes);
     }
 
@@ -783,7 +796,6 @@ void PKCS11DNSCryptoKeyEngine::create(unsigned int bits) {
   std::vector<P11KitAttribute> privAttr;
   CK_MECHANISM mech;
   CK_OBJECT_HANDLE pubKey, privKey;
-  CK_RV rv;
   std::shared_ptr<Pkcs11Token> d_slot;
   d_slot = Pkcs11Token::GetToken(d_module, d_slot_id, d_label, d_pub_label);
   if (d_slot->LoggedIn() == false)
@@ -792,33 +804,66 @@ void PKCS11DNSCryptoKeyEngine::create(unsigned int bits) {
 
   std::string pubExp("\000\001\000\001", 4); // 65537
 
-  pubAttr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY));
-  pubAttr.push_back(P11KitAttribute(CKA_KEY_TYPE, (unsigned long)CKK_RSA));
-  pubAttr.push_back(P11KitAttribute(CKA_TOKEN, (char)CK_TRUE));
-  pubAttr.push_back(P11KitAttribute(CKA_ENCRYPT, (char)CK_TRUE));
-  pubAttr.push_back(P11KitAttribute(CKA_VERIFY, (char)CK_TRUE));
-  pubAttr.push_back(P11KitAttribute(CKA_WRAP, (char)CK_TRUE));
-  pubAttr.push_back(P11KitAttribute(CKA_MODULUS_BITS, (unsigned long)bits));
-  pubAttr.push_back(P11KitAttribute(CKA_PUBLIC_EXPONENT, pubExp));
-  pubAttr.push_back(P11KitAttribute(CKA_LABEL, d_pub_label));
-
-  privAttr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY));
-  privAttr.push_back(P11KitAttribute(CKA_KEY_TYPE, (unsigned long)CKK_RSA));
-  privAttr.push_back(P11KitAttribute(CKA_TOKEN, (char)CK_TRUE));
-  privAttr.push_back(P11KitAttribute(CKA_PRIVATE, (char)CK_TRUE));
-//  privAttr.push_back(P11KitAttribute(CKA_SUBJECT, "CN=keygen"));
-  privAttr.push_back(P11KitAttribute(CKA_ID, "\x01\x02\x03\x04")); // this is mandatory if you want to export anything
-  privAttr.push_back(P11KitAttribute(CKA_SENSITIVE, (char)CK_TRUE));
-  privAttr.push_back(P11KitAttribute(CKA_DECRYPT, (char)CK_TRUE));
-  privAttr.push_back(P11KitAttribute(CKA_SIGN, (char)CK_TRUE));
-  privAttr.push_back(P11KitAttribute(CKA_UNWRAP, (char)CK_TRUE));
-  privAttr.push_back(P11KitAttribute(CKA_LABEL, d_label));
-
-  mech.mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN;
+  try {
+    mech.mechanism = dnssec2cmech.at(d_algorithm);
+  } catch (std::out_of_range& e) {
+    throw PDNSException("pkcs11: unsupported algorithm "+std::to_string(d_algorithm)+ " for key pair generation");
+  }
+
   mech.pParameter = NULL;
   mech.ulParameterLen = 0;
 
-  if ((rv = d_slot->GenerateKeyPair(&mech, pubAttr, privAttr, &pubKey, &privKey))) {
+  if (mech.mechanism == CKM_RSA_PKCS_KEY_PAIR_GEN) {
+    pubAttr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY));
+    pubAttr.push_back(P11KitAttribute(CKA_KEY_TYPE, (unsigned long)CKK_RSA));
+    pubAttr.push_back(P11KitAttribute(CKA_TOKEN, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_ENCRYPT, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_VERIFY, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_WRAP, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_MODULUS_BITS, (unsigned long)bits));
+    pubAttr.push_back(P11KitAttribute(CKA_PUBLIC_EXPONENT, pubExp));
+    pubAttr.push_back(P11KitAttribute(CKA_LABEL, d_pub_label));
+
+    privAttr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY));
+    privAttr.push_back(P11KitAttribute(CKA_KEY_TYPE, (unsigned long)CKK_RSA));
+    privAttr.push_back(P11KitAttribute(CKA_TOKEN, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_PRIVATE, (char)CK_TRUE));
+  //  privAttr.push_back(P11KitAttribute(CKA_SUBJECT, "CN=keygen"));
+    privAttr.push_back(P11KitAttribute(CKA_ID, "\x01\x02\x03\x04")); // this is mandatory if you want to export anything
+    privAttr.push_back(P11KitAttribute(CKA_SENSITIVE, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_DECRYPT, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_SIGN, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_UNWRAP, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_LABEL, d_label));
+  } else if (mech.mechanism == CKM_ECDSA_KEY_PAIR_GEN) {
+    pubAttr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY));
+    pubAttr.push_back(P11KitAttribute(CKA_KEY_TYPE, (unsigned long)CKK_ECDSA));
+    pubAttr.push_back(P11KitAttribute(CKA_TOKEN, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_ENCRYPT, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_VERIFY, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_WRAP, (char)CK_TRUE));
+    pubAttr.push_back(P11KitAttribute(CKA_LABEL, d_pub_label));
+    if (d_algorithm == 13) pubAttr.push_back(P11KitAttribute(CKA_ECDSA_PARAMS, ECDSA256_PARAMS));
+    else if (d_algorithm == 14) pubAttr.push_back(P11KitAttribute(CKA_ECDSA_PARAMS, ECDSA384_PARAMS));
+    else throw PDNSException("pkcs11: unknown algorithm "+std::to_string(d_algorithm)+" for ECDSA key pair generation");
+
+    privAttr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY));
+    privAttr.push_back(P11KitAttribute(CKA_KEY_TYPE, (unsigned long)CKK_ECDSA));
+    privAttr.push_back(P11KitAttribute(CKA_TOKEN, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_PRIVATE, (char)CK_TRUE));
+  //  privAttr.push_back(P11KitAttribute(CKA_SUBJECT, "CN=keygen"));
+    privAttr.push_back(P11KitAttribute(CKA_ID, "\x01\x02\x03\x04")); // this is mandatory if you want to export anything
+    privAttr.push_back(P11KitAttribute(CKA_SENSITIVE, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_DECRYPT, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_SIGN, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_UNWRAP, (char)CK_TRUE));
+    privAttr.push_back(P11KitAttribute(CKA_LABEL, d_label));
+  } else {
+    throw PDNSException("pkcs11: don't know how make key for algorithm "+std::to_string(d_algorithm));
+  }
+
+
+  if (d_slot->GenerateKeyPair(&mech, pubAttr, privAttr, &pubKey, &privKey)) {
     throw PDNSException("Keypair generation failed");
   }
 };
index 39939b2f4fbc437f491ed0a7561613a18f00e657..39a0aa07ec931992f912f39979327df2e7366619 100644 (file)
@@ -23,11 +23,12 @@ public:
     close(d_portfd);
   }
 
-  virtual int run(struct timeval* tv, int timeout=500);
+  virtual int run(struct timeval* tv, int timeout=500) override;
+  virtual void getAvailableFDs(std::vector<int>& fds, int timeout) override;
 
-  virtual void addFD(callbackmap_t& cbmap, int fd, callbackfunc_t toDo, const boost::any& parameter, const struct timeval* ttd=nullptr);
-  virtual void removeFD(callbackmap_t& cbmap, int fd);
-  string getName()
+  virtual void addFD(callbackmap_t& cbmap, int fd, callbackfunc_t toDo, const boost::any& parameter, const struct timeval* ttd=nullptr) override;
+  virtual void removeFD(callbackmap_t& cbmap, int fd) override;
+  string getName() const override
   {
     return "solaris completion ports";
   }
@@ -78,6 +79,58 @@ void PortsFDMultiplexer::removeFD(callbackmap_t& cbmap, int fd)
     throw FDMultiplexerException("Removing fd from port set: "+stringerror());
 }
 
+void PortsFDMultiplexer::getAvailableFDs(std::vector<int>& fds, int timeout)
+{
+  struct timespec timeoutspec;
+  timeoutspec.tv_sec = timeout / 1000;
+  timeoutspec.tv_nsec = (timeout % 1000) * 1000000;
+  unsigned int numevents = 1;
+  int ret = port_getn(d_portfd, d_pevents.get(), min(PORT_MAX_LIST, s_maxevents), &numevents, &timeoutspec);
+
+  /* port_getn has an unusual API - (ret == -1, errno == ETIME) can
+     mean partial success; you must check (*numevents) in this case
+     and process anything in there, otherwise you'll never see any
+     events from that object again. We don't care about pure timeouts
+     (ret == -1, errno == ETIME, *numevents == 0) so we don't bother
+     with that case. */
+  if (ret == -1 && errno != ETIME) {
+    if (errno != EINTR) {
+      throw FDMultiplexerException("completion port_getn returned error: " + stringerror());
+    }
+
+    // EINTR is not really an error
+    return;
+  }
+
+  if (numevents == 0) {
+    // nothing
+    return;
+  }
+
+  fds.reserve(numevents);
+
+  for (unsigned int n = 0; n < numevents; ++n) {
+    const auto fd = d_pevents[n].portev_object;
+
+    /* we need to re-associate the FD */
+    if (d_readCallbacks.count(fd)) {
+      if (port_associate(d_portfd, PORT_SOURCE_FD, fd, POLLIN, 0) < 0) {
+        throw FDMultiplexerException("Unable to add fd back to ports (read): " + stringerror());
+      }
+    }
+    else if (d_writeCallbacks.count(fd)) {
+      if (port_associate(d_portfd, PORT_SOURCE_FD, fd, POLLOUT, 0) < 0) {
+        throw FDMultiplexerException("Unable to add fd back to ports (write): " + stringerror());
+      }
+    } else {
+      /* not registered, this is unexpected */
+      continue;
+    }
+
+    fds.push_back(fd);
+  }
+}
+
 int PortsFDMultiplexer::run(struct timeval* now, int timeout)
 {
   if(d_inrun) {
@@ -85,8 +138,8 @@ int PortsFDMultiplexer::run(struct timeval* now, int timeout)
   }
   
   struct timespec timeoutspec;
-  timeoutspec.tv_sec = time / 1000;
-  timeoutspec.tv_nsec = (time % 1000) * 1000000;
+  timeoutspec.tv_sec = timeout / 1000;
+  timeoutspec.tv_nsec = (timeout % 1000) * 1000000;
   unsigned int numevents=1;
   int ret= port_getn(d_portfd, d_pevents.get(), min(PORT_MAX_LIST, s_maxevents), &numevents, &timeoutspec);
   
diff --git a/pdns/proxy-protocol.cc b/pdns/proxy-protocol.cc
new file mode 100644 (file)
index 0000000..5e62e9e
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * 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 "proxy-protocol.hh"
+
+// TODO: maybe use structs instead of explicitly working byte by byte, like https://github.com/dovecot/core/blob/master/src/lib-master/master-service-haproxy.c
+
+#define PROXYMAGIC "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
+#define PROXYMAGICLEN sizeof(PROXYMAGIC)-1
+
+static string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
+
+static std::string makeSimpleHeader(uint8_t command, uint8_t protocol, uint16_t contentLen)
+{
+  std::string ret;
+  const uint8_t versioncommand = (0x20 | command);
+
+  ret.reserve(proxymagic.size() + sizeof(versioncommand) + sizeof(protocol) + sizeof(contentLen) + contentLen);
+
+  ret.append(proxymagic);
+
+  ret.append(reinterpret_cast<const char*>(&versioncommand), sizeof(versioncommand));
+  ret.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol));
+
+  ret.append(reinterpret_cast<const char*>(&contentLen), sizeof(contentLen));
+
+  return ret;
+}
+
+std::string makeLocalProxyHeader()
+{
+  return makeSimpleHeader(0x00, 0, 0);
+}
+
+std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values)
+{
+  if (source.sin4.sin_family != destination.sin4.sin_family) {
+    throw std::runtime_error("The PROXY destination and source addresses must be of the same family");
+  }
+
+  const uint8_t command = 0x01;
+  const uint8_t protocol = (source.isIPv4() ? 0x10 : 0x20) | (tcp ? 0x01 : 0x02);
+  const size_t addrSize = source.isIPv4() ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr);
+  const uint16_t sourcePort = source.sin4.sin_port;
+  const uint16_t destinationPort = destination.sin4.sin_port;
+
+  size_t valuesSize = 0;
+  for (const auto& value : values) {
+    if (value.content.size() > std::numeric_limits<uint16_t>::max()) {
+      throw std::runtime_error("The size of proxy protocol values is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to add a value of size " + std::to_string(value.content.size()));
+    }
+    valuesSize += sizeof(uint8_t) + sizeof(uint8_t) * 2 + value.content.size();
+    if (valuesSize > std::numeric_limits<uint16_t>::max()) {
+      throw std::runtime_error("The total size of proxy protocol values is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()));
+    }
+  }
+
+  size_t total = (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort) + valuesSize;
+  if (total > std::numeric_limits<uint16_t>::max()) {
+    throw std::runtime_error("The size of a proxy protocol header is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to send one of size " + std::to_string(total));
+  }
+
+  const uint16_t contentlen = htons(static_cast<uint16_t>(total));
+  std::string ret = makeSimpleHeader(command, protocol, contentlen);
+
+  // We already established source and destination sin_family equivalence
+  if (source.isIPv4()) {
+    assert(addrSize == sizeof(source.sin4.sin_addr.s_addr));
+    ret.append(reinterpret_cast<const char*>(&source.sin4.sin_addr.s_addr), addrSize);
+    assert(addrSize == sizeof(destination.sin4.sin_addr.s_addr));
+    ret.append(reinterpret_cast<const char*>(&destination.sin4.sin_addr.s_addr), addrSize);
+  }
+  else {
+    assert(addrSize == sizeof(source.sin6.sin6_addr.s6_addr));
+    ret.append(reinterpret_cast<const char*>(&source.sin6.sin6_addr.s6_addr), addrSize);
+    assert(addrSize == sizeof(destination.sin6.sin6_addr.s6_addr));
+    ret.append(reinterpret_cast<const char*>(&destination.sin6.sin6_addr.s6_addr), addrSize);
+  }
+
+  ret.append(reinterpret_cast<const char*>(&sourcePort), sizeof(sourcePort));
+  ret.append(reinterpret_cast<const char*>(&destinationPort), sizeof(destinationPort));
+
+  for (const auto& value : values) {
+    uint16_t contentSize = htons(static_cast<uint16_t>(value.content.size()));
+    ret.append(reinterpret_cast<const char*>(&value.type), sizeof(value.type));
+    ret.append(reinterpret_cast<const char*>(&contentSize), sizeof(contentSize));
+    ret.append(reinterpret_cast<const char*>(value.content.data()), value.content.size());
+  }
+
+  return ret;
+}
+
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t isProxyHeaderComplete(const std::string& header, bool* proxy, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut)
+{
+  static const size_t addr4Size = sizeof(ComboAddress::sin4.sin_addr.s_addr);
+  static const size_t addr6Size = sizeof(ComboAddress::sin6.sin6_addr.s6_addr);
+  size_t addrSize = 0;
+  uint8_t versioncommand;
+  uint8_t protocol;
+
+  if (header.size() < s_proxyProtocolMinimumHeaderSize) {
+    // this is too short to be a complete proxy header
+    return -(s_proxyProtocolMinimumHeaderSize - header.size());
+  }
+
+  if (header.compare(0, proxymagic.size(), proxymagic) != 0) {
+    // wrong magic, can not be a proxy header
+    return 0;
+  }
+
+  versioncommand = header.at(12);
+  /* check version */
+  if (!(versioncommand & 0x20)) {
+    return 0;
+  }
+
+  /* remove the version to get the command */
+  uint8_t command = versioncommand & ~0x20;
+
+  if (command == 0x01) {
+    protocol = header.at(13);
+    if ((protocol & 0xf) == 1) {
+      if (tcp) {
+        *tcp = true;
+      }
+    } else if ((protocol & 0xf) == 2) {
+      if (tcp) {
+        *tcp = false;
+      }
+    } else {
+      return 0;
+    }
+
+    protocol = protocol >> 4;
+
+    if (protocol == 1) {
+      if (protocolOut) {
+        *protocolOut = 4;
+      }
+      addrSize = addr4Size; // IPv4
+    } else if (protocol == 2) {
+      if (protocolOut) {
+        *protocolOut = 6;
+      }
+      addrSize = addr6Size; // IPv6
+    } else {
+      // invalid protocol
+      return 0;
+    }
+
+    if (addrSizeOut) {
+      *addrSizeOut = addrSize;
+    }
+
+    if (proxy) {
+      *proxy = true;
+    }
+  }
+  else if (command == 0x00) {
+    if (proxy) {
+      *proxy = false;
+    }
+  }
+  else {
+    /* unsupported command */
+    return 0;
+  }
+
+  uint16_t contentlen = (static_cast<uint8_t>(header.at(14)) << 8) + static_cast<uint8_t>(header.at(15));
+  uint16_t expectedlen = 0;
+  if (command != 0x00) {
+    expectedlen = (addrSize * 2) + sizeof(ComboAddress::sin4.sin_port) + sizeof(ComboAddress::sin4.sin_port);
+  }
+
+  if (contentlen < expectedlen) {
+    return 0;
+  }
+
+  if (header.size() < s_proxyProtocolMinimumHeaderSize + contentlen) {
+    return -((s_proxyProtocolMinimumHeaderSize + contentlen) - header.size());
+  }
+
+  return s_proxyProtocolMinimumHeaderSize + contentlen;
+}
+
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t parseProxyHeader(const std::string& header, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values)
+{
+  size_t addrSize = 0;
+  uint8_t protocol = 0;
+  ssize_t got = isProxyHeaderComplete(header, &proxy, &tcp, &addrSize, &protocol);
+  if (got <= 0) {
+    return got;
+  }
+
+  size_t pos = s_proxyProtocolMinimumHeaderSize;
+
+  if (proxy) {
+    source = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize);
+    pos = pos + addrSize;
+    destination = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize);
+    pos = pos + addrSize;
+    source.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1)));
+    pos = pos + sizeof(uint16_t);
+    destination.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1)));
+    pos = pos + sizeof(uint16_t);
+  }
+
+  size_t remaining = got - pos;
+  while (remaining >= (sizeof(uint8_t) + sizeof(uint16_t))) {
+    /* we still have TLV values to parse */
+    uint8_t type = static_cast<uint8_t>(header.at(pos));
+    pos += sizeof(uint8_t);
+    uint16_t len = (static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos + 1));
+    pos += sizeof(uint16_t);
+
+    if (len > 0) {
+      if (len > (got - pos)) {
+        return 0;
+      }
+
+      values.push_back({ std::string(&header.at(pos), len), type });
+      pos += len;
+    }
+    else {
+      values.push_back({ std::string(), type });
+    }
+
+    remaining = got - pos;
+  }
+
+  return pos;
+}
diff --git a/pdns/proxy-protocol.hh b/pdns/proxy-protocol.hh
new file mode 100644 (file)
index 0000000..66b8f0a
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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 <iputils.hh>
+
+struct ProxyProtocolValue
+{
+  std::string content;
+  uint8_t type;
+};
+
+static const size_t s_proxyProtocolMinimumHeaderSize = 16;
+
+std::string makeLocalProxyHeader();
+std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values);
+
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t isProxyHeaderComplete(const std::string& header, bool* proxy=nullptr, bool* tcp=nullptr, size_t* addrSizeOut=nullptr, uint8_t* protocolOut=nullptr);
+
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t parseProxyHeader(const std::string& payload, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values);
index 03b6c8a5c78d89dfb652f7ad39e0147f98e6d0ba..5c9b80cf46a98915a016b46bfda3eea365b0f1d8 100644 (file)
@@ -95,6 +95,7 @@ public:
     A6=38,
     DNAME=39,
     OPT=41,
+    APL=42,
     DS=43,
     SSHFP=44,
     IPSECKEY=45,
@@ -194,6 +195,7 @@ private:
       qtype_insert("A6", 38);
       qtype_insert("DNAME", 39);
       qtype_insert("OPT", 41);
+      qtype_insert("APL", 42);
       qtype_insert("DS", 43);
       qtype_insert("SSHFP", 44);
       qtype_insert("IPSECKEY", 45);
diff --git a/pdns/query-local-address.cc b/pdns/query-local-address.cc
new file mode 100644 (file)
index 0000000..2acccab
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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 "query-local-address.hh"
+#include "iputils.hh"
+#include "dns_random.hh"
+
+namespace pdns {
+  static const ComboAddress local4("0.0.0.0");
+  static const ComboAddress local6("::");
+
+  static vector<ComboAddress> g_localQueryAddresses4;
+  static vector<ComboAddress> g_localQueryAddresses6;
+
+  ComboAddress getQueryLocalAddress(const sa_family_t family, const in_port_t port) {
+    ComboAddress ret;
+    if (family==AF_INET) {
+      if (g_localQueryAddresses4.empty()) {
+        ret = local4;
+      } else if (g_localQueryAddresses4.size() == 1) {
+        ret = g_localQueryAddresses4.at(0);
+      } else {
+        ret = g_localQueryAddresses4[dns_random(g_localQueryAddresses4.size())];
+      }
+      ret.sin4.sin_port = htons(port);
+    }
+    else {
+      if (g_localQueryAddresses6.empty()) {
+        ret = local6;
+      } else if (g_localQueryAddresses6.size() == 1) {
+        ret = g_localQueryAddresses6.at(0);
+      } else {
+        ret = g_localQueryAddresses6[dns_random(g_localQueryAddresses6.size())];
+      }
+      ret.sin6.sin6_port = htons(port);
+    }
+    return ret;
+  }
+
+  ComboAddress getNonAnyQueryLocalAddress(const sa_family_t family) {
+    if (family == AF_INET) {
+      for (const auto& addr : pdns::g_localQueryAddresses4) {
+        if (!IsAnyAddress(addr)) {
+          return addr;
+        }
+      }
+    }
+    if (family == AF_INET6) {
+      for (const auto& addr : pdns::g_localQueryAddresses6) {
+        if (!IsAnyAddress(addr)) {
+          return addr;
+        }
+      }
+    }
+    ComboAddress ret("0.0.0.0");
+    ret.reset(); // Ensure all is zero, even the addr family
+    return ret;
+  }
+
+  void parseQueryLocalAddress(const std::string &qla) {
+    vector<string> addrs;
+    stringtok(addrs, qla, ", ;");
+    for(const string& addr : addrs) {
+      ComboAddress tmp(addr);
+      if (tmp.isIPv4()) {
+        g_localQueryAddresses4.push_back(tmp);
+        continue;
+      }
+      g_localQueryAddresses6.push_back(tmp);
+    }
+  }
+
+  bool isQueryLocalAddressFamilyEnabled(const sa_family_t family) {
+    if (family == AF_INET) {
+      return !g_localQueryAddresses4.empty();
+    }
+    if (family == AF_INET6) {
+      return !g_localQueryAddresses6.empty();
+    }
+    return false;
+  }
+} // namespace pdns
diff --git a/pdns/query-local-address.hh b/pdns/query-local-address.hh
new file mode 100644 (file)
index 0000000..9515f4a
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 "iputils.hh"
+
+namespace pdns {
+  /*! pick a random query local address for family
+   *
+   * Will always return a ComboAddress.
+   *
+   * @param family Address Family, only AF_INET and AF_INET6 are supported
+   * @param port   Port to set in the returned ComboAddress
+   */
+  ComboAddress getQueryLocalAddress(const sa_family_t family, const in_port_t port);
+
+  /*! Returns a non-Any address QLA, or an empty QLA when the QLA is any
+   *
+   * @param family  Address Family
+   */
+  ComboAddress getNonAnyQueryLocalAddress(const sa_family_t family);
+
+  /*! Populate the query local address vectors
+   *
+   * Will throw when an address can't be parsed
+   *
+   * @param qla  A string of one or more ip addresses, separated by
+   *             spaces, semi-colons or commas
+   */
+  void parseQueryLocalAddress(const std::string &qla);
+
+  /*! Is the address family explicitly enabled
+   *
+   * i.e. was there an address parsed by parseQueryLocalAddress belonging
+   * to this family
+   *
+   * @param family  Address Family, only AF_INET and AF_INET6 are supported
+   */
+  bool isQueryLocalAddressFamilyEnabled(const sa_family_t family);
+} // namespace pdns
index bebe2d3ecc2e416d8a0cf2e51d089bdf42f5b0f3..72e5c08060c1f0cab75325fd9c942e90905411f5 100644 (file)
@@ -310,7 +310,7 @@ static inline uint8_t hextodec(uint8_t val)
 }
 
 
-void HEXDecode(const char* begin, const char* end, string& out)
+static void HEXDecode(const char* begin, const char* end, string& out)
 {
   if(end - begin == 1 && *begin=='-') {
     out.clear();
index 218ee83716972150ee34aaac46406c1dcf0fcdfd..c03bae308214a1f64a6734782ccc7c2d1a6eaa6e 100644 (file)
@@ -19,7 +19,7 @@ try
   vector<string> carbonServers;
 
   {
-    Lock l(&g_carbon_config_lock);
+    std::lock_guard<std::mutex> l(g_carbon_config_lock);
     stringtok(carbonServers, arg()["carbon-server"], ", ");
     namespace_name=arg()["carbon-namespace"];
     hostname=arg()["carbon-ourname"];
@@ -32,15 +32,13 @@ try
   if(namespace_name.empty()) {
     namespace_name="pdns";
   }
-  if(hostname.empty()) {
-    char tmp[80];
-    memset(tmp, 0, sizeof(tmp));
-    gethostname(tmp, sizeof(tmp));
-    char *p = strchr(tmp, '.');
-    if(p) *p=0;
-
-    hostname=tmp;
-    boost::replace_all(hostname, ".", "_");    
+  if (hostname.empty()) {
+    try {
+      hostname = getCarbonHostName();
+    }
+    catch(const std::exception& e) {
+      throw std::runtime_error(std::string("The 'carbon-ourname' setting has not been set and we are unable to determine the system's hostname: ") + e.what());
+    }
   }
   if(instance_name.empty()) {
     instance_name="recursor";
index 046f1d485ba1700cdd03372cb1f45cdbb8ad50ac..0481c8a0cc06b11d8ef02b8af125a0c4c852660c 100644 (file)
@@ -51,9 +51,9 @@ typename C::value_type::second_type constGet(const C& c, const std::string& name
   return iter->second;
 }
 
-typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string > > rpzOptions_t;
+typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string, std::vector<std::pair<int, std::string>> > > rpzOptions_t;
 
-static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL, size_t& zoneSizeHint)
+static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL, size_t& zoneSizeHint, std::unordered_set<std::string>& tags, bool& overridesGettag)
 {
   if(have.count("policyName")) {
     polName = boost::get<std::string>(have["policyName"]);
@@ -61,7 +61,7 @@ static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::
   if(have.count("defpol")) {
     defpol=DNSFilterEngine::Policy();
     defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
-    defpol->d_name = std::make_shared<std::string>(polName);
+    defpol->setName(polName);
     if(defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
       defpol->d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN,
                                                               boost::get<string>(have["defcontent"])));
@@ -82,6 +82,15 @@ static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::
   if(have.count("zoneSizeHint")) {
     zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
   }
+  if (have.count("tags")) {
+    const auto tagsTable = boost::get<std::vector<std::pair<int, std::string>>>(have["tags"]);
+    for (const auto& tag : tagsTable) {
+      tags.insert(tag.second);
+    }
+  }
+  if (have.count("overridesGettag")) {
+    overridesGettag = boost::get<bool>(have["overridesGettag"]);
+  }
 }
 
 #if HAVE_PROTOBUF
@@ -235,16 +244,20 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         std::string polName("rpzFile");
         std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
         uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
+        bool overridesGettag = true;
         if(options) {
           auto& have = *options;
           size_t zoneSizeHint = 0;
-          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint);
+          std::unordered_set<std::string> tags;
+          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint, tags, overridesGettag);
           if (zoneSizeHint > 0) {
             zone->reserve(zoneSizeHint);
           }
+          zone->setTags(std::move(tags));
         }
         g_log<<Logger::Warning<<"Loading RPZ from file '"<<filename<<"'"<<endl;
         zone->setName(polName);
+        zone->setPolicyOverridesGettag(overridesGettag);
         loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
         lci.dfe.addZone(zone);
         g_log<<Logger::Warning<<"Done loading RPZ from file '"<<filename<<"'"<<endl;
@@ -286,10 +299,14 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         if (options) {
           auto& have = *options;
           size_t zoneSizeHint = 0;
-          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint);
+          std::unordered_set<std::string> tags;
+          bool overridesGettag = true;
+          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint, tags, overridesGettag);
           if (zoneSizeHint > 0) {
             zone->reserve(zoneSizeHint);
           }
+          zone->setTags(std::move(tags));
+          zone->setPolicyOverridesGettag(overridesGettag);
 
           if(have.count("tsigname")) {
             tt.name=DNSName(toLower(boost::get<string>(have["tsigname"])));
index c7eec6943dc2138328c1090e4a6e0ce5136ecb3d..af22526170864152a34fa29839c983805af3e0f8 100644 (file)
@@ -167,7 +167,27 @@ void RecProtoBufMessage::setAppliedPolicyType(const DNSFilterEngine::PolicyType&
 #endif /* HAVE_PROTOBUF */
 }
 
-void RecProtoBufMessage::setPolicyTags(const std::vector<std::string>& policyTags)
+void RecProtoBufMessage::setAppliedPolicyTrigger(const DNSName& trigger)
+{
+#ifdef HAVE_PROTOBUF
+  PBDNSMessage_DNSResponse* response = d_message.mutable_response();
+  if (response && !trigger.empty()) {
+    response->set_appliedpolicytrigger(trigger.toString());
+  }
+#endif /* HAVE_PROTOBUF */
+}
+
+void RecProtoBufMessage::setAppliedPolicyHit(const string& hit)
+{
+#ifdef HAVE_PROTOBUF
+  PBDNSMessage_DNSResponse* response = d_message.mutable_response();
+  if (response && !hit.empty()) {
+    response->set_appliedpolicyhit(hit);
+  }
+#endif /* HAVE_PROTOBUF */
+}
+
+void RecProtoBufMessage::setPolicyTags(const std::unordered_set<std::string>& policyTags)
 {
 #ifdef HAVE_PROTOBUF
   PBDNSMessage_DNSResponse* response = d_message.mutable_response();
index e4f76b843cb7d3e0750fe44fcceb347341557c60..c5ec2f06bdde769c515b2f4eaf77f90e33f34b11 100644 (file)
@@ -52,7 +52,9 @@ public:
 #endif /* NOD_ENABLED */
   void setAppliedPolicy(const std::string& policy);
   void setAppliedPolicyType(const DNSFilterEngine::PolicyType& policyType);
-  void setPolicyTags(const std::vector<std::string>& policyTags);
+  void setAppliedPolicyTrigger(const DNSName& trigger);
+  void setAppliedPolicyHit(const string& hit);
+  void setPolicyTags(const std::unordered_set<std::string>& policyTags);
   void addPolicyTag(const std::string& policyTag);
   void removePolicyTag(const std::string& policyTag);
   std::string getAppliedPolicy() const;
index d48c5708872b61f87f662f7ce30d5a2d731f8761..f7a4138a23ebd518041c3c5610ffa11367947094 100644 (file)
@@ -81,10 +81,12 @@ static const oid dontOutqueriesOID[] = { RECURSOR_STATS_OID, 62 };
 static const oid unreachablesOID[] = { RECURSOR_STATS_OID, 63 };
 static const oid chainResendsOID[] = { RECURSOR_STATS_OID, 64 };
 static const oid tcpClientsOID[] = { RECURSOR_STATS_OID, 65 };
+#ifdef __linux__
 static const oid udpRecvbufErrorsOID[] = { RECURSOR_STATS_OID, 66 };
 static const oid udpSndbufErrorsOID[] = { RECURSOR_STATS_OID, 67 };
 static const oid udpNoportErrorsOID[] = { RECURSOR_STATS_OID, 68 };
 static const oid udpinErrorsOID[] = { RECURSOR_STATS_OID, 69 };
+#endif /* __linux__ */
 static const oid ednsPingMatchesOID[] = { RECURSOR_STATS_OID, 70 };
 static const oid ednsPingMismatchesOID[] = { RECURSOR_STATS_OID, 71 };
 static const oid dnssecQueriesOID[] = { RECURSOR_STATS_OID, 72 };
@@ -116,6 +118,9 @@ static const oid variableResponsesOID[] = { RECURSOR_STATS_OID, 97 };
 static const oid specialMemoryUsageOID[] = { RECURSOR_STATS_OID, 98 };
 static const oid rebalancedQueriesOID[] = { RECURSOR_STATS_OID, 99 };
 static const oid qnameMinFallbackSuccessOID[] = { RECURSOR_STATS_OID, 100 };
+static const oid proxyProtocolInvalidOID[] = { RECURSOR_STATS_OID, 101 };
+static const oid recordCacheContendedOID[] = { RECURSOR_STATS_OID, 102 };
+static const oid recordCacheAcquiredOID[] = { RECURSOR_STATS_OID, 103 };
 
 static std::unordered_map<oid, std::string> s_statsMap;
 
@@ -325,5 +330,8 @@ RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string&
   registerCounter64Stat("policy-result-custom", policyResultCustomOID, OID_LENGTH(policyResultCustomOID));
   registerCounter64Stat("special-memory-usage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID));
   registerCounter64Stat("rebalanced-queries", rebalancedQueriesOID, OID_LENGTH(rebalancedQueriesOID));
+  registerCounter64Stat("proxy-protocol-invalid", proxyProtocolInvalidOID, OID_LENGTH(proxyProtocolInvalidOID));
+  registerCounter64Stat("record-cache-contended", recordCacheContendedOID, OID_LENGTH(recordCacheContendedOID));
+  registerCounter64Stat("record-cache-acquired", recordCacheAcquiredOID, OID_LENGTH(recordCacheAcquiredOID));
 #endif /* HAVE_NET_SNMP */
 }
index 732bd81eacd0ae1514971a710e00364cd6eb5516..09c8f1def596d48694a704401cc30b64a62a52f8 100644 (file)
@@ -73,7 +73,7 @@ public:
 enum class StatComponent { API, Carbon, RecControl, SNMP };
 
 std::map<std::string, std::string> getAllStatsMap(StatComponent component);
-extern pthread_mutex_t g_carbon_config_lock;
+extern std::mutex g_carbon_config_lock;
 std::vector<std::pair<DNSName, uint16_t> >* pleaseGetQueryRing();
 std::vector<std::pair<DNSName, uint16_t> >* pleaseGetServfailQueryRing();
 std::vector<std::pair<DNSName, uint16_t> >* pleaseGetBogusQueryRing();
index 8829be099d969dcf1c78c089492b193c2f0bab44..0e9fa6d2848296d25e51405e9ce1b833db900cd6 100644 (file)
 #include "secpoll-recursor.hh"
 #include "pubsuffix.hh"
 #include "namespaces.hh"
-pthread_mutex_t g_carbon_config_lock=PTHREAD_MUTEX_INITIALIZER;
+std::mutex g_carbon_config_lock;
 
 static map<string, const uint32_t*> d_get32bitpointers;
 static map<string, const std::atomic<uint64_t>*> d_getatomics;
 static map<string, function< uint64_t() > >  d_get64bitmembers;
-static pthread_mutex_t d_dynmetricslock = PTHREAD_MUTEX_INITIALIZER;
+static std::mutex d_dynmetricslock;
 static map<string, std::atomic<unsigned long>* > d_dynmetrics;
 
 static std::map<StatComponent, std::set<std::string>> s_blacklistedStats;
@@ -84,7 +84,7 @@ static void addGetStat(const string& name, function<uint64_t ()> f )
 
 std::atomic<unsigned long>* getDynMetric(const std::string& str)
 {
-  Lock l(&d_dynmetricslock);
+  std::lock_guard<std::mutex> l(d_dynmetricslock);
   auto f = d_dynmetrics.find(str);
   if(f != d_dynmetrics.end())
     return f->second;
@@ -105,7 +105,7 @@ static optional<uint64_t> get(const string& name)
   if(d_get64bitmembers.count(name))
     return d_get64bitmembers.find(name)->second();
 
-  Lock l(&d_dynmetricslock);
+  std::lock_guard<std::mutex> l(d_dynmetricslock);
   auto f =rplookup(d_dynmetrics, name);
   if(f)
     return (*f)->load();
@@ -141,7 +141,7 @@ map<string,string> getAllStatsMap(StatComponent component)
   }
 
   {
-    Lock l(&d_dynmetricslock);
+    std::lock_guard<std::mutex> l(d_dynmetricslock);
     for(const auto& a : d_dynmetrics) {
       if (blacklistMap.count(a.first) == 0) {
         ret.insert({a.first, std::to_string(*a.second)});
@@ -199,21 +199,23 @@ string static doGetParameter(T begin, T end)
 }
 
 
-static uint64_t dumpNegCache(NegCache& negcache, int fd)
+static uint64_t dumpNegCache(int fd)
 {
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
-  if(!fp) { // dup probably failed
+  int newfd = dup(fd);
+  if (newfd == -1) {
     return 0;
   }
-  uint64_t ret;
-  fprintf(fp.get(), "; negcache dump from thread follows\n;\n");
-  ret = negcache.dumpToFile(fp.get());
-  return ret;
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
+  if (!fp) {
+    return 0;
+  }
+  fprintf(fp.get(), "; negcache dump follows\n;\n");
+  return g_negCache->dumpToFile(fp.get());
 }
 
 static uint64_t* pleaseDump(int fd)
 {
-  return new uint64_t(dumpNegCache(SyncRes::t_sstorage.negcache, fd) + t_packetCache->doDump(fd));
+  return new uint64_t(t_packetCache->doDump(fd));
 }
 
 static uint64_t* pleaseDumpEDNSMap(int fd)
@@ -250,7 +252,7 @@ static string doDumpNSSpeeds(T begin, T end)
     return "Error opening dump file for writing: "+stringerror()+"\n";
   uint64_t total = 0;
   try {
-    total = broadcastAccFunction<uint64_t>(boost::bind(pleaseDumpNSSpeeds, fd));
+    total = broadcastAccFunction<uint64_t>([=]{ return pleaseDumpNSSpeeds(fd); });
   }
   catch(std::exception& e)
   {
@@ -281,7 +283,7 @@ static string doDumpCache(T begin, T end)
     return "Error opening dump file for writing: "+stringerror()+"\n";
   uint64_t total = 0;
   try {
-    total = s_RC->doDump(fd) + broadcastAccFunction<uint64_t>(boost::bind(pleaseDump, fd));
+    total = g_recCache->doDump(fd) + dumpNegCache(fd) + broadcastAccFunction<uint64_t>([=]{ return pleaseDump(fd); });
   }
   catch(...){}
   
@@ -303,7 +305,7 @@ static string doDumpEDNSStatus(T begin, T end)
     return "Error opening dump file for writing: "+stringerror()+"\n";
   uint64_t total = 0;
   try {
-    total = broadcastAccFunction<uint64_t>(boost::bind(pleaseDumpEDNSMap, fd));
+    total = broadcastAccFunction<uint64_t>([=]{ return pleaseDumpEDNSMap(fd); });
   }
   catch(...){}
 
@@ -364,7 +366,7 @@ static string doDumpThrottleMap(T begin, T end)
     return "Error opening dump file for writing: "+stringerror()+"\n";
   uint64_t total = 0;
   try {
-    total = broadcastAccFunction<uint64_t>(boost::bind(pleaseDumpThrottleMap, fd));
+    total = broadcastAccFunction<uint64_t>([=]{ return pleaseDumpThrottleMap(fd); });
   }
   catch(...){}
 
@@ -386,7 +388,7 @@ static string doDumpFailedServers(T begin, T end)
     return "Error opening dump file for writing: "+stringerror()+"\n";
   uint64_t total = 0;
   try {
-    total = broadcastAccFunction<uint64_t>(boost::bind(pleaseDumpFailedServers, fd));
+    total = broadcastAccFunction<uint64_t>([=]{ return pleaseDumpFailedServers(fd); });
   }
   catch(...){}
 
@@ -396,7 +398,7 @@ static string doDumpFailedServers(T begin, T end)
 
 uint64_t* pleaseWipeCache(const DNSName& canon, bool subtree, uint16_t qtype)
 {
-  return new uint64_t(s_RC->doWipeCache(canon, subtree));
+  return new uint64_t(g_recCache->doWipeCache(canon, subtree, qtype));
 }
 
 uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree, uint16_t qtype)
@@ -407,7 +409,7 @@ uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree, uint16_t qty
 
 uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree)
 {
-  uint64_t ret = SyncRes::wipeNegCache(canon, subtree);
+  uint64_t ret = g_negCache->wipe(canon, subtree);
   return new uint64_t(ret);
 }
 
@@ -435,9 +437,14 @@ static string doWipeCache(T begin, T end, uint16_t qtype)
 
   int count=0, pcount=0, countNeg=0;
   for (auto wipe : toWipe) {
-    count+= broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, wipe.first, wipe.second, qtype));
-    pcount+= broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, wipe.first, wipe.second, qtype));
-    countNeg+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, wipe.first, wipe.second));
+    try {
+      count+= broadcastAccFunction<uint64_t>([=]{ return pleaseWipeCache(wipe.first, wipe.second, qtype);});
+      pcount+= broadcastAccFunction<uint64_t>([=]{ return pleaseWipePacketCache(wipe.first, wipe.second, qtype);});
+      countNeg+=broadcastAccFunction<uint64_t>([=]{ return pleaseWipeAndCountNegCache(wipe.first, wipe.second);});
+    }
+    catch (const std::exception& e) {
+      g_log<<Logger::Warning<<", failed: "<<e.what()<<endl;
+    }
   }
 
   return "wiped "+std::to_string(count)+" records, "+std::to_string(countNeg)+" negative records, "+std::to_string(pcount)+" packets\n";
@@ -446,7 +453,7 @@ static string doWipeCache(T begin, T end, uint16_t qtype)
 template<typename T>
 static string doSetCarbonServer(T begin, T end)
 {
-  Lock l(&g_carbon_config_lock);
+  std::lock_guard<std::mutex> l(g_carbon_config_lock);
   if(begin==end) {
     ::arg().set("carbon-server").clear();
     return "cleared carbon-server setting\n";
@@ -538,9 +545,15 @@ static string doAddNTA(T begin, T end)
   g_luaconfs.modify([who, why](LuaConfigItems& lci) {
       lci.negAnchors[who] = why;
       });
-  broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, who, true, 0xffff));
-  broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, who, true, 0xffff));
-  broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, who, true));
+  try {
+    broadcastAccFunction<uint64_t>([=]{return pleaseWipeCache(who, true, 0xffff);});
+    broadcastAccFunction<uint64_t>([=]{return pleaseWipePacketCache(who, true, 0xffff);});
+    broadcastAccFunction<uint64_t>([=]{return pleaseWipeAndCountNegCache(who, true);});
+  }
+  catch (std::exception& e) {
+    g_log<<Logger::Warning<<", failed: "<<e.what()<<endl;
+    return "Unable to clear caches while adding Negative Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
   return "Added Negative Trust Anchor for " + who.toLogString() + " with reason '" + why + "'\n";
 }
 
@@ -581,20 +594,27 @@ static string doClearNTA(T begin, T end)
 
   string removed("");
   bool first(true);
-  for (auto const &entry : toRemove) {
-    g_log<<Logger::Warning<<"Clearing Negative Trust Anchor for "<<entry<<", requested via control channel"<<endl;
-    g_luaconfs.modify([entry](LuaConfigItems& lci) {
-        lci.negAnchors.erase(entry);
-      });
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, entry, true, 0xffff));
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, entry, true, 0xffff));
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, entry, true));
-    if (!first) {
-      first = false;
-      removed += ",";
+  try {
+    for (auto const &entry : toRemove) {
+      g_log<<Logger::Warning<<"Clearing Negative Trust Anchor for "<<entry<<", requested via control channel"<<endl;
+      g_luaconfs.modify([entry](LuaConfigItems& lci) {
+                          lci.negAnchors.erase(entry);
+                        });
+      broadcastAccFunction<uint64_t>([=]{return pleaseWipeCache(entry, true, 0xffff);});
+      broadcastAccFunction<uint64_t>([=]{return pleaseWipePacketCache(entry, true, 0xffff);});
+      broadcastAccFunction<uint64_t>([=]{return pleaseWipeAndCountNegCache(entry, true);});
+      if (!first) {
+        first = false;
+        removed += ",";
+      }
+      removed += " " + entry.toStringRootDot();
     }
-    removed += " " + entry.toStringRootDot();
   }
+  catch(std::exception &e) {
+    g_log<<Logger::Warning<<", failed: "<<e.what()<<endl;
+    return "Unable to clear caches while clearing Negative Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
+
   return "Removed Negative Trust Anchors for " + removed + "\n";
 }
 
@@ -643,9 +663,9 @@ static string doAddTA(T begin, T end)
       auto ds=std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
       lci.dsAnchors[who].insert(*ds);
       });
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, who, true, 0xffff));
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, who, true, 0xffff));
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, who, true));
+    broadcastAccFunction<uint64_t>([=]{return pleaseWipeCache(who, true, 0xffff);});
+    broadcastAccFunction<uint64_t>([=]{return pleaseWipePacketCache(who, true, 0xffff);});
+    broadcastAccFunction<uint64_t>([=]{return pleaseWipeAndCountNegCache(who, true);});
     g_log<<Logger::Warning<<endl;
     return "Added Trust Anchor for " + who.toStringRootDot() + " with data " + what + "\n";
   }
@@ -684,20 +704,27 @@ static string doClearTA(T begin, T end)
 
   string removed("");
   bool first(true);
-  for (auto const &entry : toRemove) {
-    g_log<<Logger::Warning<<"Removing Trust Anchor for "<<entry<<", requested via control channel"<<endl;
-    g_luaconfs.modify([entry](LuaConfigItems& lci) {
-        lci.dsAnchors.erase(entry);
-      });
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, entry, true, 0xffff));
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, entry, true, 0xffff));
-    broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, entry, true));
-    if (!first) {
-      first = false;
-      removed += ",";
+  try {
+    for (auto const &entry : toRemove) {
+      g_log<<Logger::Warning<<"Removing Trust Anchor for "<<entry<<", requested via control channel"<<endl;
+      g_luaconfs.modify([entry](LuaConfigItems& lci) {
+                          lci.dsAnchors.erase(entry);
+                        });
+      broadcastAccFunction<uint64_t>([=]{return pleaseWipeCache(entry, true, 0xffff);});
+      broadcastAccFunction<uint64_t>([=]{return pleaseWipePacketCache(entry, true, 0xffff);});
+      broadcastAccFunction<uint64_t>([=]{return pleaseWipeAndCountNegCache(entry, true);});
+      if (!first) {
+        first = false;
+        removed += ",";
+      }
+      removed += " " + entry.toStringRootDot();
     }
-    removed += " " + entry.toStringRootDot();
   }
+  catch (std::exception& e) {
+    g_log<<Logger::Warning<<", failed: "<<e.what()<<endl;
+    return "Unable to clear caches while clearing Trust Anchor for " + who.toStringRootDot() + ": " + e.what() + "\n";
+  }
+
   return "Removed Trust Anchor(s) for" + removed + "\n";
 }
 
@@ -882,7 +909,7 @@ static uint64_t getThrottleSize()
 
 uint64_t* pleaseGetNegCacheSize()
 {
-  uint64_t tmp=(SyncRes::getNegCacheSize());
+  uint64_t tmp = g_negCache->size();
   return new uint64_t(tmp);
 }
 
@@ -934,7 +961,7 @@ static uint64_t getConcurrentQueries()
 
 static uint64_t doGetCacheSize()
 {
-  return s_RC->size();
+  return g_recCache->size();
 }
 
 static uint64_t doGetAvgLatencyUsec()
@@ -944,17 +971,17 @@ static uint64_t doGetAvgLatencyUsec()
 
 static uint64_t doGetCacheBytes()
 {
-  return s_RC->bytes();
+  return g_recCache->bytes();
 }
 
 static uint64_t doGetCacheHits()
 {
-  return s_RC->cacheHits;
+  return g_recCache->cacheHits;
 }
 
 static uint64_t doGetCacheMisses()
 {
-  return s_RC->cacheMisses;
+  return g_recCache->cacheMisses;
 }
 
 uint64_t* pleaseGetPacketCacheSize()
@@ -1023,6 +1050,8 @@ void registerAllStats()
   addGetStat("max-cache-entries", []() { return g_maxCacheEntries.load(); });
   addGetStat("max-packetcache-entries", []() { return g_maxPacketCacheEntries.load();}); 
   addGetStat("cache-bytes", doGetCacheBytes); 
+  addGetStat("record-cache-contended", []() { return g_recCache->stats().first;});
+  addGetStat("record-cache-acquired", []() { return g_recCache->stats().second;});
   
   addGetStat("packetcache-hits", doGetPacketCacheHits);
   addGetStat("packetcache-misses", doGetPacketCacheMisses); 
@@ -1112,13 +1141,13 @@ void registerAllStats()
   addGetStat("ecs-queries", &SyncRes::s_ecsqueries);
   addGetStat("ecs-responses", &SyncRes::s_ecsresponses);
   addGetStat("chain-resends", &g_stats.chainResends);
-  addGetStat("tcp-clients", boost::bind(TCPConnection::getCurrentConnections));
+  addGetStat("tcp-clients", []{return TCPConnection::getCurrentConnections();});
 
 #ifdef __linux__
-  addGetStat("udp-recvbuf-errors", boost::bind(udpErrorStats, "udp-recvbuf-errors"));
-  addGetStat("udp-sndbuf-errors", boost::bind(udpErrorStats, "udp-sndbuf-errors"));
-  addGetStat("udp-noport-errors", boost::bind(udpErrorStats, "udp-noport-errors"));
-  addGetStat("udp-in-errors", boost::bind(udpErrorStats, "udp-in-errors"));
+  addGetStat("udp-recvbuf-errors", []{return udpErrorStats("udp-recvbuf-errors");});
+  addGetStat("udp-sndbuf-errors", []{return udpErrorStats("udp-sndbuf-errors");});
+  addGetStat("udp-noport-errors", []{return udpErrorStats("udp-noport-errors");});
+  addGetStat("udp-in-errors", []{return udpErrorStats("udp-in-errors");});
 #endif
 
   addGetStat("edns-ping-matches", &g_stats.ednsPingMatches);
@@ -1157,11 +1186,11 @@ void registerAllStats()
 #endif
 
   addGetStat("dnssec-validations", &g_stats.dnssecValidations);
-  addGetStat("dnssec-result-insecure", &g_stats.dnssecResults[Insecure]);
-  addGetStat("dnssec-result-secure", &g_stats.dnssecResults[Secure]);
-  addGetStat("dnssec-result-bogus", &g_stats.dnssecResults[Bogus]);
-  addGetStat("dnssec-result-indeterminate", &g_stats.dnssecResults[Indeterminate]);
-  addGetStat("dnssec-result-nta", &g_stats.dnssecResults[NTA]);
+  addGetStat("dnssec-result-insecure", &g_stats.dnssecResults[vState::Insecure]);
+  addGetStat("dnssec-result-secure", &g_stats.dnssecResults[vState::Secure]);
+  addGetStat("dnssec-result-bogus", &g_stats.dnssecResults[vState::Bogus]);
+  addGetStat("dnssec-result-indeterminate", &g_stats.dnssecResults[vState::Indeterminate]);
+  addGetStat("dnssec-result-nta", &g_stats.dnssecResults[vState::NTA]);
 
   addGetStat("policy-result-noaction", &g_stats.policyResults[DNSFilterEngine::PolicyKind::NoAction]);
   addGetStat("policy-result-drop", &g_stats.policyResults[DNSFilterEngine::PolicyKind::Drop]);
@@ -1172,6 +1201,8 @@ void registerAllStats()
 
   addGetStat("rebalanced-queries", &g_stats.rebalancedQueries);
 
+  addGetStat("proxy-protocol-invalid", &g_stats.proxyProtocolInvalidCount);
+
   /* make sure that the ECS stats are properly initialized */
   SyncRes::clearECSStats();
   for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize4.size(); idx++) {
@@ -1314,7 +1345,7 @@ vector<ComboAddress>* pleaseGetTimeouts()
   return ret;
 }
 
-string doGenericTopRemotes(pleaseremotefunc_t func)
+static string doGenericTopRemotes(pleaseremotefunc_t func)
 {
   typedef map<ComboAddress, int, ComboAddress::addressOnlyLessThan> counts_t;
   counts_t counts;
@@ -1382,7 +1413,7 @@ static DNSName nopFilter(const DNSName& name)
   return name;
 }
 
-string doGenericTopQueries(pleasequeryfunc_t func, boost::function<DNSName(const DNSName&)> filter=nopFilter)
+static string doGenericTopQueries(pleasequeryfunc_t func, boost::function<DNSName(const DNSName&)> filter=nopFilter)
 {
   typedef pair<DNSName,uint16_t> query_t;
   typedef map<query_t, int> counts_t;
index 599e47fdc98ec33f22ab0ec40ea2ee23a18f3ebd..6caf96898d12eeca3b0c40926bccb1167330c08a 100644 (file)
@@ -101,7 +101,7 @@ const char *funnytext=
 This file is where it all happens - main is here, as are the two pivotal threads qthread() and athread()
 */
 
-void daemonize(void)
+static void daemonize(void)
 {
   if(fork())
     exit(0); // bye bye
@@ -154,7 +154,7 @@ static void writePid(void)
 
 int g_fd1[2], g_fd2[2];
 FILE *g_fp;
-pthread_mutex_t g_guardian_lock = PTHREAD_MUTEX_INITIALIZER;
+std::mutex g_guardian_lock;
 
 // The next two methods are not in dynhandler.cc because they use a few items declared in this file.
 static string DLCycleHandler(const vector<string>&parts, pid_t ppid)
@@ -176,7 +176,7 @@ static string DLRestHandler(const vector<string>&parts, pid_t ppid)
   }
   line.append(1,'\n');
   
-  Lock l(&g_guardian_lock);
+  std::lock_guard<std::mutex> l(g_guardian_lock);
 
   try {
     writen2(g_fd1[1],line.c_str(),line.size()+1);
@@ -216,7 +216,7 @@ static int guardian(int argc, char **argv)
   bool first=true;
   cpid=0;
 
-  pthread_mutex_lock(&g_guardian_lock);
+  g_guardian_lock.lock();
 
   for(;;) {
     int pid;
@@ -292,7 +292,7 @@ static int guardian(int argc, char **argv)
 
         writePid();
       }
-      pthread_mutex_unlock(&g_guardian_lock);  
+      g_guardian_lock.unlock();
       int status;
       cpid=pid;
       for(;;) {
@@ -314,7 +314,7 @@ static int guardian(int argc, char **argv)
         }
       }
 
-      pthread_mutex_lock(&g_guardian_lock);
+      g_guardian_lock.lock();
       close(g_fd1[1]);
       fclose(g_fp);
       g_fp=0;
@@ -494,7 +494,7 @@ int main(int argc, char **argv)
       }
       cerr<<" (";
       bool first = true;
-      for (auto const c : ::arg().getCommands()) {
+      for (const auto& c : ::arg().getCommands()) {
         if (!first) {
           cerr<<", ";
         }
@@ -586,8 +586,8 @@ int main(int argc, char **argv)
     DynListener::registerFunc("RESPSIZES", &DLRSizesHandler, "get histogram of response sizes");
     DynListener::registerFunc("REMOTES", &DLRemotesHandler, "get top remotes");
     DynListener::registerFunc("SET",&DLSettingsHandler, "set config variables", "<var> <value>");
-    DynListener::registerFunc("RETRIEVE",&DLNotifyRetrieveHandler, "retrieve slave domain", "<domain>");
-    DynListener::registerFunc("CURRENT-CONFIG",&DLCurrentConfigHandler, "retrieve the current configuration", "[diff|default]");
+    DynListener::registerFunc("RETRIEVE",&DLNotifyRetrieveHandler, "retrieve slave domain", "<domain> [<ip>]");
+    DynListener::registerFunc("CURRENT-CONFIG",&DLCurrentConfigHandler, "retrieve the current configuration", "[diff]");
     DynListener::registerFunc("LIST-ZONES",&DLListZones, "show list of zones", "[master|slave|native]");
     DynListener::registerFunc("TOKEN-LOGIN", &DLTokenLogin, "Login to a PKCS#11 token", "<module> <slot> <pin>");
 
@@ -636,7 +636,13 @@ int main(int argc, char **argv)
     exit(1);
   }
   
-  declareStats();
+  try {
+    declareStats();
+  }
+  catch(PDNSException &PE) {
+    g_log<<Logger::Error<<"Exiting because: "<<PE.reason<<endl;
+    exit(1);
+  }
   S.blacklist("special-memory-usage");
 
   DLOG(g_log<<Logger::Warning<<"Verbose logging in effect"<<endl);
index 1b952ba19670044ea9ea5a9d8617cb40ef3abcd2..0f93c5d317f7973bdc64a01fc1c45641fd4be01f 100644 (file)
@@ -39,25 +39,22 @@ int RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype,
   return count;
 }
 
-bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint16_t ecsBegin, uint16_t ecsEnd)
+bool RecursorPacketCache::qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass)
 {
   // this ignores checking on the EDNS subnet flags! 
   if (qname != iter->d_name || iter->d_type != qtype || iter->d_class != qclass) {
     return false;
   }
 
-  if (iter->d_ecsBegin != ecsBegin || iter->d_ecsEnd != ecsEnd) {
-    return false;
-  }
-
-  return queryMatches(iter->d_query, queryPacket, qname, ecsBegin, ecsEnd);
+  static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
+  return queryMatches(iter->d_query, queryPacket, qname, optionsToSkip);
 }
 
-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, vState* valState, RecProtoBufMessage* protobufMessage, uint16_t ecsBegin, uint16_t ecsEnd)
+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, vState* valState, RecProtoBufMessage* protobufMessage)
 {
   for(auto iter = range.first ; iter != range.second ; ++iter) {
     // the possibility is VERY real that we get hits that are not right - birthday paradox
-    if (!qrMatch(iter, queryPacket, qname, qtype, qclass, ecsBegin, ecsEnd)) {
+    if (!qrMatch(iter, queryPacket, qname, qtype, qclass)) {
       continue;
     }
 
@@ -84,7 +81,6 @@ bool RecursorPacketCache::checkResponseMatches(std::pair<packetCache_t::index<Ha
         }
       }
 #endif
-      
       return true;
     }
     else {
@@ -102,25 +98,21 @@ bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string&
 {
   DNSName qname;
   uint16_t qtype, qclass;
-  uint16_t ecsBegin;
-  uint16_t ecsEnd;
   vState valState;
-  return getResponsePacket(tag, queryPacket, qname, &qtype, &qclass, now, responsePacket, age, &valState, qhash, &ecsBegin, &ecsEnd, nullptr);
+  return getResponsePacket(tag, queryPacket, qname, &qtype, &qclass, now, responsePacket, age, &valState, qhash, nullptr);
 }
 
 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)
 {
   vState valState;
-  uint16_t ecsBegin;
-  uint16_t ecsEnd;
-  return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, &ecsBegin, &ecsEnd, nullptr);
+  return getResponsePacket(tag, queryPacket, qname, qtype, qclass, now, responsePacket, age, &valState, qhash, nullptr);
 }
 
 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, vState* valState, uint32_t* qhash, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage)
+                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
 {
-  *qhash = canHashPacket(queryPacket, ecsBegin, ecsEnd);
+  *qhash = canHashPacket(queryPacket, true);
   const auto& idx = d_packetCache.get<HashTag>();
   auto range = idx.equal_range(tie(tag,*qhash));
 
@@ -129,13 +121,13 @@ bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string&
     return false;
   }
 
-  return checkResponseMatches(range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, protobufMessage, *ecsBegin, *ecsEnd);
+  return checkResponseMatches(range, queryPacket, qname, qtype, qclass, now, responsePacket, age, valState, protobufMessage);
 }
 
 bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now,
-                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage)
+                                            std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, RecProtoBufMessage* protobufMessage)
 {
-  *qhash = canHashPacket(queryPacket, ecsBegin, ecsEnd);
+  *qhash = canHashPacket(queryPacket, true);
   const auto& idx = d_packetCache.get<HashTag>();
   auto range = idx.equal_range(tie(tag,*qhash));
 
@@ -146,11 +138,11 @@ bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string&
 
   qname = DNSName(queryPacket.c_str(), queryPacket.length(), sizeof(dnsheader), false, qtype, qclass, 0);
 
-  return checkResponseMatches(range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, protobufMessage, *ecsBegin, *ecsEnd);
+  return checkResponseMatches(range, queryPacket, qname, *qtype, *qclass, now, responsePacket, age, valState, protobufMessage);
 }
 
 
-void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, uint16_t ecsBegin, uint16_t ecsEnd, boost::optional<RecProtoBufMessage>&& protobufMessage)
+void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, boost::optional<RecProtoBufMessage>&& protobufMessage)
 {
   auto& idx = d_packetCache.get<HashTag>();
   auto range = idx.equal_range(tie(tag,qhash));
@@ -164,8 +156,6 @@ void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash,
     moveCacheItemToBack<SequencedTag>(d_packetCache, iter);
     iter->d_packet = std::move(responsePacket);
     iter->d_query = std::move(query);
-    iter->d_ecsBegin = ecsBegin;
-    iter->d_ecsEnd = ecsEnd;
     iter->d_ttd = now + ttl;
     iter->d_creation = now;
     iter->d_vstate = valState;
@@ -181,8 +171,6 @@ void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash,
   if(iter == range.second) { // nothing to refresh
     struct Entry e(qname, std::move(responsePacket), std::move(query));
     e.d_qhash = qhash;
-    e.d_ecsBegin = ecsBegin;
-    e.d_ecsEnd = ecsEnd;
     e.d_type = qtype;
     e.d_class = qclass;
     e.d_ttd = now+ttl;
@@ -212,7 +200,7 @@ uint64_t RecursorPacketCache::bytes()
   return sum;
 }
 
-void RecursorPacketCache::doPruneTo(unsigned int maxCached)
+void RecursorPacketCache::doPruneTo(size_t maxCached)
 {
   pruneCollection<SequencedTag>(*this, d_packetCache, maxCached);
 }
index 790d714fb879af7fb9d2e7815f76af956cd6c41f..8ffc61ba83764bf9757ebcc37653c9bd0115a034 100644 (file)
@@ -53,10 +53,10 @@ public:
   RecursorPacketCache();
   bool getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now, std::string* responsePacket, uint32_t* age, uint32_t* qhash);
   bool 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);
-  bool 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, vState* valState, uint32_t* qhash, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage);
-  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage);
-  void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, uint16_t ecsBegin, uint16_t ecsEnd, boost::optional<RecProtoBufMessage>&& protobufMessage);
-  void doPruneTo(unsigned int maxSize=250000);
+  bool 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, vState* valState, uint32_t* qhash, RecProtoBufMessage* protobufMessage);
+  bool getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, RecProtoBufMessage* protobufMessage);
+  void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, boost::optional<RecProtoBufMessage>&& protobufMessage);
+  void doPruneTo(size_t maxSize=250000);
   uint64_t doDump(int fd);
   int doWipePacketCache(const DNSName& name, uint16_t qtype=0xffff, bool subtree=false);
   
@@ -86,8 +86,6 @@ private:
     uint32_t d_tag;
     uint16_t d_type;
     uint16_t d_class;
-    mutable uint16_t d_ecsBegin;
-    mutable uint16_t d_ecsEnd;
     mutable vState d_vstate;
     inline bool operator<(const struct Entry& rhs) const;
 
@@ -109,8 +107,8 @@ private:
   
   packetCache_t d_packetCache;
 
-  static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint16_t ecsBegin, uint16_t ecsEnd);
-  bool 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, vState* valState, RecProtoBufMessage* protobufMessage, uint16_t ecsBegin, uint16_t ecsEnd);
+  static bool qrMatch(const packetCache_t::index<HashTag>::type::iterator& iter, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass);
+  bool 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, vState* valState, RecProtoBufMessage* protobufMessage);
 
 public:
   void preRemoval(const Entry& entry)
index 37393efedb6e96c357a2bf206d8273668b97999a..953562123f6821b505976643a8f829cb8c08588f 100644 (file)
@@ -79,12 +79,12 @@ size_t MemRecursorCache::bytes()
   return ret;
 }
 
-int32_t MemRecursorCache::handleHit(MapCombo& map, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
+int32_t MemRecursorCache::handleHit(MapCombo& map, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
 {
   // MUTEX SHOULD BE ACQUIRED
   int32_t ttd = entry->d_ttd;
 
-  if(variable && !entry->d_netmask.empty()) {
+  if (variable && (!entry->d_netmask.empty() || entry->d_rtag)) {
     *variable = true;
   }
 
@@ -138,7 +138,7 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
         /* we have nothing more specific for you */
         break;
       }
-      auto key = boost::make_tuple(qname, qtype, best);
+      auto key = boost::make_tuple(qname, qtype, boost::none, best);
       auto entry = map.d_map.find(key);
       if (entry == map.d_map.end()) {
         /* ecsIndex is not up-to-date */
@@ -170,7 +170,7 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
   }
 
   /* we have nothing specific, let's see if we have a generic one */
-  auto key = boost::make_tuple(qname, qtype, Netmask());
+  auto key = boost::make_tuple(qname, qtype, boost::none, Netmask());
   auto entry = map.d_map.find(key);
   if (entry != map.d_map.end()) {
     if (entry->d_ttd > now) {
@@ -187,31 +187,36 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
   return map.d_map.end();
 }
 
-std::pair<MemRecursorCache::NameOnlyHashedTagIterator_t, MemRecursorCache::NameOnlyHashedTagIterator_t> MemRecursorCache::getEntries(MapCombo& map, const DNSName &qname, const QType& qt)
+MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo& map, const DNSName &qname, const QType& qt, const OptTag& rtag )
 {
   // MUTEX SHOULD BE ACQUIRED
-  if (!map.d_cachecachevalid || map.d_cachedqname != qname) {
+  if (!map.d_cachecachevalid || map.d_cachedqname != qname || map.d_cachedrtag != rtag) {
     map.d_cachedqname = qname;
-    const auto& idx = map.d_map.get<NameOnlyHashedTag>();
-    map.d_cachecache = idx.equal_range(qname);
+    map.d_cachedrtag = rtag;
+    const auto& idx = map.d_map.get<NameAndRTagOnlyHashedTag>();
+    map.d_cachecache = idx.equal_range(tie(qname, rtag));
     map.d_cachecachevalid = true;
   }
   return map.d_cachecache;
 }
 
+
 bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, uint16_t qt, bool requireAuth, const ComboAddress& who)
 {
+  // This code assumes that if a routing tag is present, it matches
   // MUTEX SHOULD BE ACQUIRED
   if (requireAuth && !entry->d_auth)
     return false;
 
-  return ((entry->d_qtype == qt || qt == QType::ANY ||
-           (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
-          && (entry->d_netmask.empty() || entry->d_netmask.match(who)));
+  bool match = (entry->d_qtype == qt || qt == QType::ANY ||
+                (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
+    && (entry->d_netmask.empty() || entry->d_netmask.match(who));
+  //cerr << match << "N " << qt << ':' << entry->d_qtype << ' ' << entry->d_netmask.toString() << ':' << who.toString() << endl;
+  return match;
 }
 
 // returns -1 for no hits
-int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
+int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
 {
   time_t ttd=0;
   //  cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
@@ -222,21 +227,20 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
 
   auto& map = getMap(qname);
   const lock l(map);
-  
+
   /* If we don't have any netmask-specific entries at all, let's just skip this
      to be able to use the nice d_cachecache hack. */
-  if (qtype != QType::ANY && !map.d_ecsIndex.empty()) {
-
+  if (qtype != QType::ANY && !map.d_ecsIndex.empty() && !routingTag) {
     if (qtype == QType::ADDR) {
       int32_t ret = -1;
 
       auto entryA = getEntryUsingECSIndex(map, now, qname, QType::A, requireAuth, who);
       if (entryA != map.d_map.end()) {
-        ret = handleHit(map, entryA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
+        ret = handleHit(map, entryA, qname, res, signatures, authorityRecs, variable, state, wasAuth);
       }
       auto entryAAAA = getEntryUsingECSIndex(map, now, qname, QType::AAAA, requireAuth, who);
       if (entryAAAA != map.d_map.end()) {
-        int32_t ttdAAAA = handleHit(map, entryAAAA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
+        int32_t ttdAAAA = handleHit(map, entryAAAA, qname, res, signatures, authorityRecs, variable, state, wasAuth);
         if (ret > 0) {
           ret = std::min(ret, ttdAAAA);
         } else {
@@ -248,16 +252,42 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
     else {
       auto entry = getEntryUsingECSIndex(map, now, qname, qtype, requireAuth, who);
       if (entry != map.d_map.end()) {
-        return static_cast<int32_t>(handleHit(map, entry, qname, who, res, signatures, authorityRecs, variable, state, wasAuth) - now);
+        return static_cast<int32_t>(handleHit(map, entry, qname, res, signatures, authorityRecs, variable, state, wasAuth) - now);
       }
       return -1;
     }
   }
 
-  auto entries = getEntries(map, qname, qt);
+  if (routingTag) {
+    auto entries = getEntries(map, qname, qt, routingTag);
 
-  if(entries.first!=entries.second) {
-    for(auto i=entries.first; i != entries.second; ++i) {
+    if (entries.first != entries.second) {
+      for (auto i=entries.first; i != entries.second; ++i) {
+
+        auto firstIndexIterator = map.d_map.project<OrderedTag>(i);
+        if (i->d_ttd <= now) {
+          moveCacheItemToFront<SequencedTag>(map.d_map, firstIndexIterator);
+          continue;
+        }
+
+        if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
+          continue;
+        }
+
+        ttd = handleHit(map, firstIndexIterator, qname, res, signatures, authorityRecs, variable, state, wasAuth);
+
+        if (qt.getCode() != QType::ANY && qt.getCode() != QType::ADDR) { // normally if we have a hit, we are done
+          break;
+        }
+      }
+      return static_cast<int32_t>(ttd-now);
+    }
+  }
+  // Try (again) without tag
+  auto entries = getEntries(map, qname, qt, boost::none);
+
+  if (entries.first != entries.second) {
+    for (auto i=entries.first; i != entries.second; ++i) {
 
       auto firstIndexIterator = map.d_map.project<OrderedTag>(i);
       if (i->d_ttd <= now) {
@@ -265,22 +295,22 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
         continue;
       }
 
-      if (!entryMatches(firstIndexIterator, qtype, requireAuth, who))
+      if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
         continue;
+      }
 
-      ttd = handleHit(map, firstIndexIterator, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
+      ttd = handleHit(map, firstIndexIterator, qname, res, signatures, authorityRecs, variable, state, wasAuth);
 
-      if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done
+      if (qt.getCode() != QType::ANY && qt.getCode() != QType::ADDR) { // normally if we have a hit, we are done
         break;
+      }
     }
-
-    // cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
     return static_cast<int32_t>(ttd-now);
   }
   return -1;
 }
 
-void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, boost::optional<Netmask> ednsmask, vState state)
+void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, boost::optional<Netmask> ednsmask, const OptTag& routingTag, vState state)
 {
   auto& map = getMap(qname);
   const lock l(map);
@@ -290,7 +320,10 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt
   if (ednsmask) {
     ednsmask = ednsmask->getNormalized();
   }
-  auto key = boost::make_tuple(qname, qt.getCode(), ednsmask ? *ednsmask : Netmask());
+
+  // We only store with a tag if we have an ednsmask and the tag is available
+  // We only store an ednsmask if we do not have a tag and we do have a mask.
+  auto key = boost::make_tuple(qname, qt.getCode(), ednsmask ? routingTag : boost::none, (ednsmask && !routingTag) ? *ednsmask : Netmask());
   bool isNew = false;
   cache_t::iterator stored = map.d_map.find(key);
   if (stored == map.d_map.end()) {
@@ -306,7 +339,7 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt
   */
   if (isNew || stored->d_ttd <= now) {
     /* don't bother building an ecsIndex if we don't have any netmask-specific entries */
-    if (ednsmask && !ednsmask->empty()) {
+    if (!routingTag && ednsmask && !ednsmask->empty()) {
       auto ecsIndexKey = boost::make_tuple(qname, qt.getCode());
       auto ecsIndex = map.d_ecsIndex.find(ecsIndexKey);
       if (ecsIndex == map.d_ecsIndex.end()) {
@@ -329,7 +362,7 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt
 
   if(!auth && ce.d_auth) {  // unauth data came in, we have some auth data, but is it fresh?
     if(ce.d_ttd > now) { // we still have valid data, ignore unauth data
-      //      cerr<<"\tStill hold valid auth data, and the new data is unauth, return\n";
+      //  cerr<<"\tStill hold valid auth data, and the new data is unauth, return\n";
       return;
     }
     else {
@@ -353,11 +386,11 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt
   ce.d_records.clear();
   ce.d_records.reserve(content.size());
 
-  for(const auto i : content) {
+  for(const auto& i : content) {
     /* Yes, we have altered the d_ttl value by adding time(nullptr) to it
        prior to calling this function, so the TTL actually holds a TTD. */
     ce.d_ttd=min(maxTTD, static_cast<time_t>(i.d_ttl));   // XXX this does weird things if TTLs differ in the set
-    //    cerr<<"To store: "<<i.d_content->getZoneRepresentation()<<" with ttl/ttd "<<i.d_ttl<<", capped at: "<<maxTTD<<endl;
+    //cerr<<"To store: "<<i.d_content->getZoneRepresentation()<<" with ttl/ttd "<<i.d_ttl<<", capped at: "<<maxTTD<<endl;
     ce.d_records.push_back(i.d_content);
   }
 
@@ -375,10 +408,15 @@ size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, uint16_t qty
     auto& map = getMap(name);
     const lock l(map);
     map.d_cachecachevalid = false;
-    auto& idx = map.d_map.get<NameOnlyHashedTag>();
-    size_t n = idx.erase(name);
-    count += n;
-    map.d_entriesCount -= n;
+    auto& idx = map.d_map.get<OrderedTag>();
+    auto range = idx.equal_range(name);
+    auto i = range.first;
+    while (i != range.second) {
+      i = idx.erase(i);
+      count++;
+      map.d_entriesCount--;
+    }
+
     if (qtype == 0xffff) {
       auto& ecsIdx = map.d_ecsIndex.get<OrderedTag>();
       auto ecsIndexRange = ecsIdx.equal_range(name);
@@ -450,14 +488,14 @@ bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, uint16_t qtyp
   return false;
 }
 
-bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
+bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
 {
   auto& map = getMap(qname);
   const lock l(map);
 
   bool updated = false;
   uint16_t qtype = qt.getCode();
-  if (qtype != QType::ANY && qtype != QType::ADDR && !map.d_ecsIndex.empty()) {
+  if (qtype != QType::ANY && qtype != QType::ADDR && !map.d_ecsIndex.empty() && !routingTag) {
     auto entry = getEntryUsingECSIndex(map, now, qname, qtype, requireAuth, who);
     if (entry == map.d_map.end()) {
       return false;
@@ -470,13 +508,14 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname,
     return true;
   }
 
-  auto entries = getEntries(map, qname, qt);
+  auto entries = getEntries(map, qname, qt, routingTag);
 
   for(auto i = entries.first; i != entries.second; ++i) {
     auto firstIndexIterator = map.d_map.project<OrderedTag>(i);
 
-    if (!entryMatches(firstIndexIterator, qtype, requireAuth, who))
+    if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
       continue;
+    }
 
     i->d_state = newState;
     if (capTTD) {
@@ -511,11 +550,11 @@ uint64_t MemRecursorCache::doDump(int fd)
     const auto& sidx = map.d_map.get<SequencedTag>();
 
     time_t now = time(0);
-    for (const auto i : sidx) {
-      for (const auto j : i.d_records) {
+    for (const auto& i : sidx) {
+      for (const auto& j : i.d_records) {
         count++;
         try {
-          fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s) auth=%i %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_qtype).c_str(), j->getZoneRepresentation().c_str(), vStates[i.d_state], i.d_auth, i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
+          fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s) auth=%i %s %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_qtype).c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str());
         }
         catch(...) {
           fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
@@ -542,3 +581,9 @@ void MemRecursorCache::doPrune(size_t keep)
   pruneMutexCollectionsVector<SequencedTag>(*this, d_maps, keep, cacheSize);
 }
 
+namespace boost {
+  size_t hash_value(const MemRecursorCache::OptTag& o)
+  {
+    return o ? hash_value(o.get()) : 0xcafebaaf;
+  }
+}
index 2b0305d82a6bae366b37e26c94ed080c09643611..8166ddab8afc76e94c134e56b850a22040181947 100644 (file)
@@ -55,16 +55,18 @@ public:
   pair<uint64_t,uint64_t> stats();
   size_t ecsIndexSize();
 
-  int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs=nullptr, bool* variable=nullptr, vState* state=nullptr, bool* wasAuth=nullptr);
+  typedef boost::optional<std::string> OptTag;
 
-  void replace(time_t, const DNSName &qname, const QType& qt,  const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, boost::optional<Netmask> ednsmask=boost::none, vState state=Indeterminate);
+  int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag = boost::none, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs=nullptr, bool* variable=nullptr, vState* state=nullptr, bool* wasAuth=nullptr);
+
+  void replace(time_t, const DNSName &qname, const QType& qt,  const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, boost::optional<Netmask> ednsmask=boost::none, const OptTag& routingTag = boost::none, vState state=vState::Indeterminate);
 
   void doPrune(size_t keep);
   uint64_t doDump(int fd);
 
   size_t doWipeCache(const DNSName& name, bool sub, uint16_t qtype=0xffff);
   bool doAgeCache(time_t now, const DNSName& name, uint16_t qtype, uint32_t newTTL);
-  bool updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
+  bool updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
 
   std::atomic<uint64_t> cacheHits{0}, cacheMisses{0};
 
@@ -72,8 +74,8 @@ private:
 
   struct CacheEntry
   {
-    CacheEntry(const boost::tuple<DNSName, uint16_t, Netmask>& key, bool auth):
-      d_qname(key.get<0>()), d_netmask(key.get<2>().getNormalized()), d_state(Indeterminate), d_ttd(0), d_qtype(key.get<1>()), d_auth(auth)
+    CacheEntry(const boost::tuple<DNSName, uint16_t, OptTag, Netmask>& key, bool auth):
+      d_qname(key.get<0>()), d_netmask(key.get<3>().getNormalized()), d_rtag(key.get<2>()), d_state(vState::Indeterminate), d_ttd(0), d_qtype(key.get<1>()), d_auth(auth)
     {
     }
 
@@ -88,6 +90,7 @@ private:
     std::vector<std::shared_ptr<DNSRecord>> d_authorityRecs;
     DNSName d_qname;
     Netmask d_netmask;
+    OptTag d_rtag;
     mutable vState d_state;
     mutable time_t d_ttd;
     uint16_t d_qtype;
@@ -143,7 +146,7 @@ private:
 
   struct HashedTag {};
   struct SequencedTag {};
-  struct NameOnlyHashedTag {};
+  struct NameAndRTagOnlyHashedTag {};
   struct OrderedTag {};
 
   typedef multi_index_container<
@@ -154,19 +157,24 @@ private:
                                 CacheEntry,
                                 member<CacheEntry,DNSName,&CacheEntry::d_qname>,
                                 member<CacheEntry,uint16_t,&CacheEntry::d_qtype>,
+                                member<CacheEntry,OptTag,&CacheEntry::d_rtag>,
                                 member<CacheEntry,Netmask,&CacheEntry::d_netmask>
                           >,
-                          composite_key_compare<CanonDNSNameCompare, std::less<uint16_t>, std::less<Netmask> >
+                               composite_key_compare<CanonDNSNameCompare, std::less<uint16_t>, std::less<OptTag>, std::less<Netmask> >
                 >,
                 sequenced<tag<SequencedTag> >,
-                hashed_non_unique<tag<NameOnlyHashedTag>,
-                        member<CacheEntry,DNSName,&CacheEntry::d_qname>
+                hashed_non_unique<tag<NameAndRTagOnlyHashedTag>,
+                    composite_key<
+                      CacheEntry,
+                      member<CacheEntry,DNSName,&CacheEntry::d_qname>,
+                      member<CacheEntry,OptTag,&CacheEntry::d_rtag>
+                    >
                 >
                >
   > cache_t;
 
   typedef MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator OrderedTagIterator_t;
-  typedef MemRecursorCache::cache_t::index<MemRecursorCache::NameOnlyHashedTag>::type::iterator NameOnlyHashedTagIterator_t;
+  typedef MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator NameAndRTagOnlyHashedTagIterator_t;
 
   typedef multi_index_container<
     ECSIndexEntry,
@@ -189,6 +197,8 @@ private:
     >
   > ecsIndex_t;
 
+  typedef std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t> Entries;
+  
   struct MapCombo
   {
     MapCombo() {}
@@ -197,12 +207,18 @@ private:
     cache_t d_map;
     ecsIndex_t d_ecsIndex;
     DNSName d_cachedqname;
-    std::pair<MemRecursorCache::NameOnlyHashedTagIterator_t, MemRecursorCache::NameOnlyHashedTagIterator_t> d_cachecache;
+    OptTag d_cachedrtag;
+    Entries d_cachecache;
     std::mutex mutex;
     bool d_cachecachevalid{false};
     std::atomic<uint64_t> d_entriesCount{0};
     uint64_t d_contended_count{0};
     uint64_t d_acquired_count{0};
+
+    void invalidate()
+    {
+      d_cachecachevalid = false;
+    }
   };
 
   vector<MapCombo> d_maps;
@@ -212,9 +228,9 @@ private:
   }
 
   bool entryMatches(OrderedTagIterator_t& entry, uint16_t qt, bool requireAuth, const ComboAddress& who);
-  std::pair<NameOnlyHashedTagIterator_t, NameOnlyHashedTagIterator_t> getEntries(MapCombo& map, const DNSName &qname, const QType& qt);
+  Entries getEntries(MapCombo& map, const DNSName &qname, const QType& qt, const OptTag& rtag);
   cache_t::const_iterator getEntryUsingECSIndex(MapCombo& map, time_t now, const DNSName &qname, uint16_t qtype, bool requireAuth, const ComboAddress& who);
-  int32_t handleHit(MapCombo& map, OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth);
+  int32_t handleHit(MapCombo& map, OrderedTagIterator_t& entry, const DNSName& qname, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth);
 
 public:
   struct lock {
@@ -250,3 +266,7 @@ public:
     }
   }
 };
+
+namespace boost {
+  size_t hash_value(const MemRecursorCache::OptTag& rtag);
+}
index 20105c0e2561bce6057503c689d34eddc8cf9efe..adac88c82b446e1fa4f5b3a2457c255506156c70 100644 (file)
@@ -120,7 +120,6 @@ pdns_recursor_SOURCES = \
        filterpo.cc filterpo.hh \
        fstrm_logger.cc fstrm_logger.hh \
        gettime.cc gettime.hh \
-       gss_context.cc gss_context.hh \
        iputils.hh iputils.cc \
        ixfr.cc ixfr.hh \
        json.cc json.hh \
@@ -144,9 +143,11 @@ pdns_recursor_SOURCES = \
        pdnsexception.hh \
        pollmplexer.cc \
        protobuf.cc protobuf.hh \
+       proxy-protocol.cc proxy-protocol.hh \
        pubsuffix.hh pubsuffix.cc \
        pubsuffixloader.cc \
        qtype.hh qtype.cc \
+       query-local-address.hh query-local-address.cc \
        rcpgenerator.cc rcpgenerator.hh \
        rec-carbon.cc \
        rec-lua-conf.hh rec-lua-conf.cc \
@@ -159,6 +160,7 @@ pdns_recursor_SOURCES = \
        reczones.cc \
        remote_logger.cc remote_logger.hh \
        resolver.hh resolver.cc \
+       axfr-retriever.hh axfr-retriever.cc \
        resolve-context.hh \
        responsestats.hh responsestats.cc \
        root-addresses.hh \
@@ -167,6 +169,7 @@ pdns_recursor_SOURCES = \
        secpoll-recursor.cc secpoll-recursor.hh \
        secpoll.cc secpoll.hh \
        sholder.hh \
+       shuffle.cc shuffle.hh \
        sillyrecords.cc \
        snmp-agent.hh snmp-agent.cc \
        sortlist.cc sortlist.hh \
@@ -181,6 +184,7 @@ pdns_recursor_SOURCES = \
        uuid-utils.hh uuid-utils.cc \
        validate.cc validate.hh validate-recursor.cc validate-recursor.hh \
        version.cc version.hh \
+       views.hh \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-recursor.cc ws-recursor.hh \
@@ -220,6 +224,7 @@ endif
 
 testrunner_SOURCES = \
        arguments.cc \
+       axfr-retriever.hh axfr-retriever.cc \
        base32.cc \
        base64.cc base64.hh \
        circular_buffer.hh \
@@ -237,7 +242,6 @@ testrunner_SOURCES = \
        ednssubnet.cc ednssubnet.hh \
        filterpo.cc filterpo.hh \
        gettime.cc gettime.hh \
-       gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
        ixfr.cc ixfr.hh \
        logger.cc logger.hh \
@@ -251,6 +255,7 @@ testrunner_SOURCES = \
        pollmplexer.cc \
        protobuf.cc protobuf.hh \
        qtype.cc qtype.hh \
+       query-local-address.hh query-local-address.cc \
        rcpgenerator.cc \
        rec-protobuf.cc rec-protobuf.hh \
        recpacketcache.cc recpacketcache.hh \
@@ -300,6 +305,7 @@ testrunner_SOURCES = \
        test-syncres_cc7.cc \
        test-syncres_cc8.cc \
        test-syncres_cc9.cc \
+       test-syncres_cc10.cc \
        test-tsig.cc \
        test-xpf_cc.cc \
        testrunner.cc \
@@ -513,12 +519,24 @@ endif
 if !HAVE_SYSTEMD_PRIVATE_TMP
        $(AM_V_GEN)perl -ni -e 'print unless /^PrivateTmp/' $@
 endif
+if !HAVE_SYSTEMD_PRIVATE_USERS
+       $(AM_V_GEN)perl -ni -e 'print unless /^PrivateUsers/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_CLOCK
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectClock/' $@
+endif
 if !HAVE_SYSTEMD_PROTECT_CONTROL_GROUPS
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectControlGroups/' $@
 endif
 if !HAVE_SYSTEMD_PROTECT_HOME
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHome/' $@
 endif
+if !HAVE_SYSTEMD_PROTECT_HOSTNAME
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectHostname/' $@
+endif
+if !HAVE_SYSTEMD_PROTECT_KERNEL_LOGS
+       $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelLogs/' $@
+endif
 if !HAVE_SYSTEMD_PROTECT_KERNEL_MODULES
        $(AM_V_GEN)perl -ni -e 'print unless /^ProtectKernelModules/' $@
 endif
@@ -537,6 +555,9 @@ endif
 if !HAVE_SYSTEMD_RESTRICT_REALTIME
        $(AM_V_GEN)perl -ni -e 'print unless /^RestrictRealtime/' $@
 endif
+if !HAVE_SYSTEMD_RESTRICT_SUIDSGID
+       $(AM_V_GEN)perl -ni -e 'print unless /^RestrictSUIDSGID/' $@
+endif
 if !HAVE_SYSTEMD_SYSTEM_CALL_ARCHITECTURES
        $(AM_V_GEN)perl -ni -e 'print unless /^SystemCallArchitectures/' $@
 endif
@@ -547,6 +568,7 @@ endif
 pdns-recursor@.service: pdns-recursor.service
        $(AM_V_GEN)sed -e 's!/pdns_recursor!& --config-name=%i!' \
          -e 's!Recursor!& %i!' \
+         -e 's!RuntimeDirectory=.*!&-%i!' \
          < $< > $@
 
 systemdsystemunitdir = $(SYSTEMD_DIR)
index d4d70e5cbf48e0212572f5cfbd5fdc355e740edd..9d735211fbad2150259f162c4a2f3c28b1afee46 100644 (file)
@@ -15,7 +15,7 @@ IMPORTS
         FROM SNMPv2-CONF;
 
 rec MODULE-IDENTITY
-    LAST-UPDATED "201911140000Z"
+    LAST-UPDATED "202002170000Z"
     ORGANIZATION "PowerDNS BV"
     CONTACT-INFO "support@powerdns.com"
     DESCRIPTION
@@ -30,6 +30,9 @@ rec MODULE-IDENTITY
     REVISION "201911140000Z"
     DESCRIPTION "Added qnameMinFallbackSuccess stats."
 
+    REVISION "202002170000Z"
+    DESCRIPTION "Added proxyProtocolInvalid metric."
+
     ::= { powerdns 2 }
 
 powerdns               OBJECT IDENTIFIER ::= { enterprises 43315 }
@@ -836,6 +839,30 @@ qnameMinFallbackSuccess OBJECT-TYPE
         "Number of successful queries due to fallback mechanism within 'qname-minimization' setting"
     ::= { stats 100 }
 
+proxyProtocolInvalid OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of invalid proxy protocol headers received"
+    ::= { stats 101 }
+
+recordCacheContended OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of contended record cache lock acquisitions"
+    ::= { stats 102 }
+
+recordCacheAcquired OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Number of record cache lock acquisitions"
+    ::= { stats 103 }
+
 ---
 --- Traps / Notifications
 ---
@@ -979,7 +1006,10 @@ recGroup OBJECT-GROUP
         specialMemoryUsage,
         rebalancedQueries,
         trapReason,
-        qnameMinFallbackSuccess
+        qnameMinFallbackSuccess,
+        proxyProtocolInvalid,
+        recordCacheContended,
+        recordCacheAcquired
     }
     STATUS current
     DESCRIPTION "Objects conformance group for PowerDNS Recursor"
diff --git a/pdns/recursordist/axfr-retriever.cc b/pdns/recursordist/axfr-retriever.cc
new file mode 120000 (symlink)
index 0000000..e3e1df8
--- /dev/null
@@ -0,0 +1 @@
+../axfr-retriever.cc
\ No newline at end of file
diff --git a/pdns/recursordist/axfr-retriever.hh b/pdns/recursordist/axfr-retriever.hh
new file mode 120000 (symlink)
index 0000000..7e99348
--- /dev/null
@@ -0,0 +1 @@
+../axfr-retriever.hh
\ No newline at end of file
index 4ca20ce1184e58af2f87b7ef6c51382658960480..0b756510ae58af44ff2e17564d14162a74016346 100644 (file)
@@ -11,8 +11,8 @@ AC_CONFIG_HEADERS([config.h])
 
 AC_CANONICAL_HOST
 # Add some default CFLAGS and CXXFLAGS, can be appended to using the environment variables
-CFLAGS="-Wall -Wextra -Wshadow -Wno-unused-parameter -g -O2 $CFLAGS"
-CXXFLAGS="-Wall -Wextra -Wshadow -Wno-unused-parameter -g -O2 $CXXFLAGS"
+CFLAGS="-Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -g -O2 $CFLAGS"
+CXXFLAGS="-std=c++11 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -g -O2 $CXXFLAGS"
 
 AC_SUBST([pdns_configure_args],["$ac_configure_args"])
 AC_DEFINE_UNQUOTED([PDNS_CONFIG_ARGS],
@@ -33,7 +33,7 @@ AC_DEFINE([RECURSOR], [1],
 # Warn when pkg.m4 is missing
 m4_pattern_forbid([^_?PKG_[A-Z_]+$], [*** pkg.m4 missing, please install pkg-config])
 
-AX_CXX_COMPILE_STDCXX_11([ext], [mandatory])
+AX_CXX_COMPILE_STDCXX_11([noext], [mandatory])
 AC_PROG_LIBTOOL
 
 PDNS_CHECK_OS
index 18b4df547dd716511b27b7a459e05da779d7fe3f..18c402f5b456f7be7906fac52e9cbcd88c80b928 100644 (file)
@@ -13,7 +13,7 @@ To test, use the 'kvresp' example program provided.
 --]]
 
 function preresolve (dq)
-       print ("prereesolve handler called for: "..dq.remoteaddr:toString().. ", local: ".. dq.localaddr:toString()..", ".. dq.qname:toString()..", ".. dq.qtype)
+       print ("preresolve handler called for: "..dq.remoteaddr:toString().. ", local: ".. dq.localaddr:toString()..", ".. dq.qname:toString()..", ".. dq.qtype)
        dq.followupFunction="udpQueryResponse"
        dq.udpCallback="gotdomaindetails"
        dq.udpQueryDest=newCA("127.0.0.1:5555")
index 6d2a505e2ac43639e21344c008a4f4237e3b75d4..57975560b94aa18a8acef17cdbaf0d51e527e910 100644 (file)
@@ -1,6 +1,12 @@
 End of life statements
 ======================
 
+We aim to have a release every six months.
+The latest and previous release receive correctness, stability and security updates.
+The release before that gets critical security updates only.
+Older releases are marked end of life and receive no updates at all.
+Pre-releases do not receive immediate security updates.
+
 The currently supported release train of the PowerDNS Recursor is 4.3.
 
 PowerDNS Recursor 4.2 will only receive correctness, stability and
@@ -16,3 +22,28 @@ PowerDNS Recursor 4.0, 3.x, and 2.x are end of life.
 Note: Users with a commercial agreement with PowerDNS.COM BV or Open-Xchange
 can receive extended support for releases which are End Of Life. If you are
 such a user, these EOL statements do not apply to you.
+
+.. list-table:: PowerDNS Recursor Release Life Cycle
+   :header-rows: 1
+
+   * - Version
+     - Release date
+     - Security-Only updates
+     - End of Life
+   * - 4.3
+     - March 3 2020
+     - ~ March 2021
+     - ~ September 2021
+   * - 4.2
+     - July 16 2019
+     - ~ September 2020
+     - ~ March 2021
+   * - 4.1
+     - December 4 2017
+     - March 3 2020
+     - ~ September 2020
+   * - 4.0 and older
+     - EOL
+     - EOL
+     - EOL
+
index b33ebaf6a00d4e41a50aa0a26c0f8f98e4c641bf..8c1eade07c9e7414188a27257b37d87acd781d1a 100644 (file)
@@ -65,13 +65,13 @@ Protobuf to emit DNS logs
 
 The PowerDNS Recursor can log DNS query information over :doc:`Protocol Buffers <../lua-config/protobuf>`.
 To enable this functionality, install the  `protobuf <https://developers.google.com/protocol-buffers/>`_ library and compiler.
-The configure script will automatically detect this and bump the Boost version depencency to 1.42.
+The configure script will automatically detect this and bump the Boost version dependency to 1.42.
 
 To disable building this functionality, use ``--without-protobuf``.
 
 systemd notify support
 ^^^^^^^^^^^^^^^^^^^^^^
 
-During configure, ``configure`` will attempt to detect the availibility of `systemd or systemd-daemon <https://freedesktop.org/wiki/Software/systemd/>`_ headers.
+During configure, ``configure`` will attempt to detect the availability of `systemd or systemd-daemon <https://freedesktop.org/wiki/Software/systemd/>`_ headers.
 To force the use of systemd (and failing configure if the headers do not exist), use ``--enable-systemd``.
 To set the directory where the unit files should be installed, use ``--with-systemd=/path/to/unit/dir``.
index 2e1517200ca98394275bd154792a02c7db37edca..81bfa025bd00b99750d05182e5f5e7d8f6f72be1 100644 (file)
@@ -357,6 +357,41 @@ nameserver left to answer queries, and we need to be prepared for that.
 This is the whole DNS algorithm in PowerDNS, all in less than 700 lines
 of code. It contains a lot of tricky bits though, related to the cache.
 
+QName Minimization
+------------------
+
+Since the 4.3 release, the recursor implements a relaxed form of QName
+Minimization. This is a method to enhance privacy and described in the
+(draft) RFC 7816. By asking the authoritative server not the full
+QName, but one more label than we already know it is authoritative for
+we do not leak which exact names are queried to servers higher up in
+the hierarchy.
+
+The implementation uses a relaxed form of QName Minimization, following
+the recommendations found in the paper "A First Look at QNAME
+Minimization in the Domain Name System" by De Vries et all.
+
+We originally started with using NS probes as the example algorithm in
+the RFC draft recommends.
+
+We then quickly discovered that using NS probes were somewhat
+troublesome and after reading the mentioned paper we changed to QType
+A for probes, which worked better. We did not implemented the extra
+label prepend, not understanding why that would be needed (a more
+recent draft of the RFC came to the same conclusion).
+
+Following the recommendations in the paper we also implemented larger
+steps when many labels are present. We use steps 1-1-1-3-3-...; we
+already have a limit on the number of outgoing queries induced by a
+client query. We do a final full QName query if we get an unexpected
+error. This happens when we encounter authoritative servers that are
+not fully compliant, there are still many servers like that. The
+recursor records with respect to this fallback scenario in the
+``qname-min-fallback-success`` metric.
+
+For forwarded queries, we do not use QName Minimization.
+
+
 Some of the things we glossed over
 ----------------------------------
 
index 430452b776fea42d4aa6866331ce98c3a02aeba8..67a25ac7a64cc4119676952d173084a81ca1c6a2 100644 (file)
@@ -2,7 +2,54 @@ Changelogs for 4.1.x
 ====================
 
 .. changelog::
-  :version: 4.1.15
+  :version: 4.1.17
+  :released: 1st of July 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9283
+
+    Backport of CVE-2020-14196: Enforce webserver ACL.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9129
+    :tickets: 9127, 8640
+
+    Fix compilation on systems that do not define HOST_NAME_MAX.
+
+
+.. changelog::
+  :version: 4.1.16
+  :released: 19th of May 2020
+
+  .. change::
+     :tags: Bug Fixes
+     :pullreq: 9117
+
+     Backport of security fixes for CVE-2020-10995, CVE-2020-12244 and
+     CVE-2020-10030, plus avoid a crash when loading an invalid RPZ.
+
+  .. change::
+     :tags: Internals
+     :pullreq: 8809
+
+     Update python dependencies for docs generation.
+
+  .. change::
+     :tags: Improvements
+     :pullreq:  8868
+
+     Only log qname parsing errors when 'log-common-errors' is set.
+
+  .. change::
+     :tags: Internals
+     :pullreq: 8753
+
+     Update boost.m4.
+
+.. changelog::
+   :version: 4.1.15
   :released: 6th of December 2019
 
   .. change::
@@ -751,7 +798,7 @@ Changelogs for 4.1.x
     :pullreq: 5912
 
     Fix going Insecure on NSEC3 hashes with too many iterations, since
-    we could have gone Bogus on a positive answer synthetized from a
+    we could have gone Bogus on a positive answer synthesized from a
     wildcard if the corresponding NSEC3 had more iterations that we were
     willing to accept, while the correct result is Insecure.
 
@@ -1091,7 +1138,7 @@ Changelogs for 4.1.x
     :tags: Improvements
     :pullreq: 5699
 
-    Implement dynamic cache sizeing.
+    Implement dynamic cache sizing.
 
   .. change::
     :tags: Bug Fixes, DNSSEC
index 7eaf6809454adcc13f8f4d98d1b0f0418f8edd63..1da98268fb2a4a96893f6eb0ef5d34fb118ef18b 100644 (file)
@@ -1,5 +1,140 @@
 Changelogs for 4.2.x
 ====================
+.. changelog::
+  :version: 4.2.4
+  :released: 17th of July 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9334
+    :tickets: 9309
+
+    Validate cached DNSKEYs against the DSs, not the RRSIGs only.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9333
+    :tickets: 9297
+
+    Ignore cache-only for DNSKEYS/DS retrieval.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9332
+    :tickets: 9292
+
+    A ServFail while retrieving DS/DNSKEY records is just that.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9331
+    :tickets: 9188
+
+    Refuse DS records received from child zones.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9306
+    :tickets: 9268
+
+    Better exception handling in housekeeping/handlePolicyHit.
+
+.. changelog::
+  :version: 4.2.3
+  :released: 1st of July 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9284
+
+    Backport of CVE-2020-14196: Enforce webserver ACL.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9261
+    :tickets: 9251
+
+    Copy the negative cache entry before validating it.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9133
+    :tickets: 9127
+
+    Fix compilation on systems that do not define HOST_NAME_MAX.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9123
+    :tickets: 8640
+
+    Fix build with gcc-10
+
+.. changelog::
+  :version: 4.2.2
+  :released: 19th of May 2020
+
+  .. change::
+     :tags: Bug Fixes
+     :pullreq: 9116
+
+     Backport of security fixes for CVE-2020-10995, CVE-2020-12244 and
+     CVE-2020-10030, plus avoid a crash when loading an invalid RPZ.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9081
+
+    Add ubuntu focal target.
+
+  .. change::
+    :tags: Internals
+    :pullreq: 8988
+
+    Update gen-version to use latest tag for version number.
+
+  .. change::
+    :tags: Internals
+    :pullreq: 8964, 8752
+    :tickets: 8875
+
+    Update boost.m4.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8869
+
+    Only log qname parsing errors when 'log-common-errors' is set.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8832
+
+    Refuse NSEC records with a bitmap length > 32.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8802
+
+    Avoid startup race by setting the state of a tread before starting it.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8696
+
+    Better detection of Bogus zone cuts for DNSSEC validation.
+
+  .. change::
+    :tags: Bug Fixes.
+    :pullreq: 8674
+
+    Debian postinst / do not fail on user creation if it already exists.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8686
+
+    Fix parsing `dont-throttle-names` and `dont-throttle-netmasks` as comma separated lists.
 
 .. changelog::
   :version: 4.2.1
index 2a8e46bfbdd1a5733da162d519a637d97e159b62..d5ed14ae2e8c55b1a4a97fc8128d8f8b63e43c7e 100644 (file)
@@ -1,6 +1,187 @@
 Changelogs for 4.3.x
 ====================
 
+.. changelog::
+  :version: 4.3.4
+  :released: 8th of September 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9416
+    :tickets: 9375
+
+    Allow some more depth headroom for the no-qname-minimization fallback case.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9367
+
+    Resize hostname to final size in getCarbonHostname().
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9397
+    :tickets: 9073
+
+    Ensure runtime dirs for virtual services differ.
+
+.. changelog::
+  :version: 4.3.3
+  :released: 17th of July 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9330
+    :tickets: 9309
+
+    Validate cached DNSKEYs against the DSs, not the RRSIGs only.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9329
+    :tickets: 9297
+
+    Ignore cache-only for DNSKEYs and DS retrieval.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9328
+    :tickets: 9292
+
+    A ServFail while retrieving DS/DNSKEY records is just that.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9327
+    :tickets: 9188
+
+    Refuse DS records received from child zones.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9305
+    :tickets: 9268
+
+    Better exception handling in houseKeeping/handlePolicyHit.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9304
+    :tickets: 9299, 9301
+
+    Take initial refresh time from loaded zone.
+
+.. changelog::
+  :version: 4.3.2
+  :released: 1st of July 2020
+
+   .. change::
+     :tags: Bug Fixes
+     :pullreq: 9285
+
+     Backport of CVE-2020-14196: Enforce webserver ACL.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9262
+    :tickets: 9251
+
+    Copy the negative cache entry before validating it.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9242
+    :tickets: 9031
+
+    Fix compilation of the ports event multiplexer.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9243
+    :tickets: 9142
+
+    Defer the NOD lookup until after the response has been sent.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9245
+    :tickets: 9151
+
+    Fix the handling of DS queries for the root.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9246
+    :tickets: 9172
+
+    Fix RPZ removals when an update has several deltas.
+
+  .. change::
+    :tags: Bug Fixes.
+    :pullreq: 9247
+    :tickets: 9192, 9184
+
+    Correct depth increments.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9248
+    :tickets: 9194, 9202, 9216
+
+    CNAME loop detection.
+
+  .. change::
+    :tags: Bug Fixes.
+    :pullreq: 9249
+    :tickets: 9205
+
+    Limit the TTL of RRSIG records as well
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9128
+    :tickets: 9127
+
+    Fix compilation on systems that do not define HOST_NAME_MAX.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9122
+    :tickets: 8640
+
+    Fix build with gcc-10.
+
+.. changelog::
+  :version: 4.3.1
+  :released: 19th of May 2020
+
+  .. change::
+     :tags: Bug Fixes
+     :pullreq: 9115
+
+     Backport of security fixes for CVE-2020-10995, CVE-2020-12244 and
+     CVE-2020-10030, plus avoid a crash when loading an invalid RPZ.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9082
+
+    Add ubuntu focal target.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9048
+    :tickets: 8778
+
+    RPZ dumpFile/seedFile: store/get SOA refresh on dump/load.
+
+  .. change::
+    :tags: Internals
+    :pullreq: 8963
+    :tickets: 8875
+
+    Update boost.m4.
+
 .. changelog::
   :version: 4.3.0
   :released: 3rd of March 2020
@@ -71,7 +252,7 @@ Changelogs for 4.3.x
     :tags: Improvements
     :pullreq: 8726
 
-    Give an explicit messsage if something is wrong with socket-dir.
+    Give an explicit message if something is wrong with socket-dir.
 
 .. changelog::
   :version: 4.3.0-beta2
@@ -170,7 +351,7 @@ Changelogs for 4.3.x
     :tags: Improvements
     :pullreq: 8440
 
-    Fix -WShadow warnings (Aki Tuomi)
+    Fix -Wshadow warnings (Aki Tuomi)
 
   .. change::
     :tags: Improvements
diff --git a/pdns/recursordist/docs/changelog/4.4.rst b/pdns/recursordist/docs/changelog/4.4.rst
new file mode 100644 (file)
index 0000000..fb3496d
--- /dev/null
@@ -0,0 +1,461 @@
+Changelogs for 4.4.x
+====================
+.. changelog::
+  :version: 4.4.0-rc1
+  :released: 21st of September 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9465
+    :tickets: 9448
+
+    Only do QName Minimization for the names inside a forwarded domain.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9458
+
+    Fix the parsing of `dont-throttle-netmasks` in the presence of `dont-throttle-names`.
+
+.. changelog::
+  :version: 4.4.0-beta1
+  :released: 31st of August 2020
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9376
+
+    Store RPZ trigger and hit in appliedPolicy and protobuf message
+    and log them in the trace log.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9414
+    :tickets: 9363
+
+    Apply filtering policies (RPZ) on CNAME chains as well.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9411
+
+    Fix warning: initialized lambda captures are a C++14 extension.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9375
+
+    Allow some more depth headroom for the no-qname-minimization fallback case.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9412
+
+    Clean some coverity reported cases of exceptions thrown but not caught.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9391
+
+    Export record cache lock (contention) stats via the various channels.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9396
+
+    Allow multiple local data records when doing RPZ IP matching.
+
+  .. change::
+    :tags: Improvements, Internals
+    :pullreq: 9380
+
+    Replace the use of '1' by QClass::IN to improve readability.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9351
+    :tickets: 9227
+
+    If we have an NS in cache, use it in the forwarder case.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9196
+
+    Disable outgoing v4 when query-local-address has no v4 addresses.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9343
+
+    Resize hostname to final size in getCarbonHostname() (Aki Tuomi).
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9348
+    :tickets: 9279
+
+    Avoid name clashes on Solaris derived systems.
+
+.. changelog::
+  :version: 4.4.0-alpha2
+  :released: 20th of July 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9320
+
+    Update proxy-protocol.cc (ihsinme).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9308
+
+    Check that DNSKEYs have the zone flag set.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9314
+
+    Remove redundant toLogString() calls (Chris Hofstaedtler).
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9312
+
+    Stop cluttering the global namespace with validation states.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9231
+
+    Use explicit flag for the specific version of c++ we're targeting.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9303
+
+    Use new operator to print states.
+
+  .. change::
+    :tags: Internals, Bug Fixes
+    :pullreq: 9302
+
+    Kill an signed vs unsigned warning on OpenBSD.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9290
+
+    Refuse QType 0 right away, based on rfc6895 section 3.1.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9295
+
+    Specify a storage type for validation states.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9289
+
+    Common TCP write problems should only be logged if wanted.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9288
+
+    Dump the authority records of a negative cache entry as well.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9237
+
+    Don't validate a NXD with a NSEC proving that the name is an ENT.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9272
+    :tickets: 9266
+
+    Alternative way to do "skip cname check" for DS and DNSKEY records
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9267
+
+    Control stack depth when priming.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9252
+
+    Add version 'statistic' to prometheus.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9236
+
+    Cleanup cache cleaner pruneCollection function.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9226
+
+    Fix three shared cache issues.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9203
+
+    RPZ policy should override gettag_ffi answer by default.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9216
+
+    Don't copy the records when scanning for CNAME loops.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9213
+
+    Do not use `using namespace std;` .
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9202
+    :tickets: 9153, 9194
+
+    More sophisticated CNAME loop detection.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9205
+    :tickets: 9193
+
+    Limit the TTL of RRSIG records as well.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9207
+
+    Use std::string_view when available (Rosen Penev).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9152
+
+    Make sure we can install unsigned packages.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9162
+
+    Clarify docs (Josh Soref).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9073
+
+    Ensure runtime dirs for virtual services differ.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9085
+    :tickets: 8094
+
+    Builder: improve shipped config files (Chris Hofstaedtler).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9100
+
+    Less negatives in error messages improves readability.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9070
+
+    Boost 1.73 moved boost::bind placeholders to the placeholders namespace.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9079
+
+    Avoid throwing an exception in Logger::log().
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9076
+
+    Fix useless copies in loop reported by clang++ 10.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9078
+
+    NetmaskTree: do not test node for null, the loop guarantees node is not null.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9067
+
+    Wrap pthread objects
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9053
+
+    Get rid of a naked pointer in the /dev/poll event multiplexer.
+
+  .. change::
+    :tags: Internals, Improvements
+    :pullreq: 9016
+    :tickets: 9004
+
+    Random engine.
+
+.. changelog::
+  :version: 4.4.0-alpha1
+  :released: 22th of April 2020
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 9031
+    :tickets: 9025
+
+    Fix compilation of the ports event multiplexer.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 9000
+
+    Fix warnings with llvm10 and -Wrange-loop-construct (Kirill Ponomarev).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8985
+
+    Fix compilation without deprecated OpenSSL APIs (Rosen Penev).
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8967
+
+    Implement native DNS64 support, without Lua.
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8927
+
+    Add custom tags to RPZ hits.
+
+  .. change::
+    :tags: New Features
+    :pullreq:  8910
+
+    Allow attaching a 'routing' tag string to a query in lua code and use that
+    tag in the record cache when appropriate.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8900
+    :tickets: 8739
+
+    Detect {Libre,Open}SSL functions availability during configure.
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8898
+
+    Share record cache between threads.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8887
+
+    Better handling of reconnections in Remote Logger.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8883
+    :tickets: 8629
+
+    Add 'queue full' metrics for our remote logger, log at debug only.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8876, 8740
+    :tickets: 8875
+
+    Update boost.m4
+
+  .. change::
+    :tags: New Features
+    :pullreq: 8874
+
+    Add support for Proxy Protocol between dnsdist and the recursor.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8812
+
+    Keep a masked network in the Netmask class.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8631
+
+    Replace include guard ifdef/define with pragma once (Chris Hofstaedtler).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8830
+
+    Init zone's d_priority field.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8815
+
+    YaHTTP: Support bracketed IPv6 addresses
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8355
+
+    Rework NetmaskTree for better CPU and memory efficiency (Stephan Bosch).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8777
+    :tickets: 8697
+
+    QName Minimization sometimes uses 1 label too many.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8778
+
+    RPZ dumpFile/seedFile: store/get SOA refresh on dump/load.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8783
+
+    Add 'IO wait' and 'steal' metrics on Linux.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8792
+
+    DNSName: Don't call strlen() when the length is already known.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8640
+
+    Fix build with gcc-10 (Sander Hoentjen).
+
+
index 88022ed2097213b1cb866fd44fcb248ae5646cfb..6e6d9da42a534727726366b52f3190f1583aae83 100644 (file)
@@ -6,6 +6,7 @@ The changelogs for the recursor are split between release trains.
 .. toctree::
     :maxdepth: 2
 
+    4.4
     4.3
     4.2
     4.1
index f3695de4084b059cd7cb1aee69a34616f7860087..c4d08d84d403a8a6f31b9aedc7f31f5e9ddf3576 100644 (file)
@@ -141,7 +141,7 @@ Improvements:
    messages from being logged f48d7b657ec32517f8bfcada3bfe6353ca313314
 -  Webserver now implements CORS for the API
    ea89a97e864c43c1cb03f2959ad04c4ebe7580ad, fixing ticket #1984
--  Houskeeping thread would sometimes run multiple times simultaneously,
+-  Housekeeping thread would sometimes run multiple times simultaneously,
    which worked, but was odd cc59bce675e62e2b9657b42614ce8be3312cae82
 
 New features:
index 9c5e609d6379c5fafe07bcb48828480a1f4103cb..1f7b9ff105f5190c29d33f51ff24d25bb5b73bfe 100644 (file)
@@ -9,7 +9,8 @@ However, if ``example.com`` does not actually have an IPv6 address, what we do i
 We do this by retrieving the A records for ``www.example.com``, and translating them to AAAA records.
 Elsewhere, a NAT64 device listens on these IPv6 addresses, and extracts the IPv4 address from each packet, and proxies it on.
 
-For maximum flexibility, DNS64 support is included in the :doc:`lua-scripting/index`.
+As of 4.4.0, an efficient implementation is built the recursor and can be enabled via the using the :ref:`dns64-prefix setting <setting-dns64-prefix>`.
+On earlier versions or for maximum flexibility, DNS64 support is included in the :doc:`lua-scripting/index`.
 This allows for example to hand out custom IPv6 gateway ranges depending on the location of the requestor, enabling the use of NAT64 services close to the user.
 
 Apart from faking AAAA records, it is also possible to also generate the associated PTR records.
@@ -24,4 +25,6 @@ To setup DNS64, with both forward and reverse records, create the following Lua
 Where fe80::21b:77ff:0:0 is your "Pref64" translation prefix and the "ip6.arpa" string is the reversed form of this Pref64 address.
 Now ensure your script gets loaded by specifying it with :ref:`lua-dns-script=dns64.lua <setting-lua-dns-script>`.
 
+On our wiki, a user has kindly supplied `an example script with support for multiple prefixes <https://github.com/PowerDNS/pdns/wiki/DNS64-with-multiple-prefixes>`_.
+
 To enhance DNS64, see the :doc:`lua-scripting/index` documentation.
index f7b57e354f001cfab6aa029826878f0e39499eef..a27342fbc3d75008de90adcd6ab776d39ba5e7a6 100644 (file)
@@ -92,12 +92,12 @@ The PowerDNS Recursor ships with the DNSSEC Root key built-in.
 Configuring DNSSEC key material must be done in the :ref:`setting-lua-config-file`, using :func:`addTA`.
 This function takes 2 arguments: the node in the DNS-tree and the data of the corresponding DS record.
 
-To e.g. add a trust anchor for the root and powerdns.com, use the following config in the Lua file:
+To e.g. add a trust anchor for the root and example.com, use the following config in the Lua file:
 
 .. code:: Lua
 
     addTA('.', "63149 13 1 a59da3f5c1b97fcd5fa2b3b2b0ac91d38a60d33a") -- This is not an ICANN root
-    addTA('powerdns.com', "44030 8 2 D4C3D5552B8679FAEEBC317E5F048B614B2E5F607DC57F1553182D49 AB2179F7")
+    addTA('example.com', "44030 8 2 D4C3D5552B8679FAEEBC317E5F048B614B2E5F607DC57F1553182D49 AB2179F7")
 
 For PowerDNS Recursor 4.1.x and below, use the :func:`addDS` function instead.
 
index 7c3adb6d6752cdf10e74a4837830c926aceda596..5e00612c7c50f5e4d9cf4ac99501e17e75bf7ed7 100644 (file)
@@ -34,7 +34,7 @@ Configuring the Recursor
 The configuration file is called ``recursor.conf`` and is located in the ``SYSCONFDIR`` defined at compile-time.
 This is usually ``/etc/powerdns``, ``/etc/pdns``, ``/etc/pdns-recursor``, ``/usr/local/etc`` or similar.
 
-Run ``pdns_recursor --no-config --config | grep config-dir`` to find this location on you installation.
+Run ``pdns_recursor --config=default | grep config-dir`` to find this location on you installation.
 
 The PowerDNS Recursor listens on the local loopback interface by default, this can be changed with the :ref:`setting-local-address` setting.
 
index 54ed4b0f2258c21747a6d9b12461869f27eb1b81..aaf856ada563b4532e39c040534564907ee2f5ea 100644 (file)
@@ -16,7 +16,7 @@ Failure logging endpoint
 
     {
       "top-domains": 100,
-      "domains": ".*\.example\.com$",
+      "domains": ".*\\.example\\.com$"
     }
 
   :property int top-domains: Number of top resolved domains that are automatically monitored for failures.
@@ -36,7 +36,7 @@ Failure logging endpoint
 
     {
       "top-domains": 100,
-      "domains": ".*\.example\.com$",
+      "domains": ".*\\.example\\.com$",
       "log": [
         {
           "first_occurred": 1234567890,
@@ -49,9 +49,9 @@ Failure logging endpoint
              {
                "name": "ns1.example.net",
                "address": "192.0.2.53"
-             },
+             }
           ]
-        },
+        }
       ]
     }
 
index 3de3f069c8a86bff2aa1228655205844513e2038..bece530b2829ae830c321ce5ac8e559762f7da38 100644 (file)
@@ -28,7 +28,7 @@ be true:
 
 * ``forward-zones``, ``forward-zones-recurse`` and/or ``auth-zones``
   settings must be set (possibly to the empty string) in a
-  configuration file. These settings must not be overriden on the
+  configuration file. These settings must not be overridden on the
   command line. Setting these options on the command line will
   override what has been set in the dynamically generated
   configuration files.
index 1a476af49bd92d2066be7c9e0d26a52008ae5bf3..eed0c533a00d7f3b8deb6a917d7d341c3e4e4b2a 100644 (file)
@@ -64,7 +64,7 @@ If possible, supply the actual name of your domain and the IP address of your se
 I found a bug!
 ^^^^^^^^^^^^^^
 As much as we'd like to think we are perfect, bugs happen.
-If you have found a bug, please file a bug report on `GitHub <https://github.com/PowerDNS/pdns/issues/new>`_.
+If you have found a bug, please file a bug report on `GitHub bug report <https://github.com/PowerDNS/pdns/issues/new?template=bug_report.md>`_.
 Please fill in the template and we'll try our best to help you.
 
 I found a security issue!
@@ -74,4 +74,4 @@ Please report this in private, see the :ref:`securitypolicy`.
 I have a good idea for a feature!
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 We like to work on new things!
-You can file a feature request on `GitHub <https://github.com/PowerDNS/pdns/issues/new>`_.
+You can file a feature request on `GitHub feature request <https://github.com/PowerDNS/pdns/issues/new?template=feature_request.md>`_.
index bb35e44b24789cb53b6a637061d3577303573939..c2c44dd7fe9aa864407355d0c49f666767262b36 100644 (file)
@@ -89,7 +89,7 @@ While :func:`protobufServer` only exports the queries sent to the recursor from
   :param int reconnectWaitTime: How long to wait, in seconds, between two reconnection attempts
   :param bool asyncConnect: When set to false (default) the first connection to the server during startup will block up to ``timeout`` seconds, otherwise the connection is done in a separate thread, after the first message has been queued..
 
-Protobol Buffers Definition
+Protocol Buffers Definition
 ---------------------------
 
 The protocol buffers message types can be found in the `dnsmessage.proto <https://github.com/PowerDNS/pdns/blob/master/pdns/dnsmessage.proto>`_ file and is included here:
@@ -113,10 +113,10 @@ The recursor must have been built with configure ``--enable-dnstap`` to make thi
 
   Options:
 
-  * ``logQueries=true``: bool - log oputgoing queries
+  * ``logQueries=true``: bool - log outgoing queries
   * ``logResponses=true``: bool - log incoming responses
  
-  The follwing options apply to the settings of the framestream library. Refer to the documentation of that
+  The following options apply to the settings of the framestream library. Refer to the documentation of that
   library for the default values, exact description and allowable values for these options.
   For all these options, absence or a zero value has the effect of using the library-provided default value.
 
index 3b833eb2ec69309703d1c4b05aaa2335f2137824..73d7475642c5ff4aa337dead2ba986b5bc4e5bd5 100644 (file)
@@ -8,10 +8,25 @@ Response Policy Zone is an open standard developed by Paul Vixie (ISC and Farsig
 Frequently, Response Policy Zones get to be very large and change quickly, so it is customary to update them over IXFR.
 It allows the use of third-party feeds, and near real-time policy updates.
 
+Evaluation order
+----------------
+
 If multiple RPZs are loaded, they get consulted in the order they were
 defined in. It is however possible from Lua to make queries skip specific
 Response Policy Zones.
 
+The evaluation order of RPZ policies is not always straightforward. Before 4.4.0, the recursor first checked whether the source address of the client matched a "Client IP Address" filter
+in any RPZ zones, then if the qname matched a "QNAME" trigger. It would then start the regular resolution process and check whether any "NSDNAME" or "NSIP" rule was triggered, then after the resolution process was done check whether any of the final records matched a "Response IP Address" rule.
+It would stop as soon as a match was found and apply the requested decision immediately, unless the decision was a "passthru". In that last case it would resume the normal processing but would only evaluate the rules coming from a policy with a higher order than the one that matched.
+
+Since 4.4.0 the behaviour is a bit different, to better follow the RPZ specifications. The source address of the client is still checked first. Then the normal resolution process starts and the initial qname as well as any CNAME part of the chain starting from the qname is checked against "QNAME" rules. "NSDNAME" and "NSIP" rules are still checked during the remaining part of the process, and "Response IP Address" rules are applied to the final records in the end.
+This matches the precedence rules from the RPZ specifications that specify that "A policy rule match which occurs at an earlier stage of resolution is preferred to a policy rule match which occurs at a later stage".
+
+For performance and privacy reasons, the order of evaluation does not strictly follow the one mandated by the RPZ specifications. In particular matching on the client IP and qname is done first before any processing, NS IP and NS DNAME matching is done when a nameserver is about to be sent a query, and matching on response records is done then a stage of resolution is done.
+The RPZ specifications mention that a match on the response record from a higher order RPZ should take precedence on a qname match from a lower one. Doing so would require delaying evaluation of RPZ policies until the whole resolution process has been completed, which would mean that queries might have been sent to a malicious nameserver already, in addition to performance issues.
+
+Note that "RPZ rules do not apply to synthetic data generated by using RPZ rules. For example, if RPZ supplies a CNAME pointing to a walled garden, RPZ policies will not be used while following that CNAME. If RPZ supplies local data giving a particular A record, RPZ policies will not apply to that response IP address", as stated in section 6.1 of the RPZ specifications.
+
 Configuring RPZ
 ---------------
 An RPZ can be loaded from file or slaved from a master. To load from file, use for example:
@@ -106,6 +121,20 @@ policyName
 ^^^^^^^^^^
 The name logged as 'appliedPolicy' in :doc:`protobuf <protobuf>` messages when this policy is applied.
 
+tags
+^^^^
+.. versionadded:: 4.4.0
+
+List of tags as string, that will be added to the policy tags exported over protobuf when a policy of this zone matches.
+
+overridesGettag
+^^^^^^^^^^^^^^^
+.. versionadded:: 4.4.0
+
+`gettag_ffi` can set an answer to a query.
+By default an RPZ hit overrides this answer, unless this option is set to `false`.
+The default is `true`.
+
 zoneSizeHint
 ^^^^^^^^^^^^
 An indication of the number of expected entries in the zone, speeding up the loading of huge zones by reserving space in advance.
@@ -140,7 +169,7 @@ The default value of 0 means no restriction.
 localAddress
 ^^^^^^^^^^^^
 The source IP address to use when transferring the RPZ.
-When unset, :ref:`setting-query-local-address` and :ref:`setting-query-local-address6` are used.
+When unset, :ref:`setting-query-local-address` is used.
 
 axfrTimeout
 ^^^^^^^^^^^
index a36dd996c327ab319e3f73f57f094452ca83e770..0b1a4bbd4f8dcd76214ed87fdf74ee1bd51fa282 100644 (file)
@@ -24,7 +24,7 @@ To compare the address (so not the port) of two ComboAddresses, use :meth:`:equa
 To convert an address to human-friendly representation, use :meth:`:toString <ComboAddress:toString>` or :meth:`:toStringWithPort <ComboAddress:toStringWithPort()>`.
 To get only the port number, use :meth:`:getPort() <ComboAddress:getPort>`.
 
-.. function:: NewCA(address) -> ComboAddress
+.. function:: newCA(address) -> ComboAddress
 
   Creates a :class:`ComboAddress`.
 
index 9e63861efc72bb833a5b15d3eeb2edd036ffdee3..0014afe8ff8269439ef904b25d4bfc2bbcb10edd 100644 (file)
@@ -8,7 +8,7 @@ The DNSName object
 ------------------
 The PowerDNS Recursor's Lua engine has the notion of a :class:`DNSName`, an object that represents a name in the DNS.
 It is returned by several functions and has several functions to programmatically interact with it.
-:class:`DNSNames <DNSName>` can be compared agains each other using the :meth:`:equal <DNSName:equal>` function or the ``==`` operator.
+:class:`DNSNames <DNSName>` can be compared against each other using the :meth:`:equal <DNSName:equal>` function or the ``==`` operator.
 As names in the DNS are case-insensitive, ``www.powerdns.com`` is equal to ``Www.PowerDNS.cOM``.
 
 Creating a :class:`DNSName` is done with :func:`newDN()`.
@@ -50,8 +50,10 @@ A small example of the functionality of a :class:`DNSName` is shown below:
   .. method:: DNSName:equal(name) -> bool
 
     Returns true when both names are equal for the DNS, i.e case insensitive.
+    
+    To compare two ``DNSName`` objects, use ``==``.
 
-    :param DNSName name: The name to compare against.
+    :param DNSName string: The name to compare against.
 
   .. method:: DNSName:isPartOf(name) -> bool
 
index 704c44149df78d370a7901928b9512dba28ad177..550830c2c234aae72851c950c1c78f3148027c95 100644 (file)
@@ -62,9 +62,16 @@ The DNSQuestion object contains at least the following fields:
       Set by :ref:`policyName <rpz-policyName>` in the :func:`rpzFile` and :func:`rpzMaster` configuration items.
       It is advised to overwrite this when modifying the :attr:`DNSQuestion.appliedPolicy.policyKind`
 
-    .. attribute:: DNSQuestion.appliedPolicy.policyAction
+    .. attribute:: DNSQuestion.appliedPolicy.policyType
 
-        The action taken by the engine
+        The type of match for the policy.
+      -  ``pdns.policytypes.None``  the empty policy type
+      -  ``pdns.policytypes.QName`` a match on qname
+      -  ``pdns.policytypes.ClientIP`` a match on client IP
+      -  ``pdns.policytypes.ResponseIP`` a match on response IP
+      -  ``pdns.policytypes.NSDName`` a match on the name of a nameserver
+      -  ``pdns.policytypes.NSIP`` a match on the IP of a nameserver
 
     .. attribute:: DNSQuestion.appliedPolicy.policyCustom
 
@@ -85,6 +92,14 @@ The DNSQuestion object contains at least the following fields:
 
         The TTL in seconds for the ``pdns.policyactions.Custom`` response
 
+    .. attribute:: DNSQuestion.appliedPolicy.policyTrigger
+
+        The trigger (left-hand) part of the RPZ rule that was matched
+
+  .. attribute:: DNSQuestion.appliedPolicy.policyHit
+
+        The value that was matched. This is a string representing a name or an address.
+
   .. attribute:: DNSQuestion.wantsRPZ
 
       A boolean that indicates the use of the Policy Engine.
@@ -193,6 +208,12 @@ The DNSQuestion object contains at least the following fields:
 
       Returns the :class:`DNSHeader` of the query or nil.
 
+  .. method:: DNSQuestion:getProxyProtocolValues() -> {ProxyProtocolValue}
+
+    .. versionadded:: 4.4.0
+
+      Get the Proxy Protocol Type-Length Values if any, as a table of  :class:`ProxyProtocolValue` objects.
+
   .. method:: DNSQuestion:getRecords() -> {DNSRecord}
 
       Get a table of DNS Records in this DNS Question (or answer by now).
@@ -290,3 +311,20 @@ The EDNSOptionView Class
   .. method:: EDNSOptionView:getContent()
 
     Returns a NULL-safe string object of the first value of this EDNS option.
+
+The ProxyProtocolValue Class
+============================
+
+.. class:: ProxyProtocolValue
+
+  .. versionadded:: 4.4.0
+
+  An object that represents the value of a Proxy Protocol Type-Length Value
+
+  .. method:: ProxyProtocolValue:getContent() -> str
+
+    Returns a NULL-safe string object.
+
+  .. method:: ProxyProtocolValue:getType() -> int
+
+    Returns the type of this value.
index 7a5781c198b564aaa9da5bb4d284883002dd4b4a..dc5061ebbdde007e2a7561ca4b568bfe858d7c25 100644 (file)
@@ -1,16 +1,19 @@
 Intercepting queries with Lua
 =============================
+
 To get a quick start, we have supplied a sample script that showcases all functionality described below.
 Please find it `here <https://github.com/PowerDNS/pdns/blob/master/pdns/recursordist/contrib/powerdns-example-script.lua>`_.
 
 Queries can be intercepted in many places:
 
 -  before any packet parsing begins (:func:`ipfilter`)
+-  before the packet cache has been looked up (:func:`gettag` and its FFI counterpart, :func:`gettag_ffi`)
 -  before any filtering policy have been applied (:func:`prerpz`)
 -  before the resolving logic starts to work (:func:`preresolve`)
 -  after the resolving process failed to find a correct answer for a domain (:func:`nodata`, :func:`nxdomain`)
 -  after the whole process is done and an answer is ready for the client (:func:`postresolve`)
 -  before an outgoing query is made to an authoritative server (:func:`preoutquery`)
+-  after a filtering policy hit has occurred (:func:`policyEventFilter`)
 
 Writing Lua PowerDNS Recursor scripts
 -------------------------------------
@@ -53,13 +56,18 @@ Interception Functions
     :param DNSHeader dh: The DNS Header of the query.
 
 
-.. function:: gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) -> int
+.. function:: gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyprotocolvalues) -> multiple values
+              gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) -> int
               gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions) -> int
 
     .. versionchanged:: 4.1.0
 
       The ``tcp`` parameter was added.
 
+    .. versionchanged:: 4.4.0
+
+      The ``proxyprotocolvalues`` parameter was added.
+
     The ``gettag`` function is invoked when the Recursor attempts to discover in which packetcache an answer is available.
 
     This function must return an integer, which is the tag number of the packetcache.
@@ -69,10 +77,17 @@ Interception Functions
     .. versionadded:: 4.1.0
 
         It can also return a table whose keys and values are strings to fill the :attr:`DNSQuestion.data` table, as well as a ``requestorId`` value to fill the :attr:`DNSQuestion.requestorId` field and a ``deviceId`` value to fill the :attr:`DNSQuestion.deviceId` field.
+
     .. versionadded:: 4.3.0
 
-        Along the ``deviceId`` value that can be returned, it was addded a ``deviceName`` field to fill the :attr:`DNSQuestion.deviceName` field.
+        Along the ``deviceId`` value that can be returned, it was added a ``deviceName`` field to fill the :attr:`DNSQuestion.deviceName` field.
 
+    .. versionadded:: 4.4.0
+       A ``routingTag`` can be returned, which is used as an extra name to identify records in the record cache.
+       If a routing tag is set and a record would be stored with an ENDS subnetmask in the record cache, it will be
+       stored with the tag instead. New request using the same tag will be served by the record in the records cache,
+       avoiding querying authoritative servers.
     The tagged packetcache can e.g. be used to answer queries from cache that have e.g. been filtered for certain IPs (this logic should be implemented in :func:`gettag`).
     This ensure that queries are answered quickly compared to setting :attr:`dq.variable <DNSQuestion.variable>` to true.
     In the latter case, repeated queries will pass through the entire Lua script.
@@ -84,6 +99,21 @@ Interception Functions
     :param int qtype: The query type of the query
     :param ednsoptions: A table whose keys are EDNS option codes and values are :class:`EDNSOptionView` objects. This table is empty unless the :ref:`setting-gettag-needs-edns-options` option is set.
     :param bool tcp: Added in 4.1.0, a boolean indicating whether the query was received over UDP (false) or TCP (true).
+    :param proxyprotocolvalues: Added in 4.4.0, a table of :class:`ProxyProtocolValue` objects representing the Type-Length Values received via the Proxy Protocol, if any.
+
+    :return: ``tag`` [``, policyTags`` [``, data`` [``, reqId`` [``, deviceId`` [``, deviceName`` [``, routingTag`` ]]]]]]
+
+.. function:: gettag_ffi(param) -> optional Lua object
+
+    .. versionadded:: 4.1.2
+
+    .. versionchanged:: 4.3.0
+
+      The ability to craft answers was added.
+
+    This function is the FFI counterpart of the :func:`gettag` function, and offers the same functionality.
+    It accepts a single, scalable parameter which can be accessed using FFI accessors.
+    Like the non-FFI version, it has the ability to set a tag for the packetcache, policy tags, a routing tag, the :attr:`DNSQuestion.requestorId` and :attr:`DNSQuestion.deviceId`values and to fill the :attr:`DNSQuestion.data` table. It also offers ways to mark the answer as variable so it's not inserted into the packetcache, to set a cap on the TTL of the returned records, and to generate a response by adding records and setting the RCode. It can also instruct the recursor to do a proper resolution in order to follow any `CNAME` records added in this step.
 
 .. function:: prerpz(dq)
 
@@ -141,6 +171,43 @@ Interception Functions
 
   :param DNSQuestion dq: The DNS question to handle
 
+.. function:: policyEventFilter(event)
+
+    .. versionadded:: 4.4.0
+
+  This hook is called when a filtering policy has been hit, before the decision has been applied, making it possible to change a policy decision by altering its content or to skip it entirely.
+  Using the :meth:`event:discardPolicy() <PolicyEvent:discardPolicy>` function, it is also possible to selectively disable one or more filtering policy, for example RPZ zones.
+  The return value indicates whether the policy hit should be completely ignored (true) or applied (false), possibly after editing the action to take in that latter case (see :ref:`modifyingpolicydecisions` below). when true is returned, the resolution process will resume as if the policy hit never took place.
+
+  As an example, to ignore the result of a policy hit for the example.com domain:
+
+  .. code-block:: Lua
+
+      function policyEventFilter(event)
+        if event.qname:equal("example.com") then
+          -- ignore that policy hit
+          return true
+        end
+        return false
+      end
+
+  To alter the decision of the policy hit instead:
+
+  .. code-block:: Lua
+
+      function policyEventFilter(event)
+        if event.qname:equal("example.com") then
+          -- replace the decision with a custom CNAME
+          event.appliedPolicy.policyKind = pdns.policykinds.Custom
+          event.appliedPolicy.policyCustom = "example.net"
+          -- returning false so that the hit is not ignored
+          return false
+        end
+        return false
+      end
+
+  :param :class:`PolicyEvent` event: The event to handle
+
 Semantics
 ^^^^^^^^^
 The functions must return ``true`` if they have taken over the query and wish that the nameserver should not proceed with its regular query-processing.
@@ -151,6 +218,8 @@ An interesting rcode is NXDOMAIN (3, or ``pdns.NXDOMAIN``), which specifies the
 
 The :func:`ipfilter` and :func:`preoutquery` hooks are different, in that :func:`ipfilter` can only return a true of false value, and that :func:`preoutquery` can also set rcode -3 to signify that the whole query should be terminated.
 
+The func:`policyEventFilter` has a different meaning as well, where returning true means that the policy hit should be ignored and normal processing should be resumed.
+
 A minimal sample script:
 
 .. code-block:: Lua
@@ -261,7 +330,7 @@ The PowerDNS Recursor has a :doc:`policy engine based on Response Policy Zones (
 Starting with version 4.0.1 of the recursor, it is possible to alter this decision inside the Lua hooks.
 
 If the decision is modified in a Lua hook, ``false`` should be returned, as the query is not actually handled by Lua so the decision is picked up by the Recursor.
-The result of the policy decision is checked after :func:`preresolve` and :func:`postresolve`.
+The result of the policy decision is checked after :func:`preresolve` and :func:`postresolve` before 4.4.0. Beginning with version 4.4.0, the policy decision is checked after :func:`preresolve` and any :func:`policyEventFilter` call instead.
 
 For example, if a decision is set to ``pdns.policykinds.NODATA`` by the policy engine and is unchanged in :func:`preresolve`, the query is replied to with a NODATA response immediately after :func:`preresolve`.
 
index 45f0ddc9083ee86b0a049346ef163a299906c835..d03a9541771b65fc69c56408b4587ecfec68c059 100644 (file)
@@ -22,6 +22,7 @@ For extra performance, a Just In Time compiled version of Lua called `LuaJIT <ht
     dnsrecord
     comboaddress
     netmask
+    policyevent
     statistics
     logging
     hooks
index 57f311c26b5b2e7f7fd59dd26dcf3af215fa143b..b6c09f579b1d5cc6d4b1b298b6d716076f851665 100644 (file)
@@ -86,7 +86,7 @@ They can be matched against netmasks objects:
   nmg = newNMG()
   nmg:addMask("127.0.0.0/8")
   nmg:addMasks({"213.244.168.0/24", "130.161.0.0/16"})
-  nmg:addMasks(dofile("bad.ips")) -- contains return {"ip1","ip2"..}
+  nmg:addMasks(dofile("bad-ips.lua")) -- a lua script file that contains: return {"ip1","ip2"..}
 
   if nmg:match(dq.remoteaddr) then
     print("Intercepting query from ", dq.remoteaddr)
@@ -118,4 +118,4 @@ Prefixing a mask with ``!`` excludes that mask from matching.
 
       Returns true if ``address`` matches any of the masks in the group.
 
-      :param ComboAddress address: The IP addres to match the netmasks against.
+      :param ComboAddress address: The IP address to match the netmasks against.
diff --git a/pdns/recursordist/docs/lua-scripting/policyevent.rst b/pdns/recursordist/docs/lua-scripting/policyevent.rst
new file mode 100644 (file)
index 0000000..4a3de04
--- /dev/null
@@ -0,0 +1,85 @@
+.. _scripting-policyevent:
+
+Policy Events
+=============
+
+Since 4.4.0, the Lua hook :func:`policyEventFilter` is called along with a :class:`PolicyEvent` object whenever a filtering policy matches.
+
+PolicyEvent class
+------------------
+
+.. class:: PolicyEvent
+
+  Represents an event related to a filtering policy.
+
+  .. method:: PolicyEvent:addPolicyTag(tag)
+
+     Add policyTag ``tag`` to the list of policyTags.
+
+     :param str tag: The tag to add
+
+  .. method:: PolicyEvent:getPolicyTags() -> {str}
+
+      Get the current policy tags as a table of strings.
+
+  .. method:: PolicyEvent:setPolicyTags(tags)
+
+      Set the policy tags to ``tags``, overwriting any existing policy tags.
+
+      :param {str} tags: The policy tags
+
+  .. method:: PolicyEvent:discardPolicy(policyname)
+
+     Skip the filtering policy (for example RPZ) named ``policyname`` for this query.
+
+     :param str policyname: The name of the policy to ignore.
+
+  .. attribute:: PolicyEvent.appliedPolicy
+
+    The decision that was made by the policy engine, see :ref:`modifyingpolicydecisions`.
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyName
+
+      A string with the name of the policy.
+      Set by :ref:`policyName <rpz-policyName>` in the :func:`rpzFile` and :func:`rpzMaster` configuration items.
+      It is advised to overwrite this when modifying the :attr:`PolicyEvent.appliedPolicy.policyKind`
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyAction
+
+        The action taken by the engine
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyCustom
+
+        The CNAME content for the ``pdns.policyactions.Custom`` response, a string
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyKind
+
+      The kind of policy response, there are several policy kinds:
+
+      -  ``pdns.policykinds.Custom`` will return a NoError, CNAME answer with the value specified in :attr:`PolicyEvent.appliedPolicy.policyCustom`
+      -  ``pdns.policykinds.Drop`` will simply cause the query to be dropped
+      -  ``pdns.policykinds.NoAction`` will continue normal processing of the query
+      -  ``pdns.policykinds.NODATA`` will return a NoError response with no value in the answer section
+      -  ``pdns.policykinds.NXDOMAIN`` will return a response with a NXDomain rcode
+      -  ``pdns.policykinds.Truncate`` will return a NoError, no answer, truncated response over UDP. Normal processing will continue over TCP
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyTTL
+
+        The TTL in seconds for the ``pdns.policyactions.Custom`` response
+
+  .. attribute:: PolicyEvent.qname
+
+      :class:`DNSName` of the name the query is for.
+
+  .. attribute:: PolicyEvent.qtype
+
+      Type the query is for as an integer, can be compared against ``pdns.A``, ``pdns.AAAA``.
+
+  .. attribute:: PolicyEvent.isTcp
+
+      Whether the query was received over TCP.
+
+  .. attribute:: PolicyEvent.remote
+
+      :class:`ComboAddress` of the requestor.
+
index be117f450129d398bfe74cbad1ae92f3e3fab73e..505224e6576d3faca5c7bb141de5b76cb5134627 100644 (file)
@@ -76,6 +76,8 @@ at `<https://doc.powerdns.com/>`
     Load root hints from this *filename*
 --local-address=<address>
     Listen on *address*, separated by spaces or commas.
+    Addresses specified can include port numbers; any which do not
+    include port numbers will listen on *--local-port*.
 --local-port=<port>
     Listen on *port*.
 --log-common-errors
@@ -88,12 +90,8 @@ at `<https://doc.powerdns.com/>`
     Maximum number of simultaneous TCP clients.
 --max-tcp-per-client=<num>
     If set, maximum number of TCP sessions per client (IP address).
---query-local-address=<address>
+--query-local-address=<address[,address...]>
     Use *address* as Source IP address when sending queries.
---query-local-address6=<address>
-    Send out local IPv6 queries from *address*. Disabled by default,
-    which also disables outgoing IPv6 support. A useful setting is
-    '::0'.
 --quiet
     Suppress logging of questions and answers.
 --server-id=<text>
index a9935ee5cb8e00720d2045df64f894581095ef48..54bcd2b8a5c26981ede544cd5a8f982c87ac0852 100644 (file)
@@ -41,7 +41,7 @@ Options
 --socket-pid=<pid>    When running in SMP mode, pid of **pdns_recursor** to
                       control.
 --timeout=<num>       Number of seconds to wait for the remote PowerDNS
-                      Recursor to respond. Set to 0 for infinite.
+                      Recursor to respond.
 
 Commands
 --------
index e6aedde774bafcf7df86caeef83358244b6d3657..d65b7557f8b1bc1d64ef5cf29796262e87295f2f 100644 (file)
@@ -59,7 +59,10 @@ Should Carbon not be the preferred way of receiving metric, several other techni
 
 Using the Webserver
 ^^^^^^^^^^^^^^^^^^^
-The :doc:`API <http-api/index>` exposes a statistics endpoint at :http:get:`/api/v1/servers/:server_id/statistics`.
+The :doc:`API <http-api/index>` exposes a statistics endpoint at
+
+.. http:get:: /api/v1/servers/:server_id/statistics
+              
 This endpoint exports all statistics in a single JSON document.
 
 Using ``rec_control``
@@ -252,7 +255,8 @@ number of outgoing queries dropped because of   :ref:`setting-dont-query` settin
 qname-min-fallback-success
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. versionadded:: 4.3.0
-number of successful queries due to fallback mechanism within :ref:`qname-minimization` setting.
+
+number of successful queries due to fallback mechanism within :ref:`setting-qname-minimization` setting.
 
 ecs-queries
 ^^^^^^^^^^^
@@ -384,7 +388,7 @@ packets dropped because of (Lua) policy decision
 
 policy-result-noaction
 ^^^^^^^^^^^^^^^^^^^^^^
-packets that were not actioned upon by   the RPZ/filter engine
+packets that were not acted upon by   the RPZ/filter engine
 
 policy-result-drop
 ^^^^^^^^^^^^^^^^^^
@@ -406,6 +410,12 @@ policy-result-custom
 ^^^^^^^^^^^^^^^^^^^^
 packets that were sent a custom answer by   the RPZ/filter engine
 
+proxy-protocol-invalid
+^^^^^^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.4
+
+Invalid proxy-protocol headers received.
+
 qa-latency
 ^^^^^^^^^^
 shows the current latency average, in microseconds,   exponentially weighted over past 'latency-statistic-size' packets
@@ -426,6 +436,18 @@ rebalanced-queries
 
 number of queries balanced to a different worker thread because the first selected one was above the target load configured with 'distribution-load-factor'
 
+record-cache-acquired
+^^^^^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.4.0
+
+number of record cache lock acquisitions
+
+record-cache-contended
+^^^^^^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.4.0
+
+number of contented record cache lock acquisitions
+
 resource-limits
 ^^^^^^^^^^^^^^^
 counts number of queries that could not be   performed because of resource limits
index 88abc49a41046cd46c6bf8ba68e67f978adfecf0..bdcf4c7c59f57adb4abcd608d64f8b76f9998642 100644 (file)
@@ -3,13 +3,13 @@ Newly Observed Domain Tracking
 
 A common security technique for detecting domains that may be suspicious or be associated with bad actors such as hosting malware, phishing or botnet command and control, is to investigate domains that haven't been seen before, i.e. are newly observed.
 
-Deciding whether a domain is truly a new domain would involve deterministic methods, such as maintaining a database of all domains ever seen, and comparing all domain lookups against that database. Such a mechanism would not be scalable in a recursor, and so is best suited to offline analysis. However, determining candidate domains for such an offline service is a problem that can be solved in the recursor, given that sending all domain lookups to such an offline service would still be prohibitely costly, and given that the true number of newly observed domains is likely to be relatively small in a given time period.
+Deciding whether a domain is truly a new domain would involve deterministic methods, such as maintaining a database of all domains ever seen, and comparing all domain lookups against that database. Such a mechanism would not be scalable in a recursor, and so is best suited to offline analysis. However, determining candidate domains for such an offline service is a problem that can be solved in the recursor, given that sending all domain lookups to such an offline service would still be prohibitively costly, and given that the true number of newly observed domains is likely to be relatively small in a given time period.
 
 A simple method to determine a candidate domain would simply be to check if the domain was not in the recursor cache; indeed this is a method used by many security researchers. However, while that does produce a smaller list of candidate domains, cache misses are still relatively common, particularly in deployments where techniques such as EDNS client-subnet are used.
 
-Therefore, a feature has been developed for the recursor which uses probablistic data structures (specifically a Stable Bloom Filter (SBF): [http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf]). This recursor feature is named "Newly Observed Domain" or "NOD" for short.
+Therefore, a feature has been developed for the recursor which uses probabilistic data structures (specifically a Stable Bloom Filter (SBF): [http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf]). This recursor feature is named "Newly Observed Domain" or "NOD" for short.
 
-The use of a probablistic data structure means that the memory and CPU usage for the NOD feature is minimal, however it does mean that there can be false positives (a domain flagged as new when it is not), and false negatives (a domain that is new is not detected). The size of the SBF data structure can be tuned to reduce the FP/FN rate, although it is created with a default size (67108864 cells) that should provide a reasonably low FP/FN rate. To configure a different size use the ``new-domain-db-size`` setting to specify a higher or lower cell count. Each cell consumes 1-bit of RAM (per recursor thread) and 1-byte of disk space. 
+The use of a probabilistic data structure means that the memory and CPU usage for the NOD feature is minimal, however it does mean that there can be false positives (a domain flagged as new when it is not), and false negatives (a domain that is new is not detected). The size of the SBF data structure can be tuned to reduce the FP/FN rate, although it is created with a default size (67108864 cells) that should provide a reasonably low FP/FN rate. To configure a different size use the ``new-domain-db-size`` setting to specify a higher or lower cell count. Each cell consumes 1-bit of RAM (per recursor thread) and 1-byte of disk space. 
 
 NOD is disabled by default, and must be enabled through the use of the following setting in recursor.conf:
 
@@ -39,7 +39,7 @@ If both NOD and protobuf logging are enabled, then the ``newlyObservedDomain`` f
 Unique Domain Response
 ~~~~~~~~~~~~~~~~~~~~~~
 
-A similar feature to NOD is Unique Domain Response (UDR). This feature uses the same probablistic data structures as NOD to store information about unique responses for a given lookup domain. Determining if a particular response is unique for a given lookup domain is extremly useful for determining potential security issues such as:
+A similar feature to NOD is Unique Domain Response (UDR). This feature uses the same probabilistic data structures as NOD to store information about unique responses for a given lookup domain. Determining if a particular response is unique for a given lookup domain is extremely useful for determining potential security issues such as:
 
 * Fast-Flux Domain Names
 * Cache-Poisoning Attacks
index 775347a8b345dd58d7b0f1d04aa9051df26fcbdf..9324b5a33e50ad2810569f9e0537ad2849838b6c 100644 (file)
@@ -71,7 +71,7 @@ Whole subtrees can we wiped as well, to wipe all cache entries for 'example.com'
 
   When wiping cache entries, matching entries in *all* caches (packet cache, recursor cache, negative cache) are removed.
 
-When debugging resolving issues, it can be advantagious to have a dump of all the cache entries.
+When debugging resolving issues, it can be advantageous to have a dump of all the cache entries.
 :doc:`rec_control <manpages/rec_control.1>` can write the caches of all threads to a file::
 
   rec_control dump-cache /tmp/cache
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-01.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-01.rst
new file mode 100644 (file)
index 0000000..050436f
--- /dev/null
@@ -0,0 +1,33 @@
+PowerDNS Security Advisory 2020-01: Denial of Service
+=====================================================
+
+-  CVE: CVE-2020-10995
+-  Date: May 19th 2020
+-  Affects: PowerDNS Recursor from 4.1.0 up to and including 4.3.0
+-  Not affected: 4.1.16, 4.2.2, 4.3.1
+-  Severity: Medium
+-  Impact: Degraded Service
+-  Exploit: This problem can be triggered via a crafted reply
+-  Risk of system compromise: No
+-  Solution: Upgrade to a non-affected version
+-  Workaround: None
+
+An issue in the DNS protocol has been found that allow malicious
+parties to use recursive DNS services to attack third party
+authoritative name servers. The attack uses a crafted reply by an
+authoritative name server to amplify the resulting traffic between the
+recursive and other authoritative name servers.  Both types of service
+can suffer degraded performance as an effect.
+
+This issue has been assigned CVE-2020-10995.
+
+PowerDNS Recursor from 4.1.0 up to and including 4.3.0 is
+affected. PowerDNS Recursor 4.1.16, 4.2.2 and 4.3.1 contain a
+mitigation to limit the impact of this DNS protocol issue.
+
+Please note that at the time of writing, PowerDNS Recursor 4.0 and
+below are no longer supported, as described in
+https://doc.powerdns.com/recursor/appendices/EOL.html.
+
+We would like to thank Lior Shafir, Yehuda Afek and Anat Bremler-Barr
+for finding and subsequently reporting this issue!
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-02.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-02.rst
new file mode 100644 (file)
index 0000000..4c11151
--- /dev/null
@@ -0,0 +1,32 @@
+PowerDNS Security Advisory 2020-02: Insufficient validation of DNSSEC signatures
+================================================================================
+
+-  CVE: CVE-2020-12244
+-  Date: May 19th 2020
+-  Affects: PowerDNS Recursor from 4.1.0 up to and including 4.3.0
+-  Not affected: 4.3.1, 4.2.2, 4.1.16
+-  Severity: Medium
+-  Impact: Denial of existence spoofing
+-  Exploit: This problem can be triggered by an attacker in position
+   of man-in-the-middle
+-  Risk of system compromise: No
+-  Solution: Upgrade to a non-affected version
+-  Workaround: None
+
+An issue has been found in PowerDNS Recursor 4.1.0 through 4.3.0 where
+records in the answer section of a NXDOMAIN response lacking an SOA
+were not properly validated in SyncRes::processAnswer. This would
+allow an attacker in position of man-in-the-middle to send a NXDOMAIN
+answer for a name that does exist, bypassing DNSSEC validation.
+
+This issue has been assigned CVE-2020-12244.
+
+PowerDNS Recursor from 4.1.0 up to and including 4.3.0 is affected.
+
+Please note that at the time of writing, PowerDNS Authoritative 4.0 and
+below are no longer supported, as described in
+https://doc.powerdns.com/authoritative/appendices/EOL.html.
+
+We would like to thank Matt Nordhoff for finding and subsequently
+reporting this issue!
+
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-03.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-03.rst
new file mode 100644 (file)
index 0000000..db7e4e5
--- /dev/null
@@ -0,0 +1,38 @@
+PowerDNS Security Advisory 2020-03: Information disclosure
+==========================================================
+
+-  CVE: CVE-2020-10030
+-  Date: May 19th 2020
+-  Affects: PowerDNS Recursor from 4.1.0 up to and including 4.3.0
+-  Not affected: 4.3.1, 4.2.2, 4.1.16
+-  Severity: Low
+-  Impact: Information Disclosure, Denial of Service
+-  Exploit: This problem can be triggered via a crafted hostname
+-  Risk of system compromise: No
+-  Solution: Upgrade to a non-affected version
+-  Workaround: None
+
+An issue has been found in PowerDNS Recursor allowing an
+attacker with enough privileges to change the system's hostname to
+cause disclosure of uninitialized memory content via a stack-based
+out-of-bounds read.
+It only occurs on systems where gethostname() does not null-terminate
+the returned string if the hostname is larger than the supplied buffer.
+Linux systems are not affected because the buffer is always large enough.
+OpenBSD systems are not affected because the returned hostname is always
+null-terminated.
+Under some conditions this issue can lead to the writing of one null-byte
+out-of-bounds on the stack, causing a denial of service or possibly
+arbitrary code execution.
+
+This issue has been assigned CVE-2020-10030.
+
+PowerDNS Recursor from 4.1.0 up to and including 4.3.0 is affected.
+
+Please note that at the time of writing, PowerDNS Recursor 4.0 and
+below are no longer supported, as described in
+https://doc.powerdns.com/recursor/appendices/EOL.html.
+
+We would like to thank Valentei Sergey for finding and subsequently
+reporting this issue!
+
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-04.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2020-04.rst
new file mode 100644 (file)
index 0000000..5f99c01
--- /dev/null
@@ -0,0 +1,27 @@
+PowerDNS Security Advisory 2020-04: Access restriction bypass
+=============================================================
+
+-  CVE: CVE-2020-14196
+-  Date: July 1st 2020
+-  Affects: PowerDNS Recursor up to and including 4.3.1, 4.2.2 and 4.1.16
+-  Not affected: 4.3.2, 4.2.3, 4.1.17
+-  Severity: Low
+-  Impact: Access restriction bypass
+-  Exploit: This problem can be triggered by sending HTTP queries
+-  Risk of system compromise: No
+-  Solution: Upgrade to a non-affected version 
+-  Workaround: Disable the webserver, set a password or an API key.
+   Additionally, restrict the binding address using the
+   `webserver-address` setting to local addresses only and/or use a
+   firewall to disallow web requests from untrusted sources reaching the
+   webserver listening address.
+
+An issue has been found in PowerDNS Recursor where the ACL applied to
+the internal web server via `webserver-allow-from` is not properly
+enforced, allowing a remote attacker to send HTTP queries to the
+internal web server, bypassing the restriction.
+In the default configuration the API webserver is not enabled. Only
+installations using a non-default value for `webserver` and
+`webserver-address` are affected.
+
index cbebe9f2ef239d9580ae5c597d4d5f12b8eaf937..6520769d9bcbef608e3571f0c26d8274b2e0401c 100644 (file)
@@ -16,14 +16,18 @@ As an example:
 
 ``allow-from``
 --------------
--  IP ranges, separated by commas
--  Default: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
+-  IP addresses or netmasks, separated by commas
+-  Default: 127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10
 
 Netmasks (both IPv4 and IPv6) that are allowed to use the server.
 The default allows access only from :rfc:`1918` private IP addresses.
 Due to the aggressive nature of the internet these days, it is highly recommended to not open up the recursor for the entire internet.
 Questions from IP addresses not listed here are ignored and do not get an answer.
 
+When the Proxy Protocol is enabled (see `proxy-protocol-from`_), the recursor will check the address of the client IP advertised in the Proxy Protocol header instead of the one of the proxy.
+
+Note that specifying an IP address without a netmask uses an implicit netmask of /32 or /128.
+
 .. _setting-allow-from-file:
 
 ``allow-from-file``
@@ -119,7 +123,7 @@ Not recommended unless you have to tick an 'RFC 2181 compliant' box.
 --------------
 -  Comma separated list of 'zonename=filename' pairs
 
-Zones read from these files (in BIND format) are served authoritatively.
+Zones read from these files (in BIND format) are served authoritatively (but without the AA bit set in responses).
 DNSSEC is not supported. Example:
 
 .. code-block:: none
@@ -370,6 +374,20 @@ If `pdns-distributes-queries`_ is set, spawn this number of distributor threads
 handle incoming queries and distribute them to other threads based on a hash of the query, to maximize the cache hit
 ratio.
 
+.. _setting-dns64-prefix:
+
+``dns64-prefix``
+----------------
+.. versionadded:: 4.4.0
+
+-  Netmask, as a string
+-  Default: None
+
+Enable DNS64 (:rfc:`6147`) support using the supplied /96 IPv6 prefix. This will generate 'fake' AAAA records for names
+with only `A` records, as well as 'fake' PTR records to make sure that reverse lookup of DNS64-generated IPv6 addresses
+generate the right name.
+See :doc:`dns64` for more flexible but slower alternatives using Lua.
+
 .. _setting-dnssec:
 
 ``dnssec``
@@ -436,7 +454,7 @@ Queries to addresses for zones as configured in any of the settings `forward-zon
 .. versionadded:: 4.2.0
 
 -  Comma separated list of netmasks
--  Default: 0.0.0.0/0, ::, !127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10
+-  Default: 0.0.0.0/0, ::/0, !127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10
 
 List of requestor netmasks for which the requestor IP Address should be used as the :rfc:`EDNS Client Subnet <7871>` for outgoing queries. Outgoing queries for requestors that do not match this list will use the `ecs-scope-zero-address`_ instead.
 Valid incoming ECS values from `use-incoming-edns-subnet`_ are not replaced.
@@ -527,7 +545,7 @@ The IP address sent via EDNS Client Subnet to authoritative servers listed in
 `edns-subnet-whitelist`_ when `use-incoming-edns-subnet`_ is set and the query has
 an ECS source prefix-length set to 0.
 The default is to look for the first usable (not an ``any`` one) address in
-`query-local-address`_ then `query-local-address6`_. If no suitable address is
+`query-local-address`_ (starting with IPv4). If no suitable address is
 found, the recursor fallbacks to sending 127.0.0.1.
 
 .. _setting-edns-outgoing-bufsize:
@@ -699,14 +717,22 @@ Indication of how many queries will be averaged to get the average latency repor
 
 ``local-address``
 -----------------
--  IP addresses, comma separated
--  Default: 127.0.0.1
+-  IPv4/IPv6 Addresses, with optional port numbers, separated by commas or whitespace
+-  Default: ``0.0.0.0, ::``
 
-Local IPv4 or IPv6 addresses to bind to.
-Addresses can also contain port numbers, for IPv4 specify like this: ``192.0.2.4:5300``, for IPv6: ``[::1]:5300``.
+Local IP addresses to which we bind. Each address specified can
+include a port number; if no port is included then the
+:ref:`setting-local-port` port will be used for that address. If a
+port number is specified, it must be separated from the address with a
+':'; for an IPv6 address the address must be enclosed in square
+brackets.
 
-**Warning**: When binding to wildcard addresses, UNIX semantics mean that answers may not be sent from the address a query was received on.
-It is highly recommended to bind to explicit addresses.
+Examples::
+
+  local-address=127.0.0.1 ::1
+  local-address=0.0.0.0:5353
+  local-address=[::]:8053
+  local-address=127.0.0.1:53, [::1]:5353
 
 .. _setting-local-port:
 
@@ -861,7 +887,7 @@ Maximum number of seconds to cache an item in the DNS cache, no matter what the
 
     The minimum value of this setting is 15. i.e. setting this to lower than 15 will make this value 15.
 
-.. _setting max-concurrent-requests-per-tcp-connection:
+.. _setting-max-concurrent-requests-per-tcp-connection:
 
 ``max-concurrent-requests-per-tcp-connection``
 ----------------------------------------------
@@ -921,6 +947,26 @@ This is used to limit endlessly chasing CNAME redirections.
 If qname-minimization is enabled, the number will be forced to be 100
 at a minimum to allow for the extra queries qname-minimization generates when the cache is empty.
 
+.. _setting-max-ns-address-qperq:
+
+``max-ns-address-qperq``
+------------------------
+.. versionadded:: 4.1.16
+.. versionadded:: 4.2.2
+.. versionadded:: 4.3.1
+
+-  Integer
+-  Default: 10
+
+The maximum number of outgoing queries with empty replies for
+resolving nameserver names to addresses we allow during the resolution
+of a single client query. If IPv6 is enabled, an A and a AAAA query
+for a name counts as 1. If a zone publishes more than this number of
+NS records, the limit is further reduced for that zone by lowering
+it by the number of NS records found above the
+`max-ns-address-qperq`_ value. The limit wil not be reduced to a
+number lower than 5.
+
 .. _setting-max-negative-ttl:
 
 ``max-negative-ttl``
@@ -942,6 +988,8 @@ This setting, which defaults to 3600 seconds, puts a maximum on the amount of ti
 Total maximum number of internal recursion calls the server may use to answer a single query.
 0 means unlimited.
 The value of `stack-size`_ should be increased together with this one to prevent the stack from overflowing.
+If `qname-minimization`_ is enabled, the fallback code in case of a failing resolve is allowed an additional `max-recursion-depth/2`.
+
 
 .. versionchanged:: 4.1.0
 
@@ -1021,7 +1069,7 @@ Can be set at runtime using ``rec_control set-minimum-ttl 3600``.
 - Default: no (disabled)
 
 Whether to track newly observed domains, i.e. never seen before. This
-is a probablistic algorithm, using a stable bloom filter to store
+is a probabilistic algorithm, using a stable bloom filter to store
 records of previously seen domains. When enabled for the first time,
 all domains will appear to be newly observed, so the feature is best
 left enabled for e.g. a week or longer before using the results. Note
@@ -1210,10 +1258,10 @@ Maximum number of seconds to cache a 'server failure' answer in the packet cache
 -  Boolean
 -  Default: yes
 
-If set, PowerDNS will have only 1 thread listening on client sockets, and distribute work by itself over threads by using a hash of the query,
-maximizing the cache hit ratio. Starting with version 4.2.0, more than one distributing thread can be started using the `distributor-threads`_
-setting.
-Improves performance on Linux.
+If set, PowerDNS will use distinct threads to listen to client sockets and distribute that work to worker-threads using a hash of the query.
+This feature should maximize the cache hit ratio.
+To use more than one thread set `distributor-threads` in version 4.2.0 or newer.
+Enabling should improve performance for medium sized resolvers.
 
 .. _setting-protobuf-use-kernel-timestamp:
 
@@ -1226,6 +1274,31 @@ Improves performance on Linux.
 
 Whether to compute the latency of responses in protobuf messages using the timestamp set by the kernel when the query packet was received (when available), instead of computing it based on the moment we start processing the query.
 
+.. _setting-proxy-protocol-from:
+
+``proxy-protocol-from``
+-----------------------
+.. versionadded:: 4.4.0
+
+-  IP addresses or netmasks, separated by commas
+-  Default: empty
+
+Ranges that are required to send a Proxy Protocol version 2 header in front of UDP and TCP queries, to pass the original source and destination addresses and ports to the recursor, as well as custom values.
+Queries that are not prefixed with such a header will not be accepted from clients in these ranges. Queries prefixed by headers from clients that are not listed in these ranges will be dropped.
+
+Note that once a Proxy Protocol header has been received, the source address from the proxy header instead of the address of the proxy will be checked against the `allow-from`_ ACL, 
+
+.. _setting-proxy-protocol-maximum-size:
+
+``proxy-protocol-maximum-size``
+-------------------------------
+.. versionadded:: 4.4.0
+
+-  Integer
+-  Default: 512
+
+The maximum size, in bytes, of a Proxy Protocol payload (header, addresses and ports, and TLV values). Queries with a larger payload will be dropped.
+
 .. _setting-public-suffix-list-file:
 
 ``public-suffix-list-file``
@@ -1253,15 +1326,24 @@ described in :rfc:`7816`.
 
 ``query-local-address``
 -----------------------
--  IPv4 Address, comma separated
+.. versionchanged:: 4.4.0
+  IPv6 addresses can be set with this option as well.
+
+-  IP addresses, comma separated
 -  Default: 0.0.0.0
 
-Send out local queries from this address, or addresses, by adding multiple addresses, increased spoofing resilience is achieved.
+Send out local queries from this address, or addresses. By adding multiple
+addresses, increased spoofing resilience is achieved. When no address of a certain
+address family is configured, there are *no* queries sent with that address family.
+In the default configuration this means that IPv6 is not used for outgoing queries.
 
 .. _setting-query-local-address6:
 
 ``query-local-address6``
 ------------------------
+.. deprecated:: 4.4.0
+  Use :ref:`setting-query-local-address` for IPv4 and IPv6.
+
 -  IPv6 addresses, comma separated
 -  Default: unset
 
@@ -1277,6 +1359,20 @@ Disabled by default, which also disables outgoing IPv6 support.
 
 Don't log queries.
 
+.. _setting-record-cache-shards:
+
+``record-cache-shards``
+------------------------
+.. versionadded:: 4.4.0
+
+-  Integer
+-  Default: 1024
+
+Sets the number of shards in the record cache. If you have high
+contention as reported by
+``record-cache-contented/record-cache-acquired``, you can try to
+enlarge this value or run with fewer threads.
+
 .. _setting-reuseport:
 
 ``reuseport``
@@ -1284,9 +1380,9 @@ Don't log queries.
 -  Boolean
 -  Default: no
 
-If ``SO_REUSEPORT`` support is available, allows multiple processes to open a listening socket on the same port.
+If ``SO_REUSEPORT`` support is available, allows multiple threads and processes to open listening sockets for the same port.
 
-Since 4.1.0, when ``pdns-distributes-queries`` is set to false and ``reuseport`` is enabled, every thread will open a separate listening socket to let the kernel distribute the incoming queries, avoiding any thundering herd issue as well as the distributor thread being a bottleneck, thus leading to much higher performance on multi-core boxes.
+Since 4.1.0, when ``pdns-distributes-queries`` is set to false and ``reuseport`` is enabled, every worker-thread will open a separate listening socket to let the kernel distribute the incoming queries instead of running a distributor thread (which could otherwise be a bottleneck) and avoiding thundering herd issues, thus leading to much higher performance on multi-core boxes.
 
 .. _setting-rng:
 
@@ -1296,7 +1392,7 @@ Since 4.1.0, when ``pdns-distributes-queries`` is set to false and ``reuseport``
 - String
 - Default: auto
 
-Specify which random number generator to use. Permissible choises are
+Specify which random number generator to use. Permissible choices are
  - auto - choose automatically
  - sodium - Use libsodium ``randombytes_uniform``
  - openssl - Use libcrypto ``RAND_bytes``
@@ -1306,7 +1402,7 @@ Specify which random number generator to use. Permissible choises are
  - kiss - Use simple settable deterministic RNG. **FOR TESTING PURPOSES ONLY!**
 
 .. note::
-  Not all choises are available on all systems.
+  Not all choices are available on all systems.
 
 .. _setting-root-nx-trust:
 
@@ -1760,14 +1856,16 @@ IP address for the webserver to listen on.
 
 ``webserver-allow-from``
 ------------------------
--  IP addresses, comma separated
+-  IP addresses or netmasks, comma separated
 -  Default: 127.0.0.1,::1
 
 .. versionchanged:: 4.1.0
 
-    Default is now 127.0.0.1,::1, was 0.0.0.0,::/0 before.
+    Default is now 127.0.0.1,::1, was 0.0.0.0/0,::/0 before.
 
-These subnets are allowed to access the webserver.
+These IPs and subnets are allowed to access the webserver. Note that
+specifying an IP address without a netmask uses an implicit netmask
+of /32 or /128.
 
 .. _setting-webserver-loglevel:
 
@@ -1808,7 +1906,7 @@ When set to "detailed", all information about the request and response are logge
 The value between the hooks is a UUID that is generated for each request. This can be used to find all lines related to a single request.
 
 .. note::
-  The webserver logs these line on the NOTICE level. The :ref:`settings-loglevel` seting must be 5 or higher for these lines to end up in the log.
+  The webserver logs these line on the NOTICE level. The :ref:`setting-loglevel` seting must be 5 or higher for these lines to end up in the log.
 
 .. _setting-webserver-password:
 
@@ -1843,7 +1941,7 @@ If a PID file should be written to `socket-dir`_
 ------------------
 .. versionadded:: 4.2.0
 
--  IP ranges, separated by commas
+-  IP addresses or netmasks, separated by commas
 -  Default: empty
 
 .. note::
index ffe2f7a77535c6c61eba6b4b270d698ec9531fe1..7ea9588fe79b3b5331c1327890f9ce90e6295693 100644 (file)
@@ -37,11 +37,11 @@ Packages provided on `the PowerDNS Repository <https://repo.powerdns.com>`__ wil
 
 New settings
 ^^^^^^^^^^^^
-- The :ref:`allow-trust-anchor-query` setting has been added. This setting controls if negative trust anchors can be queried. The default is `no`.
-- The :ref:`max-concurrent-requests-per-tcp-connection` has been added. This setting controls how many requests are handled concurrently per incoming TCP connection. The default is 10.
-- The :ref:`max-generate-steps` setting has been added. This sets the maximum number of steps that will be performed when loading a BIND zone with the ``$GENERATE`` directive. The default is 0, which is unlimited.
-- The :ref:`nothing-below-nxdomain` setting has been added. This setting controls the way cached NXDOMAIN replies imply non-existence of a whole subtree. The default is `dnssec` which means that only DNSSEC validated NXDOMAINS results are used.
-- The :ref:`qname-minimization` setting has been added. This options controls if QName Minimization is used. The default is `yes`.
+- The :ref:`setting-allow-trust-anchor-query` setting has been added. This setting controls if negative trust anchors can be queried. The default is `no`.
+- The :ref:`setting-max-concurrent-requests-per-tcp-connection` has been added. This setting controls how many requests are handled concurrently per incoming TCP connection. The default is 10.
+- The :ref:`setting-max-generate-steps` setting has been added. This sets the maximum number of steps that will be performed when loading a BIND zone with the ``$GENERATE`` directive. The default is 0, which is unlimited.
+- The :ref:`setting-nothing-below-nxdomain` setting has been added. This setting controls the way cached NXDOMAIN replies imply non-existence of a whole subtree. The default is `dnssec` which means that only DNSSEC validated NXDOMAINS results are used.
+- The :ref:`setting-qname-minimization` setting has been added. This options controls if QName Minimization is used. The default is `yes`.
   
 4.1.x to 4.2.0
 --------------
index 2eaa61bc870c4af3aa6e846bb8ad7b74e5f25765..fa81ef2f121fcb4dc29d647a4745283444697463 100644 (file)
@@ -1,5 +1,5 @@
 --[[
-    Both Google and Bing offer ways to enforce the use of their 'safesearch' or 'strict' functionality
+    Google, Youtube, Bing and DuckDuckGo offer ways to enforce the use of their 'safesearch' or 'strict' functionality
     for some or all of your users. This script provides a 'handleSafeSearch' function that
     implements enforced safe search for Google and Bing.
 
 
     For Bing, only 'www.bing.com' is relevant.
 
+    For Youtube: https://support.google.com/a/answer/6214622?hl=en - option 1. Note that they offer both a very strict search, and a moderate. Usually, moderate is a good balance. If you want really strict, change the youtubedomains values to 11 instead of 1.
+
+    For DuckDuckGo: https://help.duckduckgo.com/duckduckgo-help-pages/features/safe-search/ (bottom)
+
     There is a comment below in preresolve where you could insert code to determine if a particular user should be filtered or not
 ]]--
 
@@ -22,24 +26,52 @@ do
     googledomains["ipv6"..v]=2       -- this too - change to 1 to get v4 instead of NXDOMAIN
 end
 
+youtubedomains={}
+youtubedomains['www.youtube.com'] = 1
+youtubedomains['m.youtube.com'] = 1
+youtubedomains['youtubei.googleapis.com'] = 1
+youtubedomains['youtube.googleapis.com'] = 1
+youtubedomains['www.youtube-nocookie.com'] = 1
     
 function handleSafeSearch(dq)
          local name = dq.qname:toStringNoDot():lower();
-         local status = googledomains[name]
-         if( status == 1) then
+         local statusg = googledomains[name]
+         local statusyt = youtubedomains[name]
+
+         if( statusg == 1) then
                  dq:addAnswer(pdns.CNAME, "forcesafesearch.google.com")
                  dq.rcode=0
                  dq.followupFunction="followCNAMERecords"
                  return true
-         elseif( status == 2) then
+
+         elseif( statusyt == 1) then
+                 dq:addAnswer(pdns.CNAME, "restrictmoderate.youtube.com")
+                 dq.rcode=0
+                 dq.followupFunction="followCNAMERecords"
+                 return true
+
+         elseif( statusyt == 11) then
+                 dq:addAnswer(pdns.CNAME, "restrict.youtube.com")
+                 dq.rcode=0
+                 dq.followupFunction="followCNAMERecords"
+                 return true
+
+         elseif( statusg == 2) then
                  dq.rcode=pdns.NXDOMAIN 
                  -- inserting actual SOA record is a nice touch but requires figuring out country code
                  return true
+
          elseif(name=="www.bing.com") then
                  dq:addAnswer(pdns.CNAME, "strict.bing.com")
                  dq.rcode=0
                  dq.followupFunction="followCNAMERecords"
                  return true
+
+         elseif(name=="duckduckgo.com" or name=="www.duckduckgo.com") then
+                 dq:addAnswer(pdns.CNAME, "safe.duckduckgo.com")
+                 dq.rcode=0
+                 dq.followupFunction="followCNAMERecords"
+                 return true
          end
 
          return false
diff --git a/pdns/recursordist/gss_context.cc b/pdns/recursordist/gss_context.cc
deleted file mode 120000 (symlink)
index 3ed3e71..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../gss_context.cc
\ No newline at end of file
diff --git a/pdns/recursordist/gss_context.hh b/pdns/recursordist/gss_context.hh
deleted file mode 120000 (symlink)
index 050b795..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../gss_context.hh
\ No newline at end of file
index a48107e761c633f0f8cb057635897ed6636c6b6e..a2e5036867ebc28814d4562a9262e52407d4b38c 100644 (file)
 #include "cachecleaner.hh"
 #include "utility.hh"
 
+NegCache::NegCache(size_t mapsCount) :
+  d_maps(mapsCount)
+{
+}
+
+NegCache::~NegCache()
+{
+  try {
+    typedef std::unique_ptr<lock> lock_t;
+    vector<lock_t> locks;
+    for (auto& map : d_maps) {
+      locks.push_back(lock_t(new lock(map)));
+    }
+  }
+  catch (...) {
+  }
+}
+
+size_t NegCache::size() const
+{
+  size_t count = 0;
+  for (const auto& map : d_maps) {
+    count += map.d_entriesCount;
+  }
+  return count;
+}
+
 /*!
  * Set ne to the NegCacheEntry for the last label in qname and return true if there
  * was one.
@@ -35,7 +62,7 @@
  * \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, const NegCacheEntry** ne)
+bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne)
 {
   // Never deny the root.
   if (qname.isRoot())
@@ -44,16 +71,20 @@ bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, c
   // 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) {
+  auto& map = getMap(lastLabel);
+  const lock l(map);
+
+  negcache_t::const_iterator ni = map.d_map.find(tie(lastLabel, qtnull));
+
+  while (ni != map.d_map.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<SequenceTag>(d_negcache, ni);
+    if (now.tv_sec < ni->d_ttd) {
+      ne = *ni;
+      moveCacheItemToBack<SequenceTag>(map.d_map, ni);
       return true;
     }
-    moveCacheItemToFront<SequenceTag>(d_negcache, ni);
+    moveCacheItemToFront<SequenceTag>(map.d_map, ni);
     ++ni;
   }
   return false;
@@ -68,9 +99,12 @@ bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, c
  * \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, const NegCacheEntry** ne, bool typeMustMatch)
+bool NegCache::get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch)
 {
-  const auto& idx = d_negcache.get<2>();
+  auto& map = getMap(qname);
+  const lock l(map);
+
+  const auto& idx = map.d_map.get<NegCacheEntry>();
   auto range = idx.equal_range(qname);
   auto ni = range.first;
 
@@ -78,16 +112,16 @@ bool NegCache::get(const DNSName& qname, const QType& qtype, const struct timeva
     // We have an entry
     if ((!typeMustMatch && ni->d_qtype.getCode() == 0) || ni->d_qtype == qtype) {
       // We match the QType or the whole name is denied
-      auto firstIndexIterator = d_negcache.project<0>(ni);
+      auto firstIndexIterator = map.d_map.project<CompositeKey>(ni);
 
-      if ((uint32_t)now.tv_sec < ni->d_ttd) {
+      if (now.tv_sec < ni->d_ttd) {
         // Not expired
-        *ne = &(*ni);
-        moveCacheItemToBack<SequenceTag>(d_negcache, firstIndexIterator);
+        ne = *ni;
+        moveCacheItemToBack<SequenceTag>(map.d_map, firstIndexIterator);
         return true;
       }
       // expired
-      moveCacheItemToFront<SequenceTag>(d_negcache, firstIndexIterator);
+      moveCacheItemToFront<SequenceTag>(map.d_map, firstIndexIterator);
     }
     ++ni;
   }
@@ -101,7 +135,12 @@ bool NegCache::get(const DNSName& qname, const QType& qtype, const struct timeva
  */
 void NegCache::add(const NegCacheEntry& ne)
 {
-  lruReplacingInsert<SequenceTag>(d_negcache, ne);
+  auto& map = getMap(ne.d_name);
+  const lock l(map);
+  bool inserted = lruReplacingInsert<SequenceTag>(map.d_map, ne);
+  if (inserted) {
+    map.d_entriesCount++;
+  }
 }
 
 /*!
@@ -111,9 +150,11 @@ void NegCache::add(const NegCacheEntry& ne)
  * \param qtype The type of the entry to replace
  * \param newState The new validation state
  */
-void NegCache::updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<uint32_t> capTTD)
+void NegCache::updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<time_t> capTTD)
 {
-  auto range = d_negcache.equal_range(tie(qname, qtype));
+  auto& map = getMap(qname);
+  const lock l(map);
+  auto range = map.d_map.equal_range(tie(qname, qtype));
 
   if (range.first != range.second) {
     range.first->d_validationState = newState;
@@ -128,9 +169,11 @@ void NegCache::updateValidationStatus(const DNSName& qname, const QType& qtype,
  *
  * \param qname The name of the entries to be counted
  */
-uint64_t NegCache::count(const DNSName& qname) const
+size_t NegCache::count(const DNSName& qname) const
 {
-  return d_negcache.count(tie(qname));
+  const auto& map = getMap(qname);
+  const lock l(map);
+  return map.d_map.count(tie(qname));
 }
 
 /*!
@@ -139,9 +182,11 @@ uint64_t NegCache::count(const DNSName& qname) const
  * \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
+size_t NegCache::count(const DNSName& qname, const QType qtype) const
 {
-  return d_negcache.count(tie(qname, qtype));
+  const auto& map = getMap(qname);
+  const lock l(map);
+  return map.d_map.count(tie(qname, qtype));
 }
 
 /*!
@@ -151,22 +196,32 @@ uint64_t NegCache::count(const DNSName& qname, const QType qtype) const
  * \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)
+size_t NegCache::wipe(const DNSName& name, bool subtree)
 {
-  uint64_t ret(0);
+  size_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++;
+    for (auto& m : d_maps) {
+      const lock l(m);
+      for (auto i = m.d_map.lower_bound(tie(name)); i != m.d_map.end();) {
+        if (!i->d_name.isPartOf(name))
+          break;
+        i = m.d_map.erase(i);
+        ret++;
+        m.d_entriesCount--;
+      }
     }
     return ret;
   }
 
-  ret = count(name);
-  auto range = d_negcache.equal_range(tie(name));
-  d_negcache.erase(range.first, range.second);
+  auto& map = getMap(name);
+  const lock l(map);
+  auto range = map.d_map.equal_range(tie(name));
+  auto i = range.first;
+  while (i != range.second) {
+    i = map.d_map.erase(i);
+    ret++;
+    map.d_entriesCount--;
+  }
   return ret;
 }
 
@@ -175,7 +230,11 @@ uint64_t NegCache::wipe(const DNSName& name, bool subtree)
  */
 void NegCache::clear()
 {
-  d_negcache.clear();
+  for (auto& m : d_maps) {
+    const lock l(m);
+    m.d_map.clear();
+    m.d_entriesCount = 0;
+  }
 }
 
 /*!
@@ -183,9 +242,10 @@ void NegCache::clear()
  *
  * \param maxEntries The maximum number of entries that may exist in the cache.
  */
-void NegCache::prune(unsigned int maxEntries)
+void NegCache::prune(size_t maxEntries)
 {
-  pruneCollection<SequenceTag>(*this, d_negcache, maxEntries, 200);
+  size_t cacheSize = size();
+  pruneMutexCollectionsVector<SequenceTag>(*this, d_maps, maxEntries, cacheSize);
 }
 
 /*!
@@ -193,21 +253,31 @@ void NegCache::prune(unsigned int maxEntries)
  *
  * \param fp A pointer to an open FILE object
  */
-uint64_t NegCache::dumpToFile(FILE* fp)
+size_t NegCache::dumpToFile(FILE* fp) const
 {
-  uint64_t ret(0);
+  size_t ret = 0;
   struct timeval now;
   Utility::gettimeofday(&now, nullptr);
 
-  negcache_sequence_t& sidx = d_negcache.get<SequenceTag>();
-  for (const NegCacheEntry& ne : sidx) {
-    ret++;
-    fprintf(fp, "%s %" PRId64 " IN %s VIA %s ; (%s)\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now.tv_sec), ne.d_qtype.getName().c_str(), ne.d_auth.toString().c_str(), vStates[ne.d_validationState]);
-    for (const auto& rec : ne.DNSSECRecords.records) {
-      fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s)\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now.tv_sec), DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStates[ne.d_validationState]);
-    }
-    for (const auto& sig : ne.DNSSECRecords.signatures) {
-      fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", ne.d_name.toString().c_str(), static_cast<int64_t>(ne.d_ttd - now.tv_sec), sig.d_content->getZoneRepresentation().c_str());
+  for (const auto& m : d_maps) {
+    const lock l(m);
+    auto& sidx = m.d_map.get<SequenceTag>();
+    for (const NegCacheEntry& ne : sidx) {
+      ret++;
+      int64_t ttl = ne.d_ttd - now.tv_sec;
+      fprintf(fp, "%s %" PRId64 " IN %s VIA %s ; (%s)\n", ne.d_name.toString().c_str(), ttl, ne.d_qtype.getName().c_str(), ne.d_auth.toString().c_str(), vStateToString(ne.d_validationState).c_str());
+      for (const auto& rec : ne.authoritySOA.records) {
+        fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+      }
+      for (const auto& sig : ne.authoritySOA.signatures) {
+        fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.d_content->getZoneRepresentation().c_str());
+      }
+      for (const auto& rec : ne.DNSSECRecords.records) {
+        fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.d_content->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+      }
+      for (const auto& sig : ne.DNSSECRecords.signatures) {
+        fprintf(fp, "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.d_content->getZoneRepresentation().c_str());
+      }
     }
   }
   return ret;
index 2dbabb0f4be514238399a9c1f73749dc15b1a902..1618d5af7293ebfc3d1418a579f2899c296b7458 100644 (file)
@@ -21,6 +21,8 @@
  */
 #pragma once
 
+#include <mutex>
+#include <vector>
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/hashed_index.hpp>
 #include "dnsparser.hh"
@@ -30,7 +32,7 @@
 
 using namespace ::boost::multi_index;
 
-/* FIXME should become part of the normal cache (I think) and shoudl become more like
+/* FIXME should become part of the normal cache (I think) and should become more like
  * struct {
  *   vector<DNSRecord> records;
  *   vector<DNSRecord> signatures;
@@ -47,36 +49,35 @@ typedef struct
 class NegCache : public boost::noncopyable
 {
 public:
+  NegCache(size_t mapsCount = 1024);
+  ~NegCache();
+
   struct NegCacheEntry
   {
-    DNSName d_name; // The denied name
-    QType d_qtype; // The denied type
-    DNSName d_auth; // The denying name (aka auth)
-    mutable 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
-    mutable vState d_validationState{Indeterminate};
-    uint32_t getTTD() const
+    DNSName d_name; // The denied name
+    DNSName d_auth; // The denying name (aka auth)
+    mutable time_t d_ttd; // Timestamp when this entry should die
+    mutable vState d_validationState{vState::Indeterminate};
+    QType d_qtype; // The denied type
+    time_t getTTD() const
     {
       return d_ttd;
     };
   };
 
   void add(const NegCacheEntry& ne);
-  void updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<uint32_t> capTTD);
-  bool get(const DNSName& qname, const QType& qtype, const struct timeval& now, const NegCacheEntry** ne, bool typeMustMatch = false);
-  bool getRootNXTrust(const DNSName& qname, const struct timeval& now, const 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 updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<time_t> capTTD);
+  bool get(const DNSName& qname, const QType& qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch = false);
+  bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne);
+  size_t count(const DNSName& qname) const;
+  size_t count(const DNSName& qname, const QType qtype) const;
+  void prune(size_t maxEntries);
   void clear();
-  uint64_t dumpToFile(FILE* fd);
-  uint64_t wipe(const DNSName& name, bool subtree = false);
-
-  uint64_t size()
-  {
-    return d_negcache.size();
-  };
+  size_t dumpToFile(FILE* fd) const;
+  size_t wipe(const DNSName& name, bool subtree = false);
+  size_t size() const;
 
   void preRemoval(const NegCacheEntry& entry)
   {
@@ -104,9 +105,48 @@ private:
         member<NegCacheEntry, DNSName, &NegCacheEntry::d_name>>>>
     negcache_t;
 
-  // Required for the cachecleaner
-  typedef negcache_t::nth_index<1>::type negcache_sequence_t;
+  struct MapCombo
+  {
+    MapCombo() {}
+    MapCombo(const MapCombo&) = delete;
+    MapCombo& operator=(const MapCombo&) = delete;
+    negcache_t d_map;
+    mutable std::mutex mutex;
+    std::atomic<uint64_t> d_entriesCount{0};
+    mutable uint64_t d_contended_count{0};
+    mutable uint64_t d_acquired_count{0};
+    void invalidate() {}
+  };
+
+  vector<MapCombo> d_maps;
+
+  MapCombo& getMap(const DNSName& qname)
+  {
+    return d_maps[qname.hash() % d_maps.size()];
+  }
+  const MapCombo& getMap(const DNSName& qname) const
+  {
+    return d_maps[qname.hash() % d_maps.size()];
+  }
+
+public:
+  struct lock
+  {
+    lock(const MapCombo& map) :
+      m(map.mutex)
+    {
+      if (!m.try_lock()) {
+        m.lock();
+        map.d_contended_count++;
+      }
+      map.d_acquired_count++;
+    }
+    ~lock()
+    {
+      m.unlock();
+    }
 
-  // Stores the negative cache entries
-  negcache_t d_negcache;
+  private:
+    std::mutex& m;
+  };
 };
index b17cb59ef3c74867687e6b7524c22fa951eac825..8707d567a16301a45e21fdcbba8305800a300cc4 100644 (file)
@@ -25,14 +25,19 @@ LockPersonality=true
 NoNewPrivileges=true
 PrivateDevices=true
 PrivateTmp=true
+# Setting PrivateUsers=true prevents us from opening our sockets
+ProtectClock=true
 ProtectControlGroups=true
 ProtectHome=true
+ProtectHostname=true
+ProtectKernelLogs=true
 ProtectKernelModules=true
 ProtectKernelTunables=true
 ProtectSystem=full
 RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
 RestrictNamespaces=true
 RestrictRealtime=true
+RestrictSUIDSGID=true
 SystemCallArchitectures=native
 SystemCallFilter=~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete
 
diff --git a/pdns/recursordist/proxy-protocol.cc b/pdns/recursordist/proxy-protocol.cc
new file mode 120000 (symlink)
index 0000000..ae6a943
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.cc
\ No newline at end of file
diff --git a/pdns/recursordist/proxy-protocol.hh b/pdns/recursordist/proxy-protocol.hh
new file mode 120000 (symlink)
index 0000000..bc45ee8
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.hh
\ No newline at end of file
diff --git a/pdns/recursordist/query-local-address.cc b/pdns/recursordist/query-local-address.cc
new file mode 120000 (symlink)
index 0000000..fd2ad3c
--- /dev/null
@@ -0,0 +1 @@
+../query-local-address.cc
\ No newline at end of file
diff --git a/pdns/recursordist/query-local-address.hh b/pdns/recursordist/query-local-address.hh
new file mode 120000 (symlink)
index 0000000..18e39c8
--- /dev/null
@@ -0,0 +1 @@
+../query-local-address.hh
\ No newline at end of file
index 413b758c0f194d2883f77c1f5c40bfe5148c8b35..4cc6b4a1258dfc61d60fc1b1ba32c5ce0b364f3b 100644 (file)
@@ -299,7 +299,7 @@ private:
         "Number of packets dropped because of (Lua) policy decision")},
     {"policy-result-noaction",
       MetricDefinition(PrometheusMetricType::counter,
-        "Number of packets that were not actioned upon by the RPZ/filter engine")},
+        "Number of packets that were not acted upon by the RPZ/filter engine")},
     {"policy-result-drop",
       MetricDefinition(PrometheusMetricType::counter,
         "Number of packets that were dropped by the RPZ/filter engine")},
diff --git a/pdns/recursordist/shuffle.cc b/pdns/recursordist/shuffle.cc
new file mode 120000 (symlink)
index 0000000..eaf754b
--- /dev/null
@@ -0,0 +1 @@
+../shuffle.cc
\ No newline at end of file
diff --git a/pdns/recursordist/shuffle.hh b/pdns/recursordist/shuffle.hh
new file mode 120000 (symlink)
index 0000000..90ed1c6
--- /dev/null
@@ -0,0 +1 @@
+../shuffle.hh
\ No newline at end of file
index f508925357061abc96320c7ffa32cb6a9553bfae..181f0981e1ca7c01ecc7c7b12aa11c3f8418d7c3 100644 (file)
@@ -17,7 +17,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
   std::string zoneName("Unit test policy 0");
   auto zone = std::make_shared<DNSFilterEngine::Zone>();
   zone->setName(zoneName);
-  BOOST_CHECK_EQUAL(*(zone->getName()), zoneName);
+  BOOST_CHECK_EQUAL(zone->getName(), zoneName);
   zone->setDomain(DNSName("powerdns.com."));
   BOOST_CHECK_EQUAL(zone->getDomain(), DNSName("powerdns.com."));
   zone->setSerial(42);
@@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
 
   {
     /* blocked qname */
-    auto matchingPolicy = dfe.getQueryPolicy(blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(blockedName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
     DNSFilterEngine::Policy zonePolicy;
@@ -130,19 +130,19 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     BOOST_CHECK(zonePolicy == matchingPolicy);
 
     /* but a subdomain should not be blocked (not a wildcard, and this is not suffix domain matching */
-    matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(zone->findExactQNamePolicy(DNSName("sub") + blockedName, zonePolicy) == false);
   }
 
   {
     /* blocked NS name via wildcard */
-    const auto matchingPolicy = dfe.getQueryPolicy(DNSName("sub.sub.wildcard-blocked."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(DNSName("sub.sub.wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
 
     /* looking for wildcard-blocked. should not match *.wildcard-blocked. */
-    const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(notMatchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
 
     /* a direct lookup would not match */
@@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
 
   {
     /* blocked client IP */
-    const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), clientIP, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getClientPolicy(clientIP, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
     DNSFilterEngine::Policy zonePolicy;
@@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
 
   {
     /* not blocked */
-    const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findClientPolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
@@ -246,14 +246,14 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_wildcard_with_enc)
 
   {
     const DNSName tstName("bcbsks.com.102.112.2o7.net.");
-    auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(tstName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
 
   {
     const DNSName tstName("2o7.net.");
-    auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(tstName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
   }
@@ -263,28 +263,28 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_wildcard_with_enc)
 
   {
     const DNSName tstName("112.2o7.net.");
-    auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(tstName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_WARN_MESSAGE(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None, m);
     BOOST_WARN_MESSAGE(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction, m);
   }
 
   {
     const DNSName tstName("102.112.2o7.net.");
-    auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(tstName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_WARN_MESSAGE(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None, m);
     BOOST_WARN_MESSAGE(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction, m);
   }
 
   {
     const DNSName tstName("com.112.2o7.net.");
-    auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(tstName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_WARN_MESSAGE(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None, m);
     BOOST_WARN_MESSAGE(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction, m);
   }
 
   {
     const DNSName tstName("wcmatch.2o7.net.");
-    auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    auto matchingPolicy = dfe.getQueryPolicy(tstName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
   }
@@ -317,7 +317,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
 
   {
     /* exact type does not exist, but we have a CNAME */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad1, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad1, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad1, QType::A);
@@ -332,7 +332,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
 
   {
     /* exact type exists */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad2, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
 
@@ -381,7 +381,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
 
   {
     /* exact type exists */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad2, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
 
@@ -417,6 +417,128 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
+{
+  DNSFilterEngine dfe;
+
+  std::string zoneName("Unit test policy local data using netmasks");
+  auto zone = std::make_shared<DNSFilterEngine::Zone>();
+  zone->setName(zoneName);
+
+  const DNSName name("foo.example.com");
+  const Netmask nm1("192.168.1.0/24");
+
+  zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+  zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.5")}));
+  zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1234")}));
+  BOOST_CHECK_EQUAL(zone->size(), 1U);
+
+  dfe.addZone(zone);
+
+  { // A query should match two records
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(DNSName(), QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 2U);
+    const auto& record1 = records.at(0);
+    BOOST_CHECK(record1.d_type == QType::A);
+    BOOST_CHECK(record1.d_class == QClass::IN);
+    auto content1 = std::dynamic_pointer_cast<ARecordContent>(record1.d_content);
+    BOOST_CHECK(content1 != nullptr);
+    BOOST_CHECK_EQUAL(content1->getCA().toString(), "1.2.3.4");
+
+    const auto& record2 = records.at(1);
+    BOOST_CHECK(record2.d_type == QType::A);
+    BOOST_CHECK(record2.d_class == QClass::IN);
+    auto content2 = std::dynamic_pointer_cast<ARecordContent>(record2.d_content);
+    BOOST_CHECK(content2 != nullptr);
+    BOOST_CHECK_EQUAL(content2->getCA().toString(), "1.2.3.5");
+  }
+
+  { // AAAA query should match 1 record
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(DNSName(), QType::AAAA);
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    const auto& record1 = records.at(0);
+    BOOST_CHECK(record1.d_type == QType::AAAA);
+    BOOST_CHECK(record1.d_class == QClass::IN);
+    auto content1 = std::dynamic_pointer_cast<AAAARecordContent>(record1.d_content);
+    BOOST_CHECK(content1 != nullptr);
+    BOOST_CHECK_EQUAL(content1->getCA().toString(), "::1234");
+  }
+
+  // Try to zap 1 nonexisting record
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.1.1.1")}));
+
+  // Zap a record using a wider netmask
+  zone->rmClientTrigger(Netmask("192.168.0.0/16"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+
+  // Zap a record using a narrow netmask
+  zone->rmClientTrigger(Netmask("192.168.1.1/32"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+
+  // Zap 1 existing record
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.5")}));
+
+  { // A query should match one record now
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(DNSName(), QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    const auto& record1 = records.at(0);
+    BOOST_CHECK(record1.d_type == QType::A);
+    BOOST_CHECK(record1.d_class == QClass::IN);
+    auto content1 = std::dynamic_pointer_cast<ARecordContent>(record1.d_content);
+    BOOST_CHECK(content1 != nullptr);
+    BOOST_CHECK_EQUAL(content1->getCA().toString(), "1.2.3.4");
+  }
+  { // AAAA query should still match one record
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(DNSName(), QType::AAAA);
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    const auto& record1 = records.at(0);
+    BOOST_CHECK(record1.d_type == QType::AAAA);
+    BOOST_CHECK(record1.d_class == QClass::IN);
+    auto content1 = std::dynamic_pointer_cast<AAAARecordContent>(record1.d_content);
+    BOOST_CHECK(content1 != nullptr);
+    BOOST_CHECK_EQUAL(content1->getCA().toString(), "::1234");
+  }
+
+  // Zap one more A record
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+
+  // Zap now nonexisting record
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "1.2.3.4")}));
+
+  { // AAAA query should still match one record
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(DNSName(), QType::AAAA);
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    const auto& record1 = records.at(0);
+    BOOST_CHECK(record1.d_type == QType::AAAA);
+    BOOST_CHECK(record1.d_class == QClass::IN);
+    auto content1 = std::dynamic_pointer_cast<AAAARecordContent>(record1.d_content);
+    BOOST_CHECK(content1 != nullptr);
+    BOOST_CHECK_EQUAL(content1->getCA().toString(), "::1234");
+  }
+
+  // Zap AAAA record
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1234")}));
+
+  { // there should be no match left
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.168.1.1"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+  }
+}
+
 BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
 {
   DNSFilterEngine dfe;
@@ -441,7 +563,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
 
   {
     /* zone 1 should match first */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -456,7 +578,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
 
   {
     /* zone 2 has an exact match for badUnderWildcard, but the wildcard from the first zone should match first */
-    const auto matchingPolicy = dfe.getQueryPolicy(badUnderWildcard, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(badUnderWildcard, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(badUnderWildcard, QType::A);
@@ -471,7 +593,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
 
   {
     /* zone 1 should still match if zone 2 has been disabled */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone2->getName()), true}}, DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, {{zone2->getName(), true}}, DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -486,7 +608,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
 
   {
     /* if zone 1 is disabled, zone 2 should match */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}}, DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, {{zone1->getName(), true}}, DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -501,7 +623,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
 
   {
     /* if both zones are disabled, we should not match */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}, {*(zone2->getName()), true}}, DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, {{zone1->getName(), true}, {zone2->getName(), true}}, DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
   }
 }
@@ -541,7 +663,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* client IP should match before qname */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.128"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.0.2.128"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -556,14 +678,14 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* client IP and qname should match, but zone 1 is disabled and zone2's priority is too high */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.128"), {{*(zone1->getName()), true}}, 1);
+    const auto matchingPolicy = dfe.getClientPolicy(ComboAddress("192.0.2.128"), {{zone1->getName(), true}}, 1);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
 
   {
     /* zone 1 should match first */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -578,7 +700,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* zone 1 should still match if we require a priority < 1 */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), 1);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, std::unordered_map<std::string, bool>(), 1);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -593,14 +715,14 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* nothing should match if we require a priority < 0 */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), 0);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, std::unordered_map<std::string, bool>(), 0);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
 
   {
     /* if we disable zone 1, zone 2 should match */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}}, DNSFilterEngine::maximumPriority);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, {{zone1->getName(), true}}, DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
     auto records = matchingPolicy.getCustomRecords(bad, QType::A);
@@ -615,7 +737,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* if we disable zone 1, zone 2 should match, except if we require a priority < 1 */
-    const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}}, 1);
+    const auto matchingPolicy = dfe.getQueryPolicy(bad, {{zone1->getName(), true}}, 1);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
@@ -637,7 +759,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* blocked NS name, except policy 1 is disabled and policy2's priority is too high */
-    auto matchingPolicy = dfe.getProcessingPolicy(nsName, {{*(zone1->getName()), true}}, 1);
+    auto matchingPolicy = dfe.getProcessingPolicy(nsName, {{zone1->getName(), true}}, 1);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
@@ -659,7 +781,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
 
   {
     /* blocked NS ip, except policy 1 is disabled and policy2's priority is too high */
-    auto matchingPolicy = dfe.getProcessingPolicy(nsIP, {{*(zone1->getName()), true}}, 1);
+    auto matchingPolicy = dfe.getProcessingPolicy(nsIP, {{zone1->getName(), true}}, 1);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
@@ -687,7 +809,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     DNSRecord dr;
     dr.d_type = QType::A;
     dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString());
-    const auto matchingPolicy = dfe.getPostPolicy({dr}, {{*(zone1->getName()), true}}, 1);
+    const auto matchingPolicy = dfe.getPostPolicy({dr}, {{zone1->getName(), true}}, 1);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
   }
index 5a96f2fd0ac6a536625204fe7d1f24862343749b..507ddfeb3b53ae39c1eba6108d5a48e9e684b290 100644 (file)
@@ -60,13 +60,13 @@ BOOST_AUTO_TEST_CASE(test_get_entry)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.get(qname, QType(1), now, &ne);
+  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_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_entry_exact_type)
@@ -85,11 +85,10 @@ BOOST_AUTO_TEST_CASE(test_get_entry_exact_type)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.get(qname, QType(1), now, &ne, true);
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.get(qname, QType(1), now, ne, true);
 
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne == nullptr);
 }
 
 BOOST_AUTO_TEST_CASE(test_get_NODATA_entry)
@@ -105,18 +104,17 @@ BOOST_AUTO_TEST_CASE(test_get_NODATA_entry)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.get(qname, QType(1), now, &ne);
+  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);
+  BOOST_CHECK_EQUAL(ne.d_name, qname);
+  BOOST_CHECK_EQUAL(ne.d_qtype.getName(), QType(1).getName());
+  BOOST_CHECK_EQUAL(ne.d_auth, auth);
 
-  const NegCache::NegCacheEntry* ne2 = nullptr;
-  ret = cache.get(qname, QType(16), now, &ne2);
+  NegCache::NegCacheEntry ne2;
+  ret = cache.get(qname, QType(16), now, ne2);
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne2 == nullptr);
 }
 
 BOOST_AUTO_TEST_CASE(test_getRootNXTrust_entry)
@@ -132,13 +130,13 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust_entry)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.getRootNXTrust(qname, now, &ne);
+  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_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)
@@ -155,13 +153,12 @@ BOOST_AUTO_TEST_CASE(test_add_and_get_expired_entry)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
+  NegCache::NegCacheEntry ne;
 
   now.tv_sec += 1000;
-  bool ret = cache.get(qname, QType(1), now, &ne);
+  bool ret = cache.get(qname, QType(1), now, ne);
 
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne == nullptr);
 }
 
 BOOST_AUTO_TEST_CASE(test_getRootNXTrust_expired_entry)
@@ -178,13 +175,12 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust_expired_entry)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
+  NegCache::NegCacheEntry ne;
 
   now.tv_sec += 1000;
-  bool ret = cache.getRootNXTrust(qname, now, &ne);
+  bool ret = cache.getRootNXTrust(qname, now, ne);
 
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne == nullptr);
 }
 
 BOOST_AUTO_TEST_CASE(test_add_updated_entry)
@@ -203,12 +199,12 @@ BOOST_AUTO_TEST_CASE(test_add_updated_entry)
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.get(qname, QType(1), now, &ne);
+  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_CHECK_EQUAL(ne.d_name, qname);
+  BOOST_CHECK_EQUAL(ne.d_auth, auth2);
 }
 
 BOOST_AUTO_TEST_CASE(test_getRootNXTrust)
@@ -225,12 +221,12 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust)
   cache.add(genNegCacheEntry(qname, auth, now));
   cache.add(genNegCacheEntry(qname2, auth2, now));
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.getRootNXTrust(qname, now, &ne);
+  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_CHECK_EQUAL(ne.d_name, qname2);
+  BOOST_CHECK_EQUAL(ne.d_auth, auth2);
 }
 
 BOOST_AUTO_TEST_CASE(test_getRootNXTrust_full_domain_only)
@@ -247,11 +243,10 @@ BOOST_AUTO_TEST_CASE(test_getRootNXTrust_full_domain_only)
   cache.add(genNegCacheEntry(qname, auth, now));
   cache.add(genNegCacheEntry(qname2, auth2, now, 1)); // Add the denial for COM|A
 
-  const NegCache::NegCacheEntry* ne = nullptr;
-  bool ret = cache.getRootNXTrust(qname, now, &ne);
+  NegCache::NegCacheEntry ne;
+  bool ret = cache.getRootNXTrust(qname, now, ne);
 
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne == nullptr);
 }
 
 BOOST_AUTO_TEST_CASE(test_prune)
@@ -301,11 +296,11 @@ BOOST_AUTO_TEST_CASE(test_prune_valid_entries)
   cache.prune(1);
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
-  const NegCache::NegCacheEntry* got = nullptr;
-  bool ret = cache.get(power2, QType(1), now, &got);
+  NegCache::NegCacheEntry got;
+  bool ret = cache.get(power2, QType(1), now, got);
   BOOST_REQUIRE(ret);
-  BOOST_CHECK_EQUAL(got->d_name, power2);
-  BOOST_CHECK_EQUAL(got->d_auth, auth);
+  BOOST_CHECK_EQUAL(got.d_name, power2);
+  BOOST_CHECK_EQUAL(got.d_auth, auth);
 
   /* insert power1 back */
   ne = genNegCacheEntry(power1, auth, now);
@@ -323,11 +318,11 @@ BOOST_AUTO_TEST_CASE(test_prune_valid_entries)
   cache.prune(1);
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
-  got = nullptr;
-  ret = cache.get(power2, QType(1), now, &got);
+  got = NegCache::NegCacheEntry();
+  ret = cache.get(power2, QType(1), now, got);
   BOOST_REQUIRE(ret);
-  BOOST_CHECK_EQUAL(got->d_name, power2);
-  BOOST_CHECK_EQUAL(got->d_auth, auth);
+  BOOST_CHECK_EQUAL(got.d_name, power2);
+  BOOST_CHECK_EQUAL(got.d_auth, auth);
 }
 
 BOOST_AUTO_TEST_CASE(test_wipe_single)
@@ -354,20 +349,18 @@ BOOST_AUTO_TEST_CASE(test_wipe_single)
   cache.wipe(auth);
   BOOST_CHECK_EQUAL(cache.size(), 400U);
 
-  const NegCache::NegCacheEntry* ne2 = nullptr;
-  bool ret = cache.get(auth, QType(1), now, &ne2);
+  NegCache::NegCacheEntry ne2;
+  bool ret = cache.get(auth, QType(1), now, ne2);
 
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne2 == nullptr);
 
   cache.wipe(DNSName("1.powerdns.com"));
   BOOST_CHECK_EQUAL(cache.size(), 399U);
 
-  const NegCache::NegCacheEntry* ne3 = nullptr;
-  ret = cache.get(auth, QType(1), now, &ne3);
+  NegCache::NegCacheEntry ne3;
+  ret = cache.get(auth, QType(1), now, ne3);
 
   BOOST_CHECK_EQUAL(ret, false);
-  BOOST_CHECK(ne3 == nullptr);
 }
 
 BOOST_AUTO_TEST_CASE(test_wipe_subtree)
@@ -421,14 +414,18 @@ BOOST_AUTO_TEST_CASE(test_clear)
 
 BOOST_AUTO_TEST_CASE(test_dumpToFile)
 {
-  NegCache cache;
+  NegCache cache(1);
   vector<string> expected;
   expected.push_back("www1.powerdns.com. 600 IN TYPE0 VIA powerdns.com. ; (Indeterminate)\n");
-  expected.push_back("www1.powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n");
-  expected.push_back("www1.powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
+  expected.push_back("powerdns.com. 600 IN SOA ns1. hostmaster. 1 2 3 4 5 ; (Indeterminate)\n");
+  expected.push_back("powerdns.com. 600 IN RRSIG SOA 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
+  expected.push_back("powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n");
+  expected.push_back("powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
   expected.push_back("www2.powerdns.com. 600 IN TYPE0 VIA powerdns.com. ; (Indeterminate)\n");
-  expected.push_back("www2.powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n");
-  expected.push_back("www2.powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
+  expected.push_back("powerdns.com. 600 IN SOA ns1. hostmaster. 1 2 3 4 5 ; (Indeterminate)\n");
+  expected.push_back("powerdns.com. 600 IN RRSIG SOA 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
+  expected.push_back("powerdns.com. 600 IN NSEC deadbeef. ; (Indeterminate)\n");
+  expected.push_back("powerdns.com. 600 IN RRSIG NSEC 5 3 600 20370101000000 20370101000000 24567 dummy. data ;\n");
 
   struct timeval now;
   Utility::gettimeofday(&now, 0);
@@ -436,19 +433,19 @@ BOOST_AUTO_TEST_CASE(test_dumpToFile)
   cache.add(genNegCacheEntry(DNSName("www1.powerdns.com"), DNSName("powerdns.com"), now));
   cache.add(genNegCacheEntry(DNSName("www2.powerdns.com"), DNSName("powerdns.com"), now));
 
-  FILE* fp = tmpfile();
+  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
   if (!fp)
     BOOST_FAIL("Temporary file could not be opened");
 
-  cache.dumpToFile(fp);
+  cache.dumpToFile(fp.get());
 
-  rewind(fp);
+  rewind(fp.get());
   char* line = nullptr;
   size_t len = 0;
   ssize_t read;
 
   for (const auto& str : expected) {
-    read = getline(&line, &len, fp);
+    read = getline(&line, &len, fp.get());
     if (read == -1)
       BOOST_FAIL("Unable to read a line from the temp file");
     BOOST_CHECK_EQUAL(line, str);
@@ -460,8 +457,6 @@ BOOST_AUTO_TEST_CASE(test_dumpToFile)
        last allocation if any. */
     free(line);
   }
-
-  fclose(fp);
 }
 
 BOOST_AUTO_TEST_CASE(test_count)
index 2f307b4d2a07b7317e98ed836c43e2cf668a807d..9748b55783d8d54947f88a4e2dd2788bb1a3a0de 100644 (file)
@@ -9,7 +9,7 @@ using namespace nod;
 
 BOOST_AUTO_TEST_SUITE(nod_cc)
 
-bool pdns_exception(PDNSException const& ex) { return true; }
+static bool pdns_exception(PDNSException const& ex) { return true; }
 
 BOOST_AUTO_TEST_CASE(test_basic)
 {
index 122159335984e0a8fac075b7b871290751404a50..ab18ae9d489cef886217cbdc73baf23a11e5a4f5 100644 (file)
@@ -20,6 +20,19 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
   std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
   time_t now = time(nullptr);
 
+  time_t ttd = now + 30;
+  DNSName power("powerdns.com.");
+  DNSRecord dr0;
+  ComboAddress dr0Content("2001:DB8::");
+  dr0.d_name = power;
+  dr0.d_type = QType::AAAA;
+  dr0.d_class = QClass::IN;
+  dr0.d_content = std::make_shared<AAAARecordContent>(dr0Content);
+  dr0.d_ttl = static_cast<uint32_t>(ttd);
+  dr0.d_place = DNSResourceRecord::ANSWER;
+
+  records.push_back(dr0);
+
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   MRC.replace(now, DNSName("hello"), QType(QType::A), records, signatures, authRecords, true, boost::none);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
@@ -56,18 +69,17 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     int64_t expected = counter - delcounter;
 
     for (; delcounter < counter; ++delcounter) {
-      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(delcounter)), QType(QType::A), false, &retrieved, who, nullptr)) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(delcounter)), QType(QType::A), false, &retrieved, who) > 0) {
         matches++;
+        BOOST_REQUIRE_EQUAL(retrieved.size(), records.size());
+        BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
       }
     }
     BOOST_CHECK_EQUAL(matches, expected);
-    BOOST_CHECK_EQUAL(retrieved.size(), records.size());
 
     MRC.doWipeCache(DNSName("."), true);
     BOOST_CHECK_EQUAL(MRC.size(), 0U);
 
-    time_t ttd = now + 30;
-    DNSName power("powerdns.com.");
     DNSRecord dr1;
     ComboAddress dr1Content("2001:DB8::1");
     dr1.d_name = power;
@@ -88,19 +100,18 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     dr2.d_place = DNSResourceRecord::AUTHORITY;
 
     // insert a subnet specific entry
+    records.clear();
     records.push_back(dr1);
     MRC.replace(now, power, QType(QType::AAAA), records, signatures, authRecords, true, boost::optional<Netmask>("192.0.2.1/25"));
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
-    retrieved.clear();
     // subnet specific should be returned for a matching subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("192.0.2.2"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
-    retrieved.clear();
     // subnet specific should not be returned for a different subnet
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
 
     // remove everything
@@ -114,10 +125,9 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
     // NON-subnet specific should always be returned
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
-    retrieved.clear();
 
     // insert a subnet specific entry for the same name but a different QType
     records.clear();
@@ -134,24 +144,21 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     BOOST_CHECK_EQUAL(MRC.size(), 3U);
 
     // we should still get the NON-subnet specific entry
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
-    retrieved.clear();
 
     // we should get the subnet specific entry if we are from the right subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("192.0.2.3"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
-    retrieved.clear();
 
     // but nothing from a different subnet
-    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), 0);
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), false, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
-    retrieved.clear();
 
     // QType::ANY should return any qtype, so from the right subnet we should get all of them
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), false, &retrieved, ComboAddress("192.0.2.3"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), false, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 3U);
     for (const auto& rec : retrieved) {
       BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::AAAA || rec.d_type == QType::TXT);
@@ -160,18 +167,16 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     for (const auto& rec : retrieved) {
       BOOST_CHECK(rec.d_place == DNSResourceRecord::ANSWER);
     }
-    retrieved.clear();
 
     // but only the non-subnet specific from the another subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 2U);
     for (const auto& rec : retrieved) {
       BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::TXT);
     }
-    retrieved.clear();
 
     // QType::ADDR should return both A and AAAA but no TXT, so two entries from the right subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), false, &retrieved, ComboAddress("192.0.2.3"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), false, &retrieved, ComboAddress("192.0.2.3")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 2U);
     bool gotA = false;
     bool gotAAAA = false;
@@ -186,35 +191,30 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     }
     BOOST_CHECK(gotA);
     BOOST_CHECK(gotAAAA);
-    retrieved.clear();
 
     // but only the non-subnet specific one from the another subnet
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK(retrieved.at(0).d_type == QType::A);
-    retrieved.clear();
 
     // entries are only valid until ttd, we should not get anything after that because they are expired
-    BOOST_CHECK_LT(MRC.get(ttd + 5, power, QType(QType::ADDR), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), 0);
+    BOOST_CHECK_LT(MRC.get(ttd + 5, power, QType(QType::ADDR), false, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
-    retrieved.clear();
 
     // let's age the records for our existing QType::TXT entry so they are now only valid for 5s
     uint32_t newTTL = 5;
     BOOST_CHECK_EQUAL(MRC.doAgeCache(now, power, QType::TXT, newTTL), true);
 
     // we should still be able to retrieve it
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::TXT), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), static_cast<int32_t>(newTTL));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::TXT), false, &retrieved, ComboAddress("127.0.0.1")), static_cast<int32_t>(newTTL));
     BOOST_CHECK_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK(retrieved.at(0).d_type == QType::TXT);
     // please note that this is still a TTD at this point
     BOOST_CHECK_EQUAL(retrieved.at(0).d_ttl, now + newTTL);
-    retrieved.clear();
 
     // but 10s later it should be gone
-    BOOST_CHECK_LT(MRC.get(now + 10, power, QType(QType::TXT), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), 0);
+    BOOST_CHECK_LT(MRC.get(now + 10, power, QType(QType::TXT), false, &retrieved, ComboAddress("127.0.0.1")), 0);
     BOOST_CHECK_EQUAL(retrieved.size(), 0U);
-    retrieved.clear();
 
     // wipe everything
     MRC.doWipeCache(DNSName("."), true);
@@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     records.push_back(dr2);
     MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, true, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_CHECK_EQUAL(retrieved.size(), 1U);
 
     DNSRecord dr3;
@@ -247,14 +247,14 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     // non-auth should not replace valid auth
     MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, false, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
     // but non-auth _should_ replace expired auth
     MRC.replace(ttd + 1, power, QType(QType::A), records, signatures, authRecords, false, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
-    BOOST_CHECK_EQUAL(MRC.get(ttd + 1, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (dr3.d_ttl - (ttd + 1)));
+    BOOST_CHECK_EQUAL(MRC.get(ttd + 1, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (dr3.d_ttl - (ttd + 1)));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr3Content.toString());
 
@@ -264,8 +264,8 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     MRC.replace(now, power, QType(QType::A), records, signatures, authRecords, false, boost::none);
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
     // let's first check that non-auth is not returned when we need authoritative data
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), true, &retrieved, ComboAddress("127.0.0.1"), nullptr), -now);
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), true, &retrieved, ComboAddress("127.0.0.1")), -now);
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
 
@@ -307,10 +307,9 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     BOOST_CHECK_EQUAL(MRC.size(), 3U);
 
     // we should get the most specific entry for 192.168.0.1, so the second one
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.168.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.168.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr4Content.toString());
-    retrieved.clear();
 
     // wipe everything
     MRC.doWipeCache(DNSName("."), true);
@@ -324,15 +323,13 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple)
     BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
     // we should not get it when we need authoritative data
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), true, &retrieved, ComboAddress("192.168.0.1"), nullptr), -1);
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), true, &retrieved, ComboAddress("192.168.0.1")), -1);
     BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
-    retrieved.clear();
 
     // but we should when we are OK with non-auth
-    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.168.0.1"), nullptr), (ttd - now));
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.168.0.1")), (ttd - now));
     BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
     BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
-    retrieved.clear();
   }
   catch (const PDNSException& e) {
     cerr << "Had error: " << e.reason << endl;
@@ -376,7 +373,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheGhost)
 
   /* the TTL should not have been raisd */
   std::vector<DNSRecord> retrieved;
-  BOOST_CHECK_EQUAL(MRC.get(now, ghost, QType(QType::NS), false, &retrieved, ComboAddress("192.0.2.2"), nullptr), (ttd - now));
+  BOOST_CHECK_EQUAL(MRC.get(now, ghost, QType(QType::NS), false, &retrieved, ComboAddress("192.0.2.2")), (ttd - now));
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(retrieved.at(0).d_ttl, static_cast<uint32_t>(ttd));
 }
@@ -433,11 +430,11 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
 
   /* the remaining entry should be power2, but to get it
      we need to go back in the past a bit */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), 1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), false, &retrieved, who, boost::none, nullptr), 1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
   /* check that power1 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), false, &retrieved, who, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), false, &retrieved, who, boost::none, nullptr), -1);
 
   /* clear everything up */
   MRC.doWipeCache(DNSName("."), true);
@@ -454,7 +451,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
   /* trigger a miss (expired) for power2 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), -now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, boost::none, nullptr), -now);
 
   /* power2 should have been moved to the front of the expunge
      queue, and should this time be removed first */
@@ -464,11 +461,11 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
 
   /* the remaining entry should be power1, but to get it
      we need to go back in the past a bit */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), false, &retrieved, who, nullptr), 1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power1, QType(dr1.d_type), false, &retrieved, who, boost::none, nullptr), 1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
   /* check that power2 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(ttd - 1, power2, QType(dr2.d_type), false, &retrieved, who, boost::none, nullptr), -1);
 }
 
 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
@@ -522,11 +519,11 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power2 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
   /* check that power1 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, boost::none, nullptr), -1);
 
   /* clear everything up */
   MRC.doWipeCache(DNSName("."), true);
@@ -556,11 +553,11 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power1 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
   /* check that power2 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, boost::none, nullptr), -1);
 
   /* clear everything up */
   MRC.doWipeCache(DNSName("."), true);
@@ -577,7 +574,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
   /* get a hit for power1 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
@@ -588,11 +585,11 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power1 */
-  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, nullptr), ttd - now);
+  BOOST_CHECK_EQUAL(MRC.get(now, power1, QType(dr1.d_type), false, &retrieved, who, boost::none, nullptr), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<AAAARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
   /* check that power2 is gone */
-  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, nullptr), -1);
+  BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), false, &retrieved, who, boost::none, nullptr), -1);
 
   MRC.doPrune(0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
@@ -626,7 +623,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   /* check that we can still retrieve the remaining ones */
   size_t found = 0;
   for (size_t i = 0; i <= 255; i++) {
-    retrieved.clear();
     ComboAddress whoLoop("192.0.2." + std::to_string(i));
 
     auto ret = MRC.get(now, power1, QType(QType::A), false, &retrieved, whoLoop);
@@ -685,7 +681,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
   /* no entry in the ECS index, no non-specific entry either */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), -1);
 
   /* insert a non-specific entry */
@@ -696,7 +691,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
   /* retrieve the non-specific entry */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
@@ -713,19 +707,16 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* there is an ECS index for that entry but no match, and no non-specific entry */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.4")), -1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
   /* there is an ECS index for that entry and we get a match */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
   /* there is an ECS index for that entry and we get a match,
      but it has expired. No other match, no non-specific entry */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now + ttl + 1, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), -1);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
 
@@ -738,7 +729,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   MRC.replace(now + ttl + 1, power, QType(QType::A), records, signatures, authRecords, true, Netmask("192.0.2.0/31"));
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
@@ -763,7 +753,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* check that we get the most specific one as long as it's still valid */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), 5);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
@@ -771,7 +760,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   /* there is an ECS index for that entry and we get a match,
      but it has expired.
      The second ECS is a match too, and is valid. */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now + 5 + 1, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.1")), (ttd - (now + 5 + 1)));
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
@@ -802,7 +790,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* there is an ECS index for that entry and it doesn't match. No other match, but we have a non-specific entry */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.255")), ttd - now);
   BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
@@ -843,7 +830,6 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
   /* no entry in the ECS index, no non-specific entry either */
-  retrieved.clear();
   BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, who), -1);
 
   /* insert a specific entry */
@@ -906,4 +892,236 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 }
 
+BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
+{
+  MemRecursorCache MRC;
+
+  std::vector<std::shared_ptr<DNSRecord>> authRecords;
+  std::vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+  time_t now = time(nullptr);
+  time_t ttd = now + 30;
+
+  DNSName power("powerdns.com.");
+  DNSRecord dr0;
+  ComboAddress dr0Content("192.0.2.40");
+  dr0.d_name = power;
+  dr0.d_type = QType::A;
+  dr0.d_class = QClass::IN;
+  dr0.d_content = std::make_shared<ARecordContent>(dr0Content);
+  dr0.d_ttl = static_cast<uint32_t>(ttd);
+  dr0.d_place = DNSResourceRecord::ANSWER;
+  std::vector<DNSRecord> rset0;
+  rset0.push_back(dr0);
+
+  DNSRecord dr0tagged;
+  ComboAddress dr0taggedContent("192.0.2.100");
+  dr0tagged.d_name = power;
+  dr0tagged.d_type = QType::A;
+  dr0tagged.d_class = QClass::IN;
+  dr0tagged.d_content = std::make_shared<ARecordContent>(dr0taggedContent);
+  dr0tagged.d_ttl = static_cast<uint32_t>(ttd);
+  dr0tagged.d_place = DNSResourceRecord::ANSWER;
+  std::vector<DNSRecord> rset0tagged;
+  rset0tagged.push_back(dr0tagged);
+
+  BOOST_CHECK_EQUAL(MRC.size(), 0U);
+  // An entry without edns subnet gets stored without tag as well
+  MRC.replace(ttd, DNSName("hello"), QType(QType::A), rset0, signatures, authRecords, true, boost::none, boost::none);
+  MRC.replace(ttd, DNSName("hello"), QType(QType::A), rset0, signatures, authRecords, true, boost::none, string("mytag"));
+  BOOST_CHECK_EQUAL(MRC.size(), 1U);
+  BOOST_CHECK_EQUAL(MRC.doWipeCache(DNSName("hello"), false, QType::A), 1U);
+  BOOST_CHECK_EQUAL(MRC.size(), 0U);
+  BOOST_CHECK_EQUAL(MRC.bytes(), 0U);
+
+  ComboAddress nobody;
+  ComboAddress who("192.0.2.1");
+
+  uint64_t counter = 0;
+  try {
+    for (counter = 0; counter < 100; ++counter) {
+      DNSName a = DNSName("hello ") + DNSName(std::to_string(counter));
+      BOOST_CHECK_EQUAL(DNSName(a.toString()), a);
+
+      MRC.replace(now, a, QType(QType::A), rset0, signatures, authRecords, true, boost::none, string("mytagA"));
+      MRC.replace(now, a, QType(QType::A), rset0, signatures, authRecords, true, boost::none, string("mytagB"));
+      // After this, we have untagged entries, since no address was specified for both replace calls
+    }
+
+    BOOST_CHECK_EQUAL(MRC.size(), counter);
+
+    std::vector<DNSRecord> retrieved;
+    int64_t matches = 0;
+    int64_t expected = counter;
+
+    for (counter = 0; counter < 110; counter++) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, boost::none) > 0) {
+        matches++;
+        BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
+        BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
+      }
+    }
+    BOOST_CHECK_EQUAL(matches, expected);
+
+    matches = 0;
+    for (counter = 0; counter < 110; ++counter) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, who, string("mytagB")) > 0) {
+        matches++;
+        BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
+        BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
+      }
+    }
+    BOOST_CHECK_EQUAL(matches, expected);
+
+    matches = 0;
+    for (counter = 0; counter < 110; counter++) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, who, string("mytagX")) > 0) {
+        matches++;
+        BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
+        BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
+      }
+    }
+    BOOST_CHECK_EQUAL(matches, expected);
+
+    // Now insert some tagged entries
+    for (counter = 0; counter < 50; ++counter) {
+      DNSName a = DNSName("hello ") + DNSName(std::to_string(counter));
+      MRC.replace(now, a, QType(QType::A), rset0tagged, signatures, authRecords, true, boost::optional<Netmask>("128.0.0.0/8"), string("mytagA"));
+    }
+    BOOST_CHECK_EQUAL(MRC.size(), 150U);
+
+    matches = 0;
+    for (counter = 0; counter < 110; counter++) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, boost::none) > 0) {
+        matches++;
+        BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
+        BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
+      }
+    }
+    BOOST_CHECK_EQUAL(matches, 100U);
+
+    matches = 0;
+    for (counter = 0; counter < 110; ++counter) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, string("mytagA")) > 0) {
+        matches++;
+        if (counter < 50) {
+          BOOST_CHECK_EQUAL(retrieved.size(), rset0tagged.size());
+          BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0taggedContent.toString());
+        }
+        else {
+          BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
+          BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
+        }
+      }
+    }
+    BOOST_CHECK_EQUAL(matches, 100U);
+
+    matches = 0;
+    for (counter = 0; counter < 110; counter++) {
+      if (MRC.get(now, DNSName("hello ") + DNSName(std::to_string(counter)), QType(QType::A), false, &retrieved, nobody, string("mytagX")) > 0) {
+        matches++;
+        BOOST_CHECK_EQUAL(retrieved.size(), rset0.size());
+        BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr0Content.toString());
+      }
+    }
+    BOOST_CHECK_EQUAL(matches, 100U);
+
+    MRC.doWipeCache(DNSName("."), true);
+    BOOST_CHECK_EQUAL(MRC.size(), 0U);
+
+    DNSRecord dr1;
+    ComboAddress dr1Content("192.0.2.41");
+    dr1.d_name = power;
+    dr1.d_type = QType::A;
+    dr1.d_class = QClass::IN;
+    dr1.d_content = std::make_shared<ARecordContent>(dr1Content);
+    dr1.d_ttl = static_cast<uint32_t>(ttd);
+    dr1.d_place = DNSResourceRecord::ANSWER;
+    std::vector<DNSRecord> rset1;
+    rset1.push_back(dr1);
+
+    DNSRecord dr2;
+    ComboAddress dr2Content("192.0.2.42");
+    dr2.d_name = power;
+    dr2.d_type = QType::A;
+    dr2.d_class = QClass::IN;
+    dr2.d_content = std::make_shared<ARecordContent>(dr2Content);
+    dr2.d_ttl = static_cast<uint32_t>(ttd);
+    dr2.d_place = DNSResourceRecord::ANSWER;
+    std::vector<DNSRecord> rset2;
+    rset2.push_back(dr2);
+
+    DNSRecord dr3;
+    ComboAddress dr3Content("192.0.2.43");
+    dr3.d_name = power;
+    dr3.d_type = QType::A;
+    dr3.d_class = QClass::IN;
+    dr3.d_content = std::make_shared<ARecordContent>(dr3Content);
+    dr3.d_ttl = static_cast<uint32_t>(ttd);
+    dr3.d_place = DNSResourceRecord::ANSWER;
+    std::vector<DNSRecord> rset3;
+    rset3.push_back(dr3);
+
+    // insert a tagged entry
+    MRC.replace(now, power, QType(QType::A), rset1, signatures, authRecords, true, boost::optional<Netmask>("192.0.2.0/24"), string("mytag"));
+    BOOST_CHECK_EQUAL(MRC.size(), 1U);
+
+    // tagged specific should be returned for a matching tag
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), string("mytag")), (ttd - now));
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
+    BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
+
+    // tag specific should not be returned for a different tag
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), string("othertag")), 0);
+    BOOST_CHECK_EQUAL(retrieved.size(), 0U);
+
+    // insert a new  entry without tag
+    MRC.replace(now, power, QType(QType::A), rset2, signatures, authRecords, true, boost::optional<Netmask>("192.0.3.0/24"), boost::none);
+    BOOST_CHECK_EQUAL(MRC.size(), 2U);
+
+    // tagged specific should be returned for a matching tag
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), string("mytag")), (ttd - now));
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
+    BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
+
+    // if no tag given nothing should be retrieved if address doesn't match
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("127.0.0.1"), boost::none), 0);
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
+
+    // if no tag given and no-non-tagged entries matches nothing should be returned
+    BOOST_CHECK_LT(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), boost::none), 0);
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 0U);
+
+    // Insert untagged entry with no netmask
+    MRC.replace(now, power, QType(QType::A), rset3, signatures, authRecords, true, boost::none, boost::none);
+    BOOST_CHECK_EQUAL(MRC.size(), 3U);
+
+    // Retrieval with no address and no tag should get that one
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress(), boost::none), (ttd - now));
+    BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr3Content.toString());
+
+    // If no tag given match non-tagged entry
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), boost::none), (ttd - now));
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
+    BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr3Content.toString());
+
+    // If no tag given we should be able to retrieve the netmask specific record
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.3.1"), boost::none), (ttd - now));
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
+    BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr2Content.toString());
+
+    // tagged specific should still be returned for a matching tag, address is not used
+    BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), false, &retrieved, ComboAddress("192.0.2.2"), string("mytag")), (ttd - now));
+    BOOST_REQUIRE_EQUAL(retrieved.size(), 1U);
+    BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
+
+    // remove everything
+    MRC.doWipeCache(DNSName("."), true);
+    BOOST_CHECK_EQUAL(MRC.size(), 0U);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 8769c6eb17ad52156155a29d1f20f0881dd606cc..8d4d70345361fe8a6d431b450ae185c867ea8aea 100644 (file)
@@ -5,16 +5,12 @@
 #endif
 
 #include "rpzloader.hh"
+#include "syncres.hh"
+
 #include <boost/test/unit_test.hpp>
 
 // Provide stubs for some symbols
 bool g_logRPZChanges{false};
-ComboAddress getQueryLocalAddress(int family, uint16_t port)
-{
-  cerr << "getQueryLocalAddress() STUBBED IN TEST!" << endl;
-  BOOST_ASSERT(false);
-  return ComboAddress();
-}
 
 BOOST_AUTO_TEST_SUITE(rpzloader_cc)
 
index 3fd5bd2f0d64150b87f70171ca410b8be7aa21d2..07660b08a8ced870f15673e404e3df4c3a2cf702 100644 (file)
 
 BOOST_AUTO_TEST_SUITE(test_secpoll_cc)
 
-bool checkBasicMessage1(const PDNSException& ex)
+static bool checkBasicMessage1(const PDNSException& ex)
 {
   BOOST_CHECK_EQUAL(ex.reason, "Had empty answer on NOERROR RCODE");
   return true;
 }
 
-bool checkBasicMessage2(const PDNSException& ex)
+static bool checkBasicMessage2(const PDNSException& ex)
 {
-  BOOST_CHECK_EQUAL(ex.reason, "RCODE was not NOERROR but " + RCode::to_s(1));
+  BOOST_CHECK_EQUAL(ex.reason, "RCODE was " + RCode::to_s(1));
   return true;
 }
 
-bool checkBasicMessage3(const PDNSException& ex)
+static bool checkBasicMessage3(const PDNSException& ex)
 {
   BOOST_CHECK_EQUAL(ex.reason, "No TXT record found in response");
   return true;
 }
 
-bool checkBasicMessage4(const PDNSException& ex)
+static bool checkBasicMessage4(const PDNSException& ex)
 {
   BOOST_CHECK(ex.reason.find("Could not parse status number: stoi") == 0);
   return true;
 }
 
-bool checkBasicMessage5(const PDNSException& ex)
+static bool checkBasicMessage5(const PDNSException& ex)
 {
   BOOST_CHECK(ex.reason.find("Could not parse status number: stoi") == 0);
   return true;
index dcdb0d48fb5a45365dfd66221afc45145898e5d3..54e5a9d9d073b9ecae39541bf9c1971b6699f15a 100644 (file)
@@ -10,7 +10,8 @@ RecursorStats g_stats;
 GlobalStateHolder<LuaConfigItems> g_luaconfs;
 GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
 GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
-std::unique_ptr<MemRecursorCache> s_RC{nullptr};
+std::unique_ptr<MemRecursorCache> g_recCache{nullptr};
+std::unique_ptr<NegCache> g_negCache{nullptr};
 unsigned int g_numThreads = 1;
 bool g_lowercaseOutgoing = false;
 
@@ -22,16 +23,16 @@ ArgvMap& arg()
   return theArg;
 }
 
-int getMTaskerTID()
+void primeRootNSZones(bool, unsigned int)
 {
-  return 0;
 }
 
-void primeRootNSZones(bool)
+bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret) const
 {
+  return false;
 }
 
-bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret) const
+bool RecursorLua4::policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string, bool>& dicardedPolicies) const
 {
   return false;
 }
@@ -49,11 +50,13 @@ int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool d
 
 #include "root-addresses.hh"
 
-void primeHints(void)
+bool primeHints(void)
 {
   vector<DNSRecord> nsset;
-  if (!s_RC)
-    s_RC = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
+  if (!g_recCache)
+    g_recCache = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
+  if (!g_negCache)
+    g_negCache = std::unique_ptr<NegCache>(new NegCache());
 
   DNSRecord arr, aaaarr, nsrr;
   nsrr.d_name = g_rootdnsname;
@@ -72,18 +75,19 @@ void primeHints(void)
     arr.d_content = std::make_shared<ARecordContent>(ComboAddress(rootIps4[c - 'a']));
     vector<DNSRecord> aset;
     aset.push_back(arr);
-    s_RC->replace(time(nullptr), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true); // auth, nuke it all
+    g_recCache->replace(time(nullptr), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), 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);
-      s_RC->replace(time(nullptr), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true);
+      g_recCache->replace(time(nullptr), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true);
     }
 
     nsset.push_back(nsrr);
   }
-  s_RC->replace(time(nullptr), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false); // and stuff in the cache
+  g_recCache->replace(time(nullptr), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false); // and stuff in the cache
+  return true;
 }
 
 LuaConfigItems::LuaConfigItems()
@@ -110,9 +114,11 @@ void initSR(bool debug)
     g_log.toConsole(Logger::Error);
   }
 
-  s_RC = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
+  g_recCache = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
+  g_negCache = std::unique_ptr<NegCache>(new NegCache());
 
   SyncRes::s_maxqperq = 50;
+  SyncRes::s_maxnsaddressqperq = 10;
   SyncRes::s_maxtotusec = 1000 * 7000;
   SyncRes::s_maxdepth = 40;
   SyncRes::s_maxnegttl = 3600;
@@ -122,6 +128,7 @@ void initSR(bool debug)
   SyncRes::s_packetcacheservfailttl = 60;
   SyncRes::s_serverdownmaxfails = 64;
   SyncRes::s_serverdownthrottletime = 60;
+  SyncRes::s_doIPv4 = true;
   SyncRes::s_doIPv6 = true;
   SyncRes::s_ecsipv4limit = 24;
   SyncRes::s_ecsipv6limit = 56;
@@ -195,7 +202,7 @@ void initSR(std::unique_ptr<SyncRes>& sr, bool dnssec, bool debug, time_t fakeNo
   sr->setLogMode(debug == false ? SyncRes::LogNone : SyncRes::Log);
 
   SyncRes::setDomainMap(std::make_shared<SyncRes::domainmap_t>());
-  SyncRes::clearNegCache();
+  g_negCache->clear();
 }
 
 void setDNSSECValidation(std::unique_ptr<SyncRes>& sr, const DNSSECMode& mode)
@@ -284,7 +291,7 @@ bool addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records, const
   const uint16_t type = records[recordsCount - 1].d_type;
 
   sortedRecords_t recordcontents;
-  for (const auto record : records) {
+  for (const auto& record : records) {
     if (record.d_name == name && record.d_type == type) {
       recordcontents.insert(record.d_content);
     }
index 2b909b242fdeea8662863ee5db727c275620eb73..06eca8a1e6a6ff3668e27fd97e4774534a27de7d 100644 (file)
 
 extern GlobalStateHolder<LuaConfigItems> g_luaconfs;
 
-ArgvMap& arg();
-int getMTaskerTID();
-
-void primeHints(void);
-
 void initSR(bool debug = false);
 void initSR(std::unique_ptr<SyncRes>& sr, bool dnssec = false, bool debug = false, time_t fakeNow = 0);
 
index e39176ac9102ac075db95dc128cc990988521196..49672a6bcebe1a3767813ebe9cdaf43b8273c00e 100644 (file)
@@ -25,7 +25,7 @@ BOOST_AUTO_TEST_CASE(test_root_primed)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::AAAA);
   BOOST_CHECK_EQUAL(ret[0].d_name, target);
@@ -1125,8 +1125,85 @@ BOOST_AUTO_TEST_CASE(test_cname_loop)
   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(), 0U);
+  BOOST_CHECK_EQUAL(ret.size(), 0U);
   BOOST_CHECK_EQUAL(count, 2U);
+
+  // Again to check cache
+  try {
+    res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch (const ImmediateServFailException& ex) {
+    BOOST_CHECK(true);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_long_loop)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  size_t count = 0;
+  const DNSName target1("cname1.powerdns.com.");
+  const DNSName target2("cname2.powerdns.com.");
+  const DNSName target3("cname3.powerdns.com.");
+  const DNSName target4("cname4.powerdns.com.");
+
+  sr->setAsyncCallback([target1, target2, target3, target4, &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, LWResult* res, bool* chained) {
+    count++;
+
+    if (isRootServer(ip)) {
+
+      setLWResult(res, 0, false, 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 == target1) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, target2.toString());
+        return 1;
+      }
+      else if (domain == target2) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, target3.toString());
+        return 1;
+      }
+      else if (domain == target3) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, target4.toString());
+        return 1;
+      }
+      else if (domain == target4) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, target1.toString());
+        return 1;
+      }
+
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0U);
+  BOOST_CHECK_EQUAL(count, 8U);
+
+  // And again to check cache
+  try {
+    res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch (const ImmediateServFailException& ex) {
+    BOOST_CHECK(true);
+  }
 }
 
 BOOST_AUTO_TEST_CASE(test_cname_depth)
@@ -1308,7 +1385,7 @@ BOOST_AUTO_TEST_CASE(test_dname_processing)
   BOOST_CHECK(ret[2].d_type == QType::A);
   BOOST_CHECK_EQUAL(ret[2].d_name, cnameTarget);
 
-  // Check if we correctly return a synthesizd CNAME, should send out just 1 more query
+  // Check if we correctly return a synthesized CNAME, should send out just 1 more query
   ret.clear();
   res = sr->beginResolve(uncachedTarget, QType(QType::A), QClass::IN, ret);
 
@@ -1455,7 +1532,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_secure)
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
 
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 5U); /* DNAME + RRSIG(DNAME) + CNAME + A + RRSIG(A) */
 
   BOOST_CHECK_EQUAL(queries, 11U);
@@ -1481,7 +1558,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_secure)
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
 
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 5U); /* DNAME + RRSIG(DNAME) + CNAME + A + RRSIG(A) */
 
   BOOST_CHECK_EQUAL(queries, 11U);
@@ -1602,7 +1679,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_insecure)
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
 
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U); /* DNAME + RRSIG(DNAME) + CNAME + A */
 
   BOOST_CHECK_EQUAL(queries, 9U);
@@ -1625,7 +1702,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_insecure)
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
 
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U); /* DNAME + RRSIG(DNAME) + CNAME + A */
 
   BOOST_CHECK_EQUAL(queries, 9U);
diff --git a/pdns/recursordist/test-syncres_cc10.cc b/pdns/recursordist/test-syncres_cc10.cc
new file mode 100644 (file)
index 0000000..a596a8f
--- /dev/null
@@ -0,0 +1,138 @@
+#define BOOST_TEST_DYN_LINK
+#include <boost/test/unit_test.hpp>
+
+#include "test-syncres_cc.hh"
+
+BOOST_AUTO_TEST_SUITE(syncres_cc10)
+BOOST_AUTO_TEST_CASE(test_outgoing_v4_only)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  SyncRes::s_doIPv6 = false;
+  primeHints();
+  bool v6Hit = false;
+  bool v4Hit = false;
+  int queries = 0;
+
+  const DNSName target("powerdns.com.");
+  sr->setAsyncCallback([target, &v4Hit, &v6Hit, &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, LWResult* res, bool* chained) {
+    queries++;
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+      v4Hit |= ip.isIPv4();
+      v6Hit |= ip.isIPv6();
+
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::AAAA, "2001:DB8:1::53", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "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, false);
+      v4Hit |= true;
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+      }
+      return 1;
+    }
+    else if (ip == ComboAddress("[2001:DB8:1::53]:53")) {
+      setLWResult(res, 0, true, false, false);
+      v6Hit |= true;
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+      }
+      return 1;
+    }
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int rcode;
+  rcode = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_REQUIRE_EQUAL(queries, 2);
+  BOOST_REQUIRE_EQUAL(v4Hit, true);
+  BOOST_REQUIRE_EQUAL(v6Hit, false);
+  BOOST_CHECK_EQUAL(rcode, RCode::NoError);
+  BOOST_CHECK_EQUAL(ret.size(), 1U);
+}
+
+BOOST_AUTO_TEST_CASE(test_outgoing_v4_only_no_A_in_delegation)
+{
+  // The name is not resolvable, as there's no A glue for an in-bailiwick NS
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  SyncRes::s_doIPv6 = false;
+  primeHints();
+  int queries = 0;
+
+  const DNSName target("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, LWResult* res, bool* chained) {
+    queries++;
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::AAAA, "2001:DB8:1::53", DNSResourceRecord::ADDITIONAL, 3600);
+      }
+      return 1;
+    }
+    else if (ip == ComboAddress("[2001:DB8:1::53]:53")) {
+      setLWResult(res, 0, true, false, false);
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+      }
+      return 1;
+    }
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int rcode;
+  rcode = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_REQUIRE_EQUAL(queries, 14); // We keep trying all parent nameservers, this is wrong!
+  BOOST_CHECK_EQUAL(rcode, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0U);
+}
+
+BOOST_AUTO_TEST_CASE(test_outgoing_v6_only_no_AAAA_in_delegation)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  SyncRes::s_doIPv4 = false;
+  SyncRes::s_doIPv6 = true;
+  primeHints();
+  int queries = 0;
+
+  const DNSName target("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, LWResult* res, bool* chained) {
+    queries++;
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
+        addRecordToLW(res, "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, false);
+      if (domain == DNSName("powerdns.com.")) {
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+      }
+      return 1;
+    }
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int rcode;
+  rcode = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_REQUIRE_EQUAL(queries, 14); // The recursor tries all parent nameservers... this needs to be fixed
+  BOOST_CHECK_EQUAL(rcode, RCode::ServFail);
+  BOOST_CHECK_EQUAL(ret.size(), 0U);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 5728011378ece3ca40cfae571968d68c14299962..d8c1c5c3201a816b8d178f9af0997cffd9a82ab3 100644 (file)
@@ -5,7 +5,7 @@
 
 BOOST_AUTO_TEST_SUITE(syncres_cc2)
 
-BOOST_AUTO_TEST_CASE(test_referral_depth)
+static void do_test_referral_depth(bool limited)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr);
@@ -35,36 +35,67 @@ BOOST_AUTO_TEST_CASE(test_referral_depth)
       }
       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);
+        addRecordToLW(res, "ns4.powerdns.org.", 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");
+      if (domain == DNSName("www.powerdns.com.")) {
+        addRecordToLW(res, domain, QType::A, "192.0.2.2");
+      }
+      else {
+        addRecordToLW(res, domain, QType::A, "192.0.2.1");
+      }
       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);
+  if (limited) {
+    /* Set the maximum depth low */
+    SyncRes::s_maxdepth = 4;
+    try {
+      vector<DNSRecord> ret;
+      sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+      BOOST_CHECK(false);
+    }
+    catch (const ImmediateServFailException& e) {
+      BOOST_CHECK(e.reason.find("max-recursion-depth") != string::npos);
+    }
   }
-  catch (const ImmediateServFailException& e) {
+  else {
+    // Check if the setup with high limit is OK.
+    SyncRes::s_maxdepth = 50;
+    try {
+      vector<DNSRecord> ret;
+      int rcode = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+      BOOST_CHECK_EQUAL(rcode, RCode::NoError);
+      BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+      BOOST_CHECK_EQUAL(ret[0].d_name, target);
+      BOOST_REQUIRE(ret[0].d_type == QType::A);
+      BOOST_CHECK(getRR<ARecordContent>(ret[0])->getCA() == ComboAddress("192.0.2.2"));
+    }
+    catch (const ImmediateServFailException& e) {
+      BOOST_CHECK(false);
+    }
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_referral_depth)
+{
+  // Test with limit
+  do_test_referral_depth(true);
+}
+
+BOOST_AUTO_TEST_CASE(test_referral_depth_ok)
+{
+  // Test with default limit
+  do_test_referral_depth(false);
+}
+
 BOOST_AUTO_TEST_CASE(test_cname_qperq)
 {
   std::unique_ptr<SyncRes> sr;
@@ -284,7 +315,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   /* one for target1 and one for the entire TLD */
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
@@ -292,7 +323,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust)
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_LE(ret[0].d_ttl, SyncRes::s_maxnegttl);
   /* one for target1 and one for the entire TLD */
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 
   /* we should have sent only one query */
   BOOST_CHECK_EQUAL(queriesCount, 1U);
@@ -349,7 +380,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust_specific)
 
   /* even with root-nx-trust on and a NX answer from the root,
      we should not have cached the entire TLD this time. */
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
@@ -359,7 +390,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust_specific)
   BOOST_REQUIRE(ret[0].d_type == QType::A);
   BOOST_CHECK(getRR<ARecordContent>(ret[0])->getCA() == ComboAddress("192.0.2.2"));
 
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   BOOST_CHECK_EQUAL(queriesCount, 3U);
 }
@@ -411,14 +442,14 @@ BOOST_AUTO_TEST_CASE(test_root_nx_dont_trust)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   /* one for target1 */
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   /* one for target1 */
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   /* we should have sent three queries */
   BOOST_CHECK_EQUAL(queriesCount, 3U);
@@ -461,28 +492,28 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target3, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target4, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   // Now test without RFC 8020 to see the cache and query count grow
   SyncRes::s_hardenNXD = SyncRes::HardenNXD::No;
@@ -493,7 +524,7 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   // New query
   ret.clear();
@@ -501,21 +532,21 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 3U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 
   ret.clear();
   res = sr->beginResolve(target3, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 3U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 3U);
 
   ret.clear();
   res = sr->beginResolve(target4, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 4U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 4U);
 
   // reset
   SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
@@ -624,34 +655,34 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath_dnssec)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target3, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target4, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   // Now test without RFC 8020 to see the cache and query count grow
   SyncRes::s_hardenNXD = SyncRes::HardenNXD::No;
@@ -660,35 +691,35 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath_dnssec)
   ret.clear();
   res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   // New query
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 
   ret.clear();
   res = sr->beginResolve(target3, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 13U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 3U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 3U);
 
   ret.clear();
   res = sr->beginResolve(target4, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 15U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 4U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 4U);
 
   // reset
   SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
@@ -744,28 +775,28 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nodata)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 3U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 
   ret.clear();
   res = sr->beginResolve(target3, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 }
 
 BOOST_AUTO_TEST_CASE(test_rfc8020_nodata_bis)
@@ -818,28 +849,28 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nodata_bis)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 3U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
 
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::TXT), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 
   ret.clear();
   res = sr->beginResolve(target3, QType(QType::TXT), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 2U);
 }
 
 BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response)
@@ -896,7 +927,7 @@ BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response)
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   /* no negative cache entry because the response was variable */
-  BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 0U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 0U);
 }
 
 BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_allowed)
@@ -934,7 +965,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_allowed)
   /* should have been cached */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -973,7 +1004,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_no_ttl_limit_allowed)
   /* should have been cached because /24 is more specific than /16 but TTL limit is nof effective */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -1012,7 +1043,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_allowed)
   /* should have been cached */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -1052,7 +1083,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_and_scope_allowed)
   /* should have been cached */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
 }
 
@@ -1092,7 +1123,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_notallowed)
   /* should have NOT been cached because TTL of 60 is too small and /24 is more specific than /16 */
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_LT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_LT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 0U);
 }
 
@@ -1200,7 +1231,7 @@ BOOST_AUTO_TEST_CASE(test_flawed_nsset)
   std::vector<shared_ptr<RRSIGRecordContent>> sigs;
   addRecordToList(records, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, now + 3600);
 
-  s_RC->replace(now, target, QType(QType::NS), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, boost::optional<Netmask>());
+  g_recCache->replace(now, target, QType(QType::NS), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, boost::optional<Netmask>());
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
@@ -1244,6 +1275,49 @@ BOOST_AUTO_TEST_CASE(test_completely_flawed_nsset)
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
 
+BOOST_AUTO_TEST_CASE(test_completely_flawed_big_nsset)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&queriesCount, 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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (isRootServer(ip) && domain == target) {
+      setLWResult(res, 0, false, false, true);
+      // 20 NS records
+      for (int i = 0; i < 20; i++) {
+        string n = string("pdns-public-ns") + std::to_string(i) + string(".powerdns.com.");
+        addRecordToLW(res, domain, QType::NS, n, DNSResourceRecord::AUTHORITY, 172800);
+      }
+      return 1;
+    }
+    else if (domain.toString().length() > 14 && domain.toString().substr(0, 14) == "pdns-public-ns") {
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+      return 1;
+    }
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  try {
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(0);
+  }
+  catch (const ImmediateServFailException& ex) {
+    BOOST_CHECK_EQUAL(ret.size(), 0U);
+    // one query to get NSs, then A and AAAA for each NS, 5th NS hits the limit
+    // limit is reduced to 5, because zone publishes many (20) NS
+    BOOST_CHECK_EQUAL(queriesCount, 11U);
+  }
+}
+
 BOOST_AUTO_TEST_CASE(test_cache_hit)
 {
   std::unique_ptr<SyncRes> sr;
@@ -1257,13 +1331,13 @@ BOOST_AUTO_TEST_CASE(test_cache_hit)
     return 0;
   });
 
-  /* we populate the cache with eveything we need */
+  /* we populate the cache with everything we need */
   time_t now = sr->getNow().tv_sec;
   std::vector<DNSRecord> records;
   std::vector<shared_ptr<RRSIGRecordContent>> sigs;
 
   addRecordToList(records, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, now + 3600);
-  s_RC->replace(now, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, boost::optional<Netmask>());
+  g_recCache->replace(now, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, boost::optional<Netmask>());
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
@@ -1336,13 +1410,13 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumTTL);
 
   cached.clear();
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::NS), false, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), false, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl);
@@ -1400,19 +1474,19 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ecs_ttl)
 
   const ComboAddress who("192.0.2.128");
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumECSTTL);
 
   cached.clear();
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::NS), false, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::NS), false, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl);
 
   cached.clear();
-  BOOST_REQUIRE_GT(s_RC->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_GT(cached[0].d_ttl, now);
   BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_minimumTTL);
@@ -1452,7 +1526,7 @@ BOOST_AUTO_TEST_CASE(test_cache_expired_ttl)
   std::vector<shared_ptr<RRSIGRecordContent>> sigs;
   addRecordToList(records, target, QType::A, "192.0.2.42", DNSResourceRecord::ANSWER, now - 60);
 
-  s_RC->replace(now - 3600, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, boost::optional<Netmask>());
+  g_recCache->replace(now - 3600, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, boost::optional<Netmask>());
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
index d0847bb4a3999eba4258db6cc61c8f1710fc7608..3299244d3dde76a156dcf185dcd3be32338df1ed 100644 (file)
@@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(test_cache_auth)
   /* check that we correctly cached only the answer entry, not the additional one */
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_REQUIRE_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_REQUIRE_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(QType(cached.at(0).d_type).getName(), QType(QType::A).getName());
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(cached.at(0))->getCA().toString(), ComboAddress("192.0.2.2").toString());
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(test_skip_opt_any)
   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, LWResult* res, bool* chained) {
     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::ANY, "\\# 0");
     addRecordToLW(res, domain, QType::OPT, "");
     return 1;
   });
@@ -317,7 +317,7 @@ BOOST_AUTO_TEST_CASE(test_answer_no_aa)
   const ComboAddress who;
   vector<DNSRecord> cached;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  BOOST_REQUIRE_EQUAL(s_RC->get(now, target, QType(QType::A), false, &cached, who, &signatures), -1);
+  BOOST_REQUIRE_EQUAL(g_recCache->get(now, target, QType(QType::A), false, &cached, who, boost::none, &signatures), -1);
 }
 
 BOOST_AUTO_TEST_CASE(test_special_types)
@@ -869,7 +869,7 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 
@@ -877,11 +877,116 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
 
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord_dnssec)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  /* signed */
+  const DNSName parent("test.");
+  const DNSName target1("a.test.");
+  const DNSName target2("b.test.");
+
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("test."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  const ComboAddress forwardedNS("192.0.2.42:53");
+  size_t queriesCount = 0;
+  size_t DSforParentCount = 0;
+
+  SyncRes::AuthDomain ad;
+  ad.d_rdForward = false;
+  ad.d_servers.push_back(forwardedNS);
+  (*SyncRes::t_sstorage.domainmap)[DNSName("test.")] = ad;
+
+  sr->setAsyncCallback([parent, target1, target2, keys, forwardedNS, &queriesCount, &DSforParentCount](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    BOOST_CHECK_EQUAL(sendRDQuery, false);
+
+    if (type == QType::DS && domain == parent) {
+      DSforParentCount++;
+    }
+    if (type == QType::DS || type == QType::DNSKEY) {
+      if (domain != parent && domain.isPartOf(parent)) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false /* no cut / delegation */);
+      }
+      else {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+      }
+    }
+
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, parent, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 42);
+      addRRSIG(keys, res->d_records, g_rootdnsname, 300);
+      addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+    }
+
+    if (ip != forwardedNS) {
+      return 0;
+    }
+
+    if (domain == target1 && type == QType::A) {
+
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, target1, QType::A, "192.0.2.1");
+      addRRSIG(keys, res->d_records, parent, 300);
+
+      return 1;
+    }
+    if (domain == target2 && type == QType::A) {
+
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, target2, QType::A, "192.0.2.2");
+      addRRSIG(keys, res->d_records, parent, 300);
+
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+  BOOST_CHECK_EQUAL(DSforParentCount, 1U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+  BOOST_CHECK_EQUAL(DSforParentCount, 1U);
+
+  /* new target should no cause a DS query for tets. */
+  ret.clear();
+  res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK_EQUAL(queriesCount, 7U);
+  BOOST_CHECK_EQUAL(DSforParentCount, 1U);
+}
+
 BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_bogus)
 {
   std::unique_ptr<SyncRes> sr;
@@ -940,7 +1045,7 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_bogus)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 
@@ -948,7 +1053,7 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_bogus)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
@@ -1003,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_nodata_bogus)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   /* com|NS, powerdns.com|NS, powerdns.com|A */
   BOOST_CHECK_EQUAL(queriesCount, 3U);
@@ -1012,7 +1117,7 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_nodata_bogus)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   /* we don't store empty results */
   BOOST_CHECK_EQUAL(queriesCount, 4U);
@@ -1054,7 +1159,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 0U);
   BOOST_CHECK(sr->wasOutOfBand());
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 
   /* a second time, to check that the OOB flag is set when the query cache is used */
   ret.clear();
@@ -1064,7 +1169,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 0U);
   BOOST_CHECK(sr->wasOutOfBand());
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 
   /* a third time, to check that the validation is disabled when the OOB flag is set */
   ret.clear();
@@ -1075,7 +1180,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 0U);
   BOOST_CHECK(sr->wasOutOfBand());
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 }
 
 BOOST_AUTO_TEST_CASE(test_auth_zone_oob_cname)
@@ -1123,7 +1228,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_cname)
   BOOST_CHECK(ret[1].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 0U);
   BOOST_CHECK(sr->wasOutOfBand());
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 
   /* a second time, to check that the OOB flag is set when the query cache is used */
   ret.clear();
@@ -1134,7 +1239,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_cname)
   BOOST_CHECK(ret[1].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 0U);
   BOOST_CHECK(sr->wasOutOfBand());
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 
   /* a third time, to check that the validation is disabled when the OOB flag is set */
   ret.clear();
@@ -1146,7 +1251,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_cname)
   BOOST_CHECK(ret[1].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 0U);
   BOOST_CHECK(sr->wasOutOfBand());
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 }
 
 BOOST_AUTO_TEST_CASE(test_auth_zone)
index f879409d4ab56668f5e7d2b0b35478a718c99d37..8199f318cc4df41df1a5c04c518bbb22ad485c83 100644 (file)
@@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_delegation)
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
 }
 
 BOOST_AUTO_TEST_CASE(test_auth_zone_delegation_point)
@@ -516,7 +516,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -525,7 +525,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
@@ -604,7 +604,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_ksk_zsk)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -613,7 +613,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_ksk_zsk)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
@@ -670,7 +670,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_dnskey)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -679,18 +679,171 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_dnskey)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
 
-BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_without_zone_flag)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
 
   setDNSSECValidation(sr, DNSSECMode::ValidateAll);
 
+  primeHints();
+  const DNSName target(".");
+  testkeysset_t keys;
+
+  /* Generate key material for "." */
+  auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256));
+  dcke->create(dcke->getBits());
+  DNSSECPrivateKey csk;
+  csk.d_flags = 0;
+  csk.setKey(dcke);
+  DSRecordContent ds = makeDSFromDNSKey(target, csk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+
+  keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(csk, ds);
+
+  /* Set the root DS */
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  luaconfsCopy.dsAnchors[g_rootdnsname].insert(ds);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (domain == target && type == QType::NS) {
+
+      setLWResult(res, 0, true, false, true);
+      char addr[] = "a.root-servers.net.";
+      for (char idx = 'a'; idx <= 'm'; idx++) {
+        addr[0] = idx;
+        addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+      }
+
+      addRRSIG(keys, res->d_records, domain, 300);
+      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;
+    }
+    else if (domain == target && type == QType::DNSKEY) {
+
+      setLWResult(res, 0, true, false, true);
+
+      addDNSKEY(keys, domain, 300, res->d_records);
+      addRRSIG(keys, res->d_records, domain, 300);
+
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
+  /* 13 NS + 1 RRSIG */
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_revoked)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target(".");
+  testkeysset_t keys;
+
+  /* Generate key material for "." */
+  auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256));
+  dcke->create(dcke->getBits());
+  DNSSECPrivateKey csk;
+  csk.d_flags = 257 | 128;
+  csk.setKey(dcke);
+  DSRecordContent ds = makeDSFromDNSKey(target, csk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+
+  keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(csk, ds);
+
+  /* Set the root DS */
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  luaconfsCopy.dsAnchors[g_rootdnsname].insert(ds);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (domain == target && type == QType::NS) {
+
+      setLWResult(res, 0, true, false, true);
+      char addr[] = "a.root-servers.net.";
+      for (char idx = 'a'; idx <= 'm'; idx++) {
+        addr[0] = idx;
+        addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+      }
+
+      addRRSIG(keys, res->d_records, domain, 300);
+      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;
+    }
+    else if (domain == target && type == QType::DNSKEY) {
+
+      setLWResult(res, 0, true, false, true);
+
+      addDNSKEY(keys, domain, 300, res->d_records);
+      addRRSIG(keys, res->d_records, domain, 300);
+
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
+  /* 13 NS + 1 RRSIG */
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+}
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::Process);
+
   primeHints();
   const DNSName target(".");
   testkeysset_t dskeys;
@@ -754,10 +907,12 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
     return 0;
   });
 
+  /* === with validation enabled === */
+  sr->setDNSSECValidationRequested(true);
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -766,9 +921,42 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  /* === first without validation, then with (just-in-time validation) === */
+  /* clear the caches */
+  g_recCache = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
+  g_negCache = std::unique_ptr<NegCache>(new NegCache());
+  sr->setDNSSECValidationRequested(false);
+  primeHints();
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
+  /* 13 NS + 1 RRSIG */
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 3U);
+
+  /* now we ask for the DNSKEYs (still without validation) */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::DNSKEY), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
+  /* 1 SOA + 1 RRSIG */
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
+
+  /* again, to test the cache WITH validation */
+  sr->setDNSSECValidationRequested(true);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
@@ -834,7 +1022,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -843,7 +1031,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
@@ -904,7 +1092,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* 13 NS + 0 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 13U);
   /* no RRSIG so no query for DNSKEYs */
@@ -914,7 +1102,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 13U);
   /* check that we capped the TTL to max-cache-bogus-ttl */
   for (const auto& record : ret) {
@@ -991,7 +1179,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_algorithm)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   /* no supported DS so no query for DNSKEYs */
@@ -1001,7 +1189,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_algorithm)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
@@ -1072,7 +1260,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_digest)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   /* no supported DS so no query for DNSKEYs */
@@ -1082,7 +1270,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_digest)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
@@ -1141,7 +1329,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_sig)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -1150,7 +1338,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_sig)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
@@ -1210,7 +1398,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
@@ -1219,7 +1407,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
@@ -1285,7 +1473,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 3U);
 
@@ -1293,7 +1481,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 3U);
 
@@ -1301,7 +1489,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds)
   ret.clear();
   res = sr->beginResolve(DNSName("com."), QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 3U);
 }
@@ -1359,7 +1547,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds_direct)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(DNSName("com."), QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
index 48b566d21b26e8f06814c21aefe842c0da673653..29f94e23c2e01495d8c6ea23c939c5415f9244a4 100644 (file)
@@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
@@ -189,7 +189,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -197,7 +197,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -206,7 +206,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 }
@@ -290,7 +290,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 
@@ -298,7 +298,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 
@@ -307,7 +307,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
@@ -399,7 +399,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta)
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   /* Should be insecure because of the NTA */
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 
@@ -408,7 +408,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta)
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   /* Should be insecure because of the NTA */
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
@@ -488,7 +488,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta)
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   /* Should be insecure because of the NTA */
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 
@@ -496,7 +496,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
@@ -579,7 +579,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -587,7 +587,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
@@ -695,7 +695,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 
@@ -703,7 +703,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 }
@@ -804,7 +804,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 
@@ -812,7 +812,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 }
@@ -884,7 +884,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
 
@@ -892,7 +892,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
 }
@@ -975,7 +975,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 8U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
 
@@ -983,7 +983,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 8U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
 }
@@ -1068,7 +1068,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_n
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* because we pass along the duplicated NSEC3 */
   BOOST_REQUIRE_EQUAL(ret.size(), 9U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
@@ -1077,7 +1077,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_n
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* because we pass along the duplicated NSEC3 */
   BOOST_REQUIRE_EQUAL(ret.size(), 9U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
@@ -1162,7 +1162,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_too_many_ite
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 8U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
 
@@ -1170,7 +1170,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_too_many_ite
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 8U);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
 }
@@ -1280,7 +1280,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 10U);
 
@@ -1288,7 +1288,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 10U);
 }
@@ -1395,7 +1395,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 
@@ -1403,7 +1403,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 }
@@ -1501,7 +1501,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 
@@ -1509,7 +1509,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
 }
@@ -1564,7 +1564,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* A + RRSIG, NSEC + RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
 }
@@ -1626,7 +1626,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_like_expanded_from_wildcard
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* A + RRSIG, NSEC + RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
 }
@@ -1692,14 +1692,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_incomplete_cache_zonecut_qm)
       if (domain == hero && type == QType::NS) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, hero, QType::NS, "dns1.p03.nsone.net.", DNSResourceRecord::AUTHORITY, 3600);
-        addDS(DNSName(hero), 300, res->d_records, keys);
-        addRRSIG(keys, res->d_records, DNSName(hero), 300);
+        addDS(hero, 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, com, 300);
       }
       else if (domain == nsone && type == QType::A) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, nsone, QType::NS, "dns1.p01.nsone.net.", DNSResourceRecord::AUTHORITY, 3600);
         addNSECRecordToLW(nsone, DNSName("zzz.nsone.net."), {QType::NS, QType::SOA, QType::RRSIG, QType::DNSKEY}, 600, res->d_records);
-        addRRSIG(keys, res->d_records, nsone, 300);
+        addRRSIG(keys, res->d_records, net, 300);
         addRecordToLW(res, "dns1.p01.nsone.net", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
       }
       else {
@@ -1746,16 +1746,237 @@ BOOST_AUTO_TEST_CASE(test_dnssec_incomplete_cache_zonecut_qm)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(DNSName("herokuapp.com."), QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 12U);
 
   ret.clear();
   res = sr->beginResolve(DNSName("dns1.p03.nsone.net."), QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 16U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_ds)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  /* make sure that the signature inception and validity times are computed
+     based on the SyncRes time, not the current one, in case the function
+     takes too long. */
+
+  const time_t fixedNow = sr->getNow().tv_sec;
+
+  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, fixedNow](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    DNSName auth = domain;
+    if (domain == target) {
+      auth = DNSName("powerdns.com.");
+    }
+
+    if (type == QType::DS && domain == DNSName("powerdns.com.")) {
+      /* time out */
+      return 0;
+    }
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
+    }
+
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+      addDS(DNSName("com."), 300, res->d_records, keys);
+      addRRSIG(keys, res->d_records, DNSName("."), 300, false, boost::none, boost::none, fixedNow);
+      addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+      return 1;
+    }
+
+    if (ip == ComboAddress("192.0.2.1:53")) {
+      if (domain == DNSName("com.")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
+        addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRRSIG(keys, res->d_records, domain, 300);
+      }
+      else {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+        /* do NOT include the DS here */
+        //addDS(auth, 300, res->d_records, keys);
+        //addRRSIG(keys, res->d_records, DNSName("com."), 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+      }
+      return 1;
+    }
+
+    if (ip == ComboAddress("192.0.2.2:53")) {
+      if (type == QType::NS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+        addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        addRRSIG(keys, res->d_records, auth, 300);
+      }
+      else {
+        setLWResult(res, RCode::NoError, true, false, true);
+        addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, fixedNow);
+      }
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  try {
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch (const ImmediateServFailException& e) {
+    BOOST_CHECK(e.reason.find("Server Failure while retrieving DS records for powerdns.com") != string::npos);
+  }
+
+  /* and a second time to check nothing was cached */
+  try {
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch (const ImmediateServFailException& e) {
+    BOOST_CHECK(e.reason.find("Server Failure while retrieving DS records for powerdns.com") != string::npos);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_dnskey)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  /* make sure that the signature inception and validity times are computed
+     based on the SyncRes time, not the current one, in case the function
+     takes too long. */
+
+  const time_t fixedNow = sr->getNow().tv_sec;
+
+  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, fixedNow](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    DNSName auth = domain;
+    if (domain == target) {
+      auth = DNSName("powerdns.com.");
+    }
+
+    if (type == QType::DNSKEY && domain == DNSName("powerdns.com.")) {
+      /* time out */
+      return 0;
+    }
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
+    }
+
+    if (isRootServer(ip)) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+      addDS(DNSName("com."), 300, res->d_records, keys);
+      addRRSIG(keys, res->d_records, DNSName("."), 300, false, boost::none, boost::none, fixedNow);
+      addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+      return 1;
+    }
+
+    if (ip == ComboAddress("192.0.2.1:53")) {
+      if (domain == DNSName("com.")) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
+        addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        addRRSIG(keys, res->d_records, domain, 300);
+      }
+      else {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+        addDS(auth, 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+      }
+      return 1;
+    }
+
+    if (ip == ComboAddress("192.0.2.2:53")) {
+      if (type == QType::NS) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+        addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, fixedNow);
+        addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        addRRSIG(keys, res->d_records, auth, 300);
+      }
+      else {
+        setLWResult(res, RCode::NoError, true, false, true);
+        addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, fixedNow);
+      }
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  try {
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch (const ImmediateServFailException& e) {
+    BOOST_CHECK(e.reason.find("Server Failure while retrieving DNSKEY records for powerdns.com") != string::npos);
+  }
+
+  /* and a second time to check nothing was cached */
+  try {
+    sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+    BOOST_CHECK(false);
+  }
+  catch (const ImmediateServFailException& e) {
+    BOOST_CHECK(e.reason.find("Server Failure while retrieving DNSKEY records for powerdns.com") != string::npos);
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index ca0852f9c8720fcc66335e0cfa8f0e472d2522c8..7c93429b8dd028bd7ac4348035e3980323c126b1 100644 (file)
@@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
   BOOST_CHECK_EQUAL(dsQueriesCount, 3U);
@@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
   BOOST_CHECK_EQUAL(dsQueriesCount, 3U);
@@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -233,11 +233,61 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_ds_root)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target(".");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (type == QType::DS) {
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      addRRSIG(keys, res->d_records, DNSName("."), 300);
+      addNSECRecordToLW(domain, DNSName("aaa."), {QType::DNSKEY, QType::SOA, QType::NS, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+      addRRSIG(keys, res->d_records, DNSName("."), 300);
+      return 1;
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
 {
   /* check that we don't accept a signer below us */
@@ -335,7 +385,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 10U);
 
@@ -343,7 +393,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 10U);
 }
@@ -443,7 +493,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
   BOOST_CHECK_EQUAL(dsQueriesCount, 2U);
@@ -452,7 +502,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
   BOOST_CHECK_EQUAL(dsQueriesCount, 2U);
@@ -533,7 +583,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_CHECK_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -541,7 +591,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
@@ -621,7 +671,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 
@@ -629,7 +679,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 8U);
 }
@@ -728,7 +778,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   /* 4 NS: com at ., com at com, powerdns.com at com, powerdns.com at powerdns.com
@@ -740,7 +790,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
@@ -848,7 +898,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK(ret[0].d_type == QType::SOA);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
@@ -857,7 +907,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK(ret[0].d_type == QType::SOA);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
@@ -954,7 +1004,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK(ret[0].d_type == QType::SOA);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
@@ -963,7 +1013,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 6U);
   BOOST_CHECK(ret[0].d_type == QType::SOA);
   BOOST_CHECK_EQUAL(queriesCount, 6U);
@@ -1017,7 +1067,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_direct_ds)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::DS || record.d_type == QType::RRSIG);
@@ -1028,7 +1078,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_direct_ds)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::DS || record.d_type == QType::RRSIG);
@@ -1083,7 +1133,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_direct_ds)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::SOA || record.d_type == QType::NSEC || record.d_type == QType::RRSIG);
@@ -1094,7 +1144,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_direct_ds)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::SOA || record.d_type == QType::NSEC || record.d_type == QType::RRSIG);
@@ -1210,7 +1260,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
@@ -1219,7 +1269,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 9U);
index 0ed32254fd867f3e8aa9d4fecea06ad1f82b574d..6fbcc2725ec07c7b9e555ddeef6bc3812a404863 100644 (file)
@@ -118,7 +118,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
@@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
@@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   /* 4 NS (com from root, com from com, powerdns.com from com,
      powerdns.com from powerdns.com)
@@ -241,7 +241,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
@@ -362,7 +362,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 
@@ -370,7 +370,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 }
@@ -485,7 +485,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 
@@ -493,7 +493,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 }
@@ -611,7 +611,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 
@@ -619,7 +619,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 }
@@ -707,7 +707,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 
@@ -715,7 +715,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 }
@@ -803,7 +803,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 
@@ -811,7 +811,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 11U);
 }
@@ -899,7 +899,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 12U);
 
@@ -907,7 +907,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 12U);
 }
@@ -1020,7 +1020,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* no RRSIG to show */
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 10U);
@@ -1029,7 +1029,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 10U);
 }
@@ -1114,7 +1114,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta)
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   /* should be insecure but we have a TA for powerdns.com. */
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   /* We got a RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK(ret[0].d_type == QType::A);
@@ -1124,7 +1124,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 5U);
@@ -1210,7 +1210,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig)
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   /* should be insecure but we have a TA for powerdns.com., but no RRSIG so Bogus */
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* No RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
@@ -1220,7 +1220,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
@@ -1280,7 +1280,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_nta)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   /* 13 NS + 1 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
@@ -1289,7 +1289,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_nta)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 14U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
@@ -1336,7 +1336,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ta)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   /* 13 NS + 0 RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 13U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
@@ -1345,7 +1345,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ta)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 13U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 }
@@ -1388,7 +1388,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   /* com|NS, powerdns.com|NS, powerdns.com|A */
   BOOST_CHECK_EQUAL(queriesCount, 3U);
@@ -1397,7 +1397,60 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+  /* we don't store empty results */
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys](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, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+    }
+    else {
+
+      setLWResult(res, RCode::NXDomain, true, false, true);
+      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(sr->getValidationState(), vState::Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 0U);
+  /* com|NS, powerdns.com|NS, powerdns.com|A */
+  BOOST_CHECK_EQUAL(queriesCount, 3U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 0U);
   /* we don't store empty results */
   BOOST_CHECK_EQUAL(queriesCount, 4U);
index 9fb52c8899cb665d0cbe54c04d80fc27c24705b6..309454b507ff212374dd321717b1091c7ac3cf7d 100644 (file)
@@ -47,11 +47,11 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap)
   denialMap[std::make_pair(DNSName("example.org."), QType::NSEC)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
+  BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
 
   denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A, false, false);
   /* let's check that d.example.org. is not denied by this proof */
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_denial_wrap_case_1)
@@ -83,11 +83,11 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_wrap_case_1)
   denialMap[std::make_pair(DNSName("z.example.org."), QType::NSEC)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("a.example.org."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
+  BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
 
   denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A, false, false);
   /* let's check that d.example.org. is not denied by this proof */
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_denial_wrap_case_2)
@@ -119,11 +119,11 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_wrap_case_2)
   denialMap[std::make_pair(DNSName("y.example.org."), QType::NSEC)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("z.example.org."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
+  BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
 
   denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A, false, false);
   /* let's check that d.example.org. is not denied by this proof */
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_denial_only_one_nsec)
@@ -155,11 +155,11 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_only_one_nsec)
   denialMap[std::make_pair(DNSName("a.example.org."), QType::NSEC)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
+  BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
 
   denialState = getDenial(denialMap, DNSName("a.example.org."), QType::A, false, false);
   /* let's check that d.example.org. is not denied by this proof */
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_root_nxd_denial)
@@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_root_nxd_denial)
   denialMap[std::make_pair(DNSName("."), QType::NSEC)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("b."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
+  BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_ancestor_nxqtype_denial)
@@ -247,14 +247,14 @@ BOOST_AUTO_TEST_CASE(test_nsec_ancestor_nxqtype_denial)
   dState denialState = getDenial(denialMap, DNSName("a."), QType::A, false, false);
   /* no data means the qname/qtype is not denied, because an ancestor
      delegation NSEC can only deny the DS */
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 
   /* it can not be used to deny any RRs below that owner name either */
   denialState = getDenial(denialMap, DNSName("sub.a."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 
   denialState = getDenial(denialMap, DNSName("a."), QType::DS, true, true);
-  BOOST_CHECK_EQUAL(denialState, NXQTYPE);
+  BOOST_CHECK_EQUAL(denialState, dState::NXQTYPE);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial)
@@ -296,7 +296,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial)
   /* Insecure because the NS is not set, so while it does
      denies the DS, it can't prove an insecure delegation */
   dState denialState = getDenial(denialMap, DNSName("a."), QType::DS, true, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_nxqtype_cname)
@@ -325,7 +325,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_nxqtype_cname)
 
   /* this NSEC is not valid to deny a.powerdns.com|A since it states that a CNAME exists */
   dState denialState = getDenial(denialMap, DNSName("a.powerdns.com."), QType::A, true, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname)
@@ -354,7 +354,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname)
 
   /* this NSEC3 is not valid to deny a.powerdns.com|A since it states that a CNAME exists */
   dState denialState = getDenial(denialMap, DNSName("a.powerdns.com."), QType::A, false, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_nxdomain_denial_missing_wildcard)
@@ -382,7 +382,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_nxdomain_denial_missing_wildcard)
   denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_nxdomain_denial_missing_wildcard)
@@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxdomain_denial_missing_wildcard)
   denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
 
   dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, false, false);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_ent_denial)
@@ -452,16 +452,16 @@ BOOST_AUTO_TEST_CASE(test_nsec_ent_denial)
   /* this NSEC is valid to prove a NXQTYPE at c.powerdns.com because it proves that
      it is an ENT */
   dState denialState = getDenial(denialMap, DNSName("c.powerdns.com."), QType::AAAA, true, true);
-  BOOST_CHECK_EQUAL(denialState, NXQTYPE);
+  BOOST_CHECK_EQUAL(denialState, dState::NXQTYPE);
 
   /* this NSEC is not valid to prove a NXQTYPE at b.powerdns.com,
      it could prove a NXDOMAIN if it had an additional wildcard denial */
   denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::AAAA, true, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 
   /* this NSEC is not valid to prove a NXQTYPE for QType::A at a.c.powerdns.com either */
   denialState = getDenial(denialMap, DNSName("a.c.powerdns.com."), QType::A, true, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 
   /* if we add the wildcard denial proof, we should get a NXDOMAIN proof for b.powerdns.com */
   recordContents.clear();
@@ -476,7 +476,12 @@ BOOST_AUTO_TEST_CASE(test_nsec_ent_denial)
   denialMap[std::make_pair(DNSName(").powerdns.com."), QType::NSEC)] = pair;
 
   denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, true, false);
-  BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
+  BOOST_CHECK_EQUAL(denialState, dState::NXDOMAIN);
+
+  /* this NSEC is NOT valid to prove a NXDOMAIN at c.powerdns.com because it proves that
+     it exists and is an ENT */
+  denialState = getDenial(denialMap, DNSName("c.powerdns.com."), QType::AAAA, true, false);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
@@ -519,10 +524,10 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
   dState denialState = getDenial(denialMap, DNSName("a."), QType::A, false, true);
   /* no data means the qname/qtype is not denied, because an ancestor
      delegation NSEC3 can only deny the DS */
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 
   denialState = getDenial(denialMap, DNSName("a."), QType::DS, true, true);
-  BOOST_CHECK_EQUAL(denialState, NXQTYPE);
+  BOOST_CHECK_EQUAL(denialState, dState::NXQTYPE);
 
   /* it can not be used to deny any RRs below that owner name either */
   /* Add NSEC3 for the next closer */
@@ -552,7 +557,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial)
   denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
 
   denialState = getDenial(denialMap, DNSName("sub.a."), QType::A, false, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_denial_too_many_iterations)
@@ -582,7 +587,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_denial_too_many_iterations)
 
   dState denialState = getDenial(denialMap, DNSName("a."), QType::A, false, true);
   /* since we refuse to compute more than g_maxNSEC3Iterations iterations, it should be Insecure */
-  BOOST_CHECK_EQUAL(denialState, INSECURE);
+  BOOST_CHECK_EQUAL(denialState, dState::INSECURE);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial)
@@ -624,7 +629,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial)
   /* Insecure because the NS is not set, so while it does
      denies the DS, it can't prove an insecure delegation */
   dState denialState = getDenial(denialMap, DNSName("a."), QType::DS, true, true);
-  BOOST_CHECK_EQUAL(denialState, NODATA);
+  BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity)
@@ -671,26 +676,26 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 
   /* check that the entry has not been negatively cached for longer than the RRSIG validity */
-  const NegCache::NegCacheEntry* ne = nullptr;
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_ttd, fixedNow + 1);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Secure);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 1U);
+  NegCache::NegCacheEntry ne;
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_ttd, fixedNow + 1);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Secure);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 1U);
 
   /* again, to test the cache */
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
@@ -742,26 +747,26 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_bogus_validity)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 
   /* check that the entry has been negatively cached but not longer than s_maxbogusttl */
-  const NegCache::NegCacheEntry* ne = nullptr;
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_ttd, fixedNow + SyncRes::s_maxbogusttl);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Bogus);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0U);
+  NegCache::NegCacheEntry ne;
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_ttd, fixedNow + SyncRes::s_maxbogusttl);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Bogus);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 0U);
 
   /* again, to test the cache */
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 3U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
@@ -809,7 +814,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 
@@ -817,7 +822,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity)
   const ComboAddress who;
   vector<DNSRecord> cached;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  BOOST_REQUIRE_EQUAL(s_RC->get(tnow, target, QType(QType::A), true, &cached, who, &signatures), 1);
+  BOOST_REQUIRE_EQUAL(g_recCache->get(tnow, target, QType(QType::A), true, &cached, who, boost::none, &signatures), 1);
   BOOST_REQUIRE_EQUAL(cached.size(), 1U);
   BOOST_REQUIRE_EQUAL(signatures.size(), 1U);
   BOOST_CHECK_EQUAL((cached[0].d_ttl - tnow), 1);
@@ -826,7 +831,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity)
   ret.clear();
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
@@ -878,7 +883,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_secure)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A || record.d_type == QType::RRSIG);
@@ -890,7 +895,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_secure)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A || record.d_type == QType::RRSIG);
@@ -943,7 +948,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_insecure)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A);
@@ -955,7 +960,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_insecure)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A);
@@ -1012,7 +1017,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A);
@@ -1025,7 +1030,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   /* check that we correctly capped the TTD for a Bogus record after
      just-in-time validation */
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
@@ -1041,7 +1046,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A);
index 8364a872c0f48f05ffab0a1d894cf87fc7aa1ac7..915451715df0dcbed337093011edee72a0a989d1 100644 (file)
@@ -61,7 +61,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_secure)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A || record.d_type == QType::RRSIG);
@@ -73,12 +73,12 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_secure)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A || record.d_type == QType::RRSIG);
   }
-  BOOST_CHECK_EQUAL(queriesCount, 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_insecure)
@@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_insecure)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
@@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_insecure)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
@@ -211,7 +211,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
@@ -224,7 +224,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   /* check that we correctly capped the TTD for a Bogus record after
      just-in-time validation */
@@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus)
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
     BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
   }
-  BOOST_CHECK_EQUAL(queriesCount, 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 
   ret.clear();
   /* and a third time to make sure that the validation status (and TTL!)
@@ -240,13 +240,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
     BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
   }
-  BOOST_CHECK_EQUAL(queriesCount, 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_additional_without_rrsig)
@@ -310,7 +310,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_additional_without_rrsig)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::RRSIG || record.d_type == QType::A);
@@ -323,7 +323,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_additional_without_rrsig)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(addTarget, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::RRSIG || record.d_type == QType::A);
@@ -383,34 +383,34 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
   /* check that the entry has been negatively cached */
-  const NegCache::NegCacheEntry* ne = nullptr;
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Indeterminate);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 1U);
+  NegCache::NegCacheEntry ne;
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Indeterminate);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 1U);
 
   ret.clear();
   /* second one _does_ require validation */
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Secure);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Secure);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 1U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure_ds)
@@ -461,7 +461,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure_ds)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
 
@@ -470,7 +470,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure_ds)
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 4U);
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
@@ -521,33 +521,33 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_insecure)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
   /* check that the entry has not been negatively cached */
-  const NegCache::NegCacheEntry* ne = nullptr;
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Indeterminate);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0U);
+  NegCache::NegCacheEntry ne;
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Indeterminate);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 0U);
 
   ret.clear();
   /* second one _does_ require validation */
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(queriesCount, 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Insecure);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Insecure);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 0U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus)
@@ -604,7 +604,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus)
   sr->setDNSSECValidationRequested(false);
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     if (record.d_type == QType::SOA) {
@@ -612,34 +612,34 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus)
     }
   }
   BOOST_CHECK_EQUAL(queriesCount, 1U);
-  const NegCache::NegCacheEntry* ne = nullptr;
-  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Indeterminate);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->d_ttd, now + SyncRes::s_maxnegttl);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0U);
+  NegCache::NegCacheEntry ne;
+  BOOST_CHECK_EQUAL(g_negCache->size(), 1U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Indeterminate);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.d_ttd, now + SyncRes::s_maxnegttl);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 0U);
 
   ret.clear();
   /* second one _does_ require validation */
   sr->setDNSSECValidationRequested(true);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
   }
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Bogus);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->d_ttd, now + SyncRes::s_maxbogusttl);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Bogus);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.d_ttd, now + SyncRes::s_maxbogusttl);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 0U);
 
   ret.clear();
   /* third one _does_ not require validation, we just check that
@@ -647,19 +647,19 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus)
   sr->setDNSSECValidationRequested(false);
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
-  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
   for (const auto& record : ret) {
     BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
   }
   BOOST_CHECK_EQUAL(queriesCount, 4U);
-  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
-  BOOST_CHECK_EQUAL(ne->d_validationState, Bogus);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1U);
-  BOOST_CHECK_EQUAL(ne->d_ttd, now + SyncRes::s_maxbogusttl);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0U);
-  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0U);
+  BOOST_REQUIRE_EQUAL(g_negCache->get(target, QType(QType::A), sr->getNow(), ne), true);
+  BOOST_CHECK_EQUAL(ne.d_validationState, vState::Bogus);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1U);
+  BOOST_CHECK_EQUAL(ne.d_ttd, now + SyncRes::s_maxbogusttl);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 0U);
+  BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 0U);
 }
 
 BOOST_AUTO_TEST_CASE(test_lowercase_outgoing)
@@ -765,7 +765,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo)
 
   dsmap_t ds;
   auto state = sr->getDSRecords(target, ds, false, 0, false);
-  BOOST_CHECK_EQUAL(state, Secure);
+  BOOST_CHECK_EQUAL(state, vState::Secure);
   BOOST_REQUIRE_EQUAL(ds.size(), 1U);
   for (const auto& i : ds) {
     BOOST_CHECK_EQUAL(i.d_digesttype, DNSSECKeeper::DIGEST_SHA256);
@@ -818,7 +818,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo_all_sha)
 
   dsmap_t ds;
   auto state = sr->getDSRecords(target, ds, false, 0, false);
-  BOOST_CHECK_EQUAL(state, Secure);
+  BOOST_CHECK_EQUAL(state, vState::Secure);
   BOOST_REQUIRE_EQUAL(ds.size(), 1U);
   for (const auto& i : ds) {
     BOOST_CHECK_EQUAL(i.d_digesttype, DNSSECKeeper::DIGEST_SHA384);
@@ -871,7 +871,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo_two_highest)
 
   dsmap_t ds;
   auto state = sr->getDSRecords(target, ds, false, 0, false);
-  BOOST_CHECK_EQUAL(state, Secure);
+  BOOST_CHECK_EQUAL(state, vState::Secure);
   BOOST_REQUIRE_EQUAL(ds.size(), 2U);
   for (const auto& i : ds) {
     BOOST_CHECK_EQUAL(i.d_digesttype, DNSSECKeeper::DIGEST_SHA256);
@@ -937,7 +937,7 @@ BOOST_AUTO_TEST_CASE(test_cname_plus_authority_ns_ttl)
   vector<DNSRecord> cached;
   bool wasAuth = false;
 
-  auto ttl = s_RC->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who, nullptr, nullptr, nullptr, nullptr, &wasAuth);
+  auto ttl = g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who, boost::none, nullptr, nullptr, nullptr, nullptr, &wasAuth);
   BOOST_REQUIRE_GE(ttl, 1);
   BOOST_REQUIRE_LE(ttl, 42);
   BOOST_CHECK_EQUAL(cached.size(), 1U);
@@ -946,7 +946,7 @@ BOOST_AUTO_TEST_CASE(test_cname_plus_authority_ns_ttl)
   cached.clear();
 
   /* Also check that the the part in additional is still not auth */
-  BOOST_REQUIRE_GE(s_RC->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who, nullptr, nullptr, nullptr, nullptr, &wasAuth), -1);
+  BOOST_REQUIRE_GE(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who, boost::none, nullptr, nullptr, nullptr, nullptr, &wasAuth), -1);
   BOOST_CHECK_EQUAL(cached.size(), 1U);
   BOOST_CHECK_EQUAL(wasAuth, false);
 }
@@ -987,14 +987,14 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_general)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   cached.clear();
-  BOOST_CHECK_LT(s_RC->get(now, target, QType(QType::AAAA), true, &cached, who), 0);
-  BOOST_CHECK_EQUAL(s_RC->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::DNAME), true, &cached, who), -1);
-  BOOST_CHECK_LT(s_RC->get(now, target, QType(QType::MX), true, &cached, who), 0);
-  BOOST_CHECK_EQUAL(s_RC->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::SOA), true, &cached, who), -1);
-  BOOST_CHECK_LT(s_RC->get(now, target, QType(QType::TXT), false, &cached, who), 0);
-  BOOST_CHECK_EQUAL(s_RC->get(now, DNSName("powerdns.com."), QType(QType::AAAA), false, &cached, who), -1);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::AAAA), true, &cached, who), 0);
+  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::DNAME), true, &cached, who), -1);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::MX), true, &cached, who), 0);
+  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("not-sanitization.powerdns.com."), QType(QType::SOA), true, &cached, who), -1);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::TXT), false, &cached, who), 0);
+  BOOST_CHECK_EQUAL(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::AAAA), false, &cached, who), -1);
 }
 
 BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_relevant_additional_aaaa)
@@ -1022,11 +1022,11 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_relevant_additional_aaaa)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   cached.clear();
   /* not auth since it was in the additional section */
-  BOOST_CHECK_LT(s_RC->get(now, target, QType(QType::AAAA), true, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, target, QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, target, QType(QType::AAAA), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::AAAA), false, &cached, who), 0);
 }
 
 BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
@@ -1054,6 +1054,7 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
       setLWResult(res, 0, false, 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, "powerdns.com.", QType::DS, "1 8 2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAA", 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);
@@ -1063,6 +1064,7 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
     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");
+      addRecordToLW(res, "powerdns.com.", QType::DS, "2 8 2 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBB", DNSResourceRecord::AUTHORITY);
       return 1;
     }
     else {
@@ -1080,17 +1082,23 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(s_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, target, QType(QType::A), true, &cached, who), 0);
   cached.clear();
 
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("com."), QType(QType::NS), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("a.gtld-servers.net."), QType(QType::AAAA), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::AAAA), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("com."), QType(QType::NS), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("a.gtld-servers.net."), QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::A), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns1.powerdns.com."), QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::A), false, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("pdns-public-ns2.powerdns.com."), QType(QType::AAAA), false, &cached, who), 0);
+
+  cached.clear();
+  /* check that we accepted the DS from the parent, and not from the child zone */
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::DS), false, &cached, who), 0);
+  BOOST_REQUIRE_EQUAL(cached.size(), 1U);
+  BOOST_CHECK_EQUAL(cached.at(0).d_content->getZoneRepresentation(), "1 8 2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
 }
 
 BOOST_AUTO_TEST_CASE(test_records_sanitization_scrubs_ns_nxd)
@@ -1120,12 +1128,12 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_scrubs_ns_nxd)
 
   const ComboAddress who;
   vector<DNSRecord> cached;
-  BOOST_CHECK_GT(s_RC->get(now, DNSName("powerdns.com."), QType(QType::SOA), true, &cached, who), 0);
+  BOOST_CHECK_GT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::SOA), true, &cached, who), 0);
   cached.clear();
 
-  BOOST_CHECK_LT(s_RC->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who), 0);
-  BOOST_CHECK_LT(s_RC->get(now, DNSName("spoofed.ns."), QType(QType::A), false, &cached, who), 0);
-  BOOST_CHECK_LT(s_RC->get(now, DNSName("spoofed.ns."), QType(QType::AAAA), false, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, DNSName("powerdns.com."), QType(QType::NS), false, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, DNSName("spoofed.ns."), QType(QType::A), false, &cached, who), 0);
+  BOOST_CHECK_LT(g_recCache->get(now, DNSName("spoofed.ns."), QType(QType::AAAA), false, &cached, who), 0);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index 1aff2b5a15cca8d0c9666fc55618634f2c79a3cf..0dc784936c0423dc967774e920d8cc15f956beda 100644 (file)
@@ -29,7 +29,7 @@
 #include <iostream>
 #include <dnsrecords.hh>
 
-bool init_unit_test()
+static bool init_unit_test()
 {
   reportAllTypes();
   return true;
diff --git a/pdns/recursordist/views.hh b/pdns/recursordist/views.hh
new file mode 120000 (symlink)
index 0000000..2213b7d
--- /dev/null
@@ -0,0 +1 @@
+../views.hh
\ No newline at end of file
index fd64f6aee406ac13a35278190ed96a857b8f71d3..4f074c7da2718209e3337066661292ae6eb8d4dc 100644 (file)
@@ -42,10 +42,10 @@ static void insertIntoRootNSZones(const DNSName &name) {
   }
 }
 
-void primeHints(void)
+bool primeHints(void)
 {
   // prime root cache
-  const vState validationState = Insecure;
+  const vState validationState = vState::Insecure;
   vector<DNSRecord> nsset;
   t_rootNSZones.clear();
 
@@ -68,13 +68,13 @@ void primeHints(void)
       arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
       vector<DNSRecord> aset;
       aset.push_back(arr);
-      s_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, validationState); // auth, nuke it all
+      g_recCache->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState); // 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);
-        s_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, validationState);
+        g_recCache->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState);
       }
       
       nsset.push_back(nsrr);
@@ -84,26 +84,59 @@ void primeHints(void)
     ZoneParserTNG zpt(::arg()["hint-file"]);
     zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
     DNSResourceRecord rr;
+    set<DNSName> seenNS;
+    set<DNSName> seenA;
+    set<DNSName> seenAAAA;
 
     while(zpt.get(rr)) {
       rr.ttl+=time(0);
       if(rr.qtype.getCode()==QType::A) {
+        seenA.insert(rr.qname);
         vector<DNSRecord> aset;
         aset.push_back(DNSRecord(rr));
-        s_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, validationState); // auth, etc see above
+        g_recCache->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState); // auth, etc see above
       } else if(rr.qtype.getCode()==QType::AAAA) {
+        seenAAAA.insert(rr.qname);
         vector<DNSRecord> aaaaset;
         aaaaset.push_back(DNSRecord(rr));
-        s_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, validationState);
+        g_recCache->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState);
       } else if(rr.qtype.getCode()==QType::NS) {
+        seenNS.insert(DNSName(rr.content));
         rr.content=toLower(rr.content);
         nsset.push_back(DNSRecord(rr));
       }
       insertIntoRootNSZones(rr.qname.getLastLabel());
     }
+
+    // Check reachability of A and AAAA records
+    bool reachableA = false, reachableAAAA = false;
+    for (auto const& r: seenA) {
+      if (seenNS.count(r)) {
+        reachableA = true;
+      }
+    }
+    for (auto const& r: seenAAAA) {
+      if (seenNS.count(r)) {
+        reachableAAAA = true;
+      }
+    }
+    if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) {
+      g_log<<Logger::Error<<"Running IPv4 only but no IPv4 root hints"<<endl;
+      return false;
+    }
+    if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) {
+      g_log<<Logger::Error<<"Running IPv6 only but no IPv6 root hints"<<endl;
+      return false;
+    }
+    if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) {
+      g_log<<Logger::Error<<"No valid root hints"<<endl;
+      return false;
+    }
   }
-  s_RC->doWipeCache(g_rootdnsname, false, QType::NS);
-  s_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, boost::none, validationState); // and stuff in the cache
+
+  g_recCache->doWipeCache(g_rootdnsname, false, QType::NS);
+  g_recCache->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, boost::none, boost::none, validationState); // and stuff in the cache
+  return true;
 }
 
 
@@ -115,7 +148,7 @@ void primeHints(void)
 // servers are authoritative for root-servers.net, and some
 // implementations reply not with a delegation on a root-servers.net
 // DS query, but with a NODATA response (the domain is unsigned).
-void primeRootNSZones(bool dnssecmode)
+void primeRootNSZones(bool dnssecmode, unsigned int depth)
 {
   struct timeval now;
   gettimeofday(&now, 0);
@@ -130,9 +163,9 @@ void primeRootNSZones(bool dnssecmode)
   // so make a local copy
   set<DNSName> copy(t_rootNSZones);  
   for (const auto & qname: copy) {
-    s_RC->doWipeCache(qname, false, QType::NS);
+    g_recCache->doWipeCache(qname, false, QType::NS);
     vector<DNSRecord> ret;
-    sr.beginResolve(qname, QType(QType::NS), QClass::IN, ret);
+    sr.beginResolve(qname, QType(QType::NS), QClass::IN, ret, depth + 1);
   }
 }
 
@@ -249,7 +282,7 @@ ComboAddress parseIPAndPort(const std::string& input, uint16_t port)
 }
 
 
-void convertServersForAD(const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, bool verbose=true)
+static void convertServersForAD(const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, bool verbose=true)
 {
   vector<string> servers;
   stringtok(servers, input, sepa);
@@ -268,13 +301,7 @@ void convertServersForAD(const std::string& input, SyncRes::AuthDomain& ad, cons
     g_log<<endl;
 }
 
-void* pleaseWipeNegCache()
-{
-  SyncRes::clearNegCache();
-  return 0;
-}
-
-void* pleaseUseNewSDomainsMap(std::shared_ptr<SyncRes::domainmap_t> newmap)
+static void* pleaseUseNewSDomainsMap(std::shared_ptr<SyncRes::domainmap_t> newmap)
 {
   SyncRes::setDomainMap(newmap);
   return 0;
@@ -338,13 +365,13 @@ string reloadAuthAndForwards()
       }
     }
 
-    for(const auto i : oldAndNewDomains) {
-      broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, i, true, 0xffff));
-      broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, i, true, 0xffff));
-      broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, i, true));
+    for(const auto& i : oldAndNewDomains) {
+      broadcastAccFunction<uint64_t>([&]{return pleaseWipeCache(i, true, 0xffff);});
+      broadcastAccFunction<uint64_t>([&]{return pleaseWipePacketCache(i, true, 0xffff);});
+      broadcastAccFunction<uint64_t>([&]{return pleaseWipeAndCountNegCache(i, true);});
     }
 
-    broadcastFunction(boost::bind(pleaseUseNewSDomainsMap, newDomainMap));
+    broadcastFunction([=]{return pleaseUseNewSDomainsMap(newDomainMap);});
     return "ok\n";
   }
   catch(std::exception& e) {
index 70b7af35fdd2b09ac63b58691e49a985e6cbde85..d6cf953c63b1f197d269148201b08a713f6521ff 100644 (file)
@@ -25,8 +25,8 @@
 #endif
 
 #include <atomic>
-#include <condition_variable>
 #include <queue>
+#include <mutex>
 #include <thread>
 
 #include "iputils.hh"
index 774fa7e56402378524de46135cc6535cedf0cb1b..a5bdec36be42ff8c40f14b99323432ff3e9143b5 100644 (file)
 #include "base64.hh"
 #include "dnswriter.hh"
 #include "dnsparser.hh"
-
+#include "query-local-address.hh"
 
 #include "dns_random.hh"
 #include <poll.h>
-#include "gss_context.hh"
 #include "namespaces.hh"
 
+using pdns::resolver::parseResult;
+
 int makeQuerySocket(const ComboAddress& local, bool udpOrTCP, bool nonLocalBind)
 {
   ComboAddress ourLocal(local);
@@ -100,10 +101,12 @@ Resolver::Resolver()
   locals["default4"] = -1;
   locals["default6"] = -1;
   try {
-    if(!::arg()["query-local-address"].empty())
-      locals["default4"] = makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind"));
-    if(!::arg()["query-local-address6"].empty())
-      locals["default6"] = makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind"));
+    if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+      locals["default4"] = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
+    }
+    if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+      locals["default6"] = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
+    }
   }
   catch(...) {
     if(locals["default4"]>=0)
@@ -156,12 +159,11 @@ uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& l
   // choose socket based on local
   if (local.sin4.sin_family == 0) {
     // up to us.
-    sock = remote.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"];
-    if (sock == -1) {
+    if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
       string ipv = remote.sin4.sin_family == AF_INET ? "4" : "6";
-      string qla = remote.sin4.sin_family == AF_INET ? "" : "6";
-      throw ResolverException("No IPv" + ipv + " socket available, is query-local-address" + qla + " unset?");
+      throw ResolverException("No IPv" + ipv + " socket available, is such an address configured in query-local-address?");
     }
+    sock = remote.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"];
   } else {
     std::string lstr = local.toString();
     std::map<std::string, int>::iterator lptr;
@@ -188,36 +190,41 @@ uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& l
   return randomid;
 }
 
-static int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result)
-{
-  result->clear();
-
-  if(mdp.d_header.rcode)
-    return mdp.d_header.rcode;
-
-  if(origQname.countLabels()) {  // not AXFR
-    if(mdp.d_header.id != id) 
-      throw ResolverException("Remote nameserver replied with wrong id");
-    if(mdp.d_header.qdcount != 1)
-      throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")");
-    if(mdp.d_qname != origQname)
-      throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toLogString()+"!="+ origQname.toLogString()+".)");
-  }
+namespace pdns {
+  namespace resolver {
+    int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result)
+    {
+      result->clear();
+
+      if(mdp.d_header.rcode)
+        return mdp.d_header.rcode;
+
+      if(origQname.countLabels()) {  // not AXFR
+        if(mdp.d_header.id != id) 
+          throw ResolverException("Remote nameserver replied with wrong id");
+        if(mdp.d_header.qdcount != 1)
+          throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")");
+        if(mdp.d_qname != origQname)
+          throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toLogString()+"!="+ origQname.toLogString()+".)");
+      }
 
-  vector<DNSResourceRecord> ret;
-  DNSResourceRecord rr;
-  result->reserve(mdp.d_answers.size());
+      vector<DNSResourceRecord> ret;
+      DNSResourceRecord rr;
+      result->reserve(mdp.d_answers.size());
 
-  for (const auto& i: mdp.d_answers) {
-    rr.qname = i.first.d_name;
-    rr.qtype = i.first.d_type;
-    rr.ttl = i.first.d_ttl;
-    rr.content = i.first.d_content->getZoneRepresentation(true);
-    result->push_back(rr);
-  }
+      for (const auto& i: mdp.d_answers) {
+        rr.qname = i.first.d_name;
+        rr.qtype = i.first.d_type;
+        rr.ttl = i.first.d_ttl;
+        rr.content = i.first.d_content->getZoneRepresentation(true);
+        result->push_back(rr);
+      }
 
-  return 0;
-}
+      return 0;
+    }
+
+  } // namespace resolver
+} // namespace pdns
 
 bool Resolver::tryGetSOASerial(DNSName *domain, ComboAddress* remote, uint32_t *theirSerial, uint32_t *theirInception, uint32_t *theirExpire, uint16_t* id)
 {
@@ -362,221 +369,3 @@ void Resolver::getSoaSerial(const ComboAddress& ipport, const DNSName &domain, u
   }
 }
 
-AXFRRetriever::AXFRRetriever(const ComboAddress& remote,
-                             const DNSName& domain,
-                             const TSIGTriplet& tt, 
-                             const ComboAddress* laddr,
-                             size_t maxReceivedBytes,
-                             uint16_t timeout)
-  : d_tsigVerifier(tt, remote, d_trc), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes)
-{
-  ComboAddress local;
-  if (laddr != nullptr) {
-    local = ComboAddress(*laddr);
-  } else {
-    string qlas = remote.sin4.sin_family == AF_INET ? "query-local-address" : "query-local-address6";
-    if (::arg()[qlas].empty()) {
-      throw ResolverException("Unable to determine source address for AXFR request to " + remote.toStringWithPort() + " for " + domain.toLogString() + ". " + qlas + " is unset");
-    }
-    local=ComboAddress(::arg()[qlas]);
-  }
-  d_sock = -1;
-  try {
-    d_sock = makeQuerySocket(local, false); // make a TCP socket
-    if (d_sock < 0)
-      throw ResolverException("Error creating socket for AXFR request to "+d_remote.toStringWithPort());
-    d_buf = shared_array<char>(new char[65536]);
-    d_remote = remote; // mostly for error reporting
-    this->connect(timeout);
-    d_soacount = 0;
-  
-    vector<uint8_t> packet;
-    DNSPacketWriter pw(packet, domain, QType::AXFR);
-    pw.getHeader()->id = dns_random_uint16();
-  
-    if(!tt.name.empty()) {
-      if (tt.algo == DNSName("hmac-md5"))
-        d_trc.d_algoName = tt.algo + DNSName("sig-alg.reg.int");
-      else
-        d_trc.d_algoName = tt.algo;
-      d_trc.d_time = time(0);
-      d_trc.d_fudge = 300;
-      d_trc.d_origID=ntohs(pw.getHeader()->id);
-      d_trc.d_eRcode=0;
-      addTSIG(pw, d_trc, tt.name, tt.secret, "", false);
-    }
-  
-    uint16_t replen=htons(packet.size());
-    Utility::iovec iov[2];
-    iov[0].iov_base=reinterpret_cast<char*>(&replen);
-    iov[0].iov_len=2;
-    iov[1].iov_base=packet.data();
-    iov[1].iov_len=packet.size();
-  
-    int ret=Utility::writev(d_sock, iov, 2);
-    if(ret < 0)
-      throw ResolverException("Error sending question to "+d_remote.toStringWithPort()+": "+stringerror());
-    if(ret != (int)(2+packet.size())) {
-      throw ResolverException("Partial write on AXFR request to "+d_remote.toStringWithPort());
-    }
-  
-    int res = waitForData(d_sock, timeout, 0);
-    
-    if(!res)
-      throw ResolverException("Timeout waiting for answer from "+d_remote.toStringWithPort()+" during AXFR");
-    if(res<0)
-      throw ResolverException("Error waiting for answer from "+d_remote.toStringWithPort()+": "+stringerror());
-  }
-  catch(...) {
-    if(d_sock >= 0)
-      close(d_sock);
-    d_sock = -1;
-    throw;
-  }
-}
-
-AXFRRetriever::~AXFRRetriever()
-{
-  close(d_sock);
-}
-
-
-
-int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records, uint16_t timeout) // Implementation is making sure RFC2845 4.4 is followed.
-{
-  if(d_soacount > 1)
-    return false;
-
-  // d_sock is connected and is about to spit out a packet
-  int len=getLength(timeout);
-  if(len<0)
-    throw ResolverException("EOF trying to read axfr chunk from remote TCP client");
-
-  if (d_maxReceivedBytes > 0 && (d_maxReceivedBytes - d_receivedBytes) < (size_t) len)
-    throw ResolverException("Reached the maximum number of received bytes during AXFR");
-
-  timeoutReadn(len, timeout);
-
-  d_receivedBytes += (uint16_t) len;
-
-  MOADNSParser mdp(false, d_buf.get(), len);
-
-  int err = mdp.d_header.rcode;
-
-  if(err) {
-    throw ResolverException("AXFR chunk error: " + RCode::to_s(err));
-  }
-
-  try {
-    d_tsigVerifier.check(std::string(d_buf.get(), len), mdp);
-  }
-  catch(const std::runtime_error& re) {
-    throw ResolverException(re.what());
-  }
-
-  if(!records) {
-    err = parseResult(mdp, DNSName(), 0, 0, &res);
-
-    if (!err) {
-      for(const auto& answer :  mdp.d_answers)
-        if (answer.first.d_type == QType::SOA)
-          d_soacount++;
-    }
-  }
-  else {
-    records->clear();
-    records->reserve(mdp.d_answers.size());
-
-    for(auto& r: mdp.d_answers) {
-      if (r.first.d_type == QType::SOA) {
-        d_soacount++;
-      }
-
-      records->push_back(std::move(r.first));
-    }
-  }
-
-  return true;
-}
-
-void AXFRRetriever::timeoutReadn(uint16_t bytes, uint16_t timeoutsec)
-{
-  time_t start=time(nullptr);
-  int n=0;
-  int numread;
-  while(n<bytes) {
-    int res=waitForData(d_sock, timeoutsec-(time(nullptr)-start));
-    if(res<0)
-      throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror());
-    if(!res)
-      throw ResolverException("Timeout while reading data from remote nameserver over TCP");
-
-    numread=recv(d_sock, d_buf.get()+n, bytes-n, 0);
-    if(numread<0)
-      throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror());
-    if(numread==0)
-      throw ResolverException("Remote nameserver closed TCP connection");
-    n+=numread;
-  }
-}
-
-void AXFRRetriever::connect(uint16_t timeout)
-{
-  setNonBlocking( d_sock );
-
-  int err;
-
-  if((err=::connect(d_sock,(struct sockaddr*)&d_remote, d_remote.getSocklen()))<0 && errno!=EINPROGRESS) {
-    try {
-      closesocket(d_sock);
-    }
-    catch(const PDNSException& e) {
-      d_sock=-1;
-      throw ResolverException("Error closing AXFR socket after connect() failed: "+e.reason);
-    }
-
-    throw ResolverException("connect: "+stringerror());
-  }
-
-  if(!err)
-    goto done;
-
-  err=waitForRWData(d_sock, false, timeout, 0); // wait for writeability
-  
-  if(!err) {
-    try {
-      closesocket(d_sock); // timeout
-    }
-    catch(const PDNSException& e) {
-      d_sock=-1;
-      throw ResolverException("Error closing AXFR socket after timeout: "+e.reason);
-    }
-
-    d_sock=-1;
-    errno=ETIMEDOUT;
-    
-    throw ResolverException("Timeout connecting to server");
-  }
-  else if(err < 0) {
-    throw ResolverException("Error connecting: "+stringerror());
-  }
-  else {
-    Utility::socklen_t len=sizeof(err);
-    if(getsockopt(d_sock, SOL_SOCKET,SO_ERROR,(char *)&err,&len)<0)
-      throw ResolverException("Error connecting: "+stringerror()); // Solaris
-
-    if(err)
-      throw ResolverException("Error connecting: "+string(strerror(err)));
-  }
-  
- done:
-  setBlocking( d_sock );
-  // d_sock now connected
-}
-
-int AXFRRetriever::getLength(uint16_t timeout)
-{
-  timeoutReadn(2, timeout);
-  return (unsigned char)d_buf[0]*256+(unsigned char)d_buf[1];
-}
-
index 17b4b7ccdd4fc876b879ef09243b78ace0f1c46f..c78ef139f20a0a923104178acf0578c6ec852e55 100644 (file)
@@ -76,31 +76,8 @@ private:
   std::map<std::string, int> locals;
 };
 
-class AXFRRetriever : public boost::noncopyable
-{
-  public:
-    AXFRRetriever(const ComboAddress& remote,
-                  const DNSName& zone,
-                  const TSIGTriplet& tt = TSIGTriplet(),
-                  const ComboAddress* laddr = NULL,
-                  size_t maxReceivedBytes=0,
-                  uint16_t timeout=10);
-    ~AXFRRetriever();
-    int getChunk(Resolver::res_t &res, vector<DNSRecord>* records=0, uint16_t timeout=10);
-  
-  private:
-    void connect(uint16_t timeout);
-    int getLength(uint16_t timeout);
-    void timeoutReadn(uint16_t bytes, uint16_t timeoutsec=10);
-
-    shared_array<char> d_buf;
-    string d_domain;
-    int d_sock;
-    int d_soacount;
-    ComboAddress d_remote;
-    TSIGTCPVerifier d_tsigVerifier;
-
-    size_t d_receivedBytes;
-    size_t d_maxReceivedBytes;
-    TSIGRecordContent d_trc;
-};
+namespace pdns {
+  namespace resolver {
+    int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result);
+  } // namespace resolver
+} // namespace pdns
index 71173d015960f6597b16f1e5903999757ec78e46..bd8388fe2436c2b3478c3d868100480f4dd934d7 100644 (file)
 #include "dns_random.hh"
 #include "backends/gsql/ssql.hh"
 #include "communicator.hh"
+#include "query-local-address.hh"
 
 extern StatBag S;
 extern CommunicatorClass Communicator;
 
-pthread_mutex_t PacketHandler::s_rfc2136lock=PTHREAD_MUTEX_INITIALIZER;
+std::mutex PacketHandler::s_rfc2136lock;
 
 // Implement section 3.2.1 and 3.2.2 of RFC2136
 int PacketHandler::checkUpdatePrerequisites(const DNSRecord *rr, DomainInfo *di) {
@@ -121,11 +122,10 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
     if (rrType == QType::NSEC3PARAM) {
       g_log<<Logger::Notice<<msgPrefix<<"Adding/updating NSEC3PARAM for zone, resetting ordernames."<<endl;
 
-      NSEC3PARAMRecordContent nsec3param(rr->d_content->getZoneRepresentation(), di->zone.toString() /* FIXME400 huh */);
+      *ns3pr = NSEC3PARAMRecordContent(rr->d_content->getZoneRepresentation(), di->zone.toString() /* FIXME400 huh */);
       *narrow = false; // adding a NSEC3 will cause narrow mode to be dropped, as you cannot specify that in a NSEC3PARAM record
-      d_dk.setNSEC3PARAM(di->zone, nsec3param, (*narrow));
-
-      *haveNSEC3 = d_dk.getNSEC3PARAM(di->zone, ns3pr, narrow);
+      d_dk.setNSEC3PARAM(di->zone, *ns3pr, (*narrow));
+      *haveNSEC3 = true;
 
       vector<DNSResourceRecord> rrs;
       set<DNSName> qnames, nssets, dssets;
@@ -400,15 +400,16 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
         d_dk.unsetNSEC3PARAM(rr->d_name);
       else if (rr->d_class == QClass::NONE) {
         NSEC3PARAMRecordContent nsec3rr(rr->d_content->getZoneRepresentation(), di->zone.toString() /* FIXME400 huh */);
-        if (ns3pr->getZoneRepresentation() == nsec3rr.getZoneRepresentation())
+        if (*haveNSEC3 && ns3pr->getZoneRepresentation() == nsec3rr.getZoneRepresentation())
           d_dk.unsetNSEC3PARAM(rr->d_name);
         else
           return 0;
       } else
         return 0;
 
-      // We retrieve new values, other RR's in this update package might need it as well.
-      *haveNSEC3 = d_dk.getNSEC3PARAM(di->zone, ns3pr, narrow);
+      // Update NSEC3 variables, other RR's in this update package might need them as well.
+      *haveNSEC3 = false;
+      *narrow = false;
 
       vector<DNSResourceRecord> rrs;
       set<DNSName> qnames, nssets, dssets, ents;
@@ -597,14 +598,10 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
   for(const auto& remote : di.masters) {
     g_log<<Logger::Notice<<msgPrefix<<"Forwarding packet to master "<<remote<<endl;
 
-    ComboAddress local;
-    if (remote.sin4.sin_family == AF_INET && !::arg()["query-local-address"].empty()) {
-      local = ComboAddress(::arg()["query-local-address"]);
-    } else if(remote.sin4.sin_family == AF_INET6 && !::arg()["query-local-address6"].empty()) {
-      local = ComboAddress(::arg()["query-local-address6"]);
-    } else {
+    if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
       continue;
     }
+    auto local = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
     int sock = makeQuerySocket(local, false); // create TCP socket. RFC2136 section 6.2 seems to be ok with this.
     if(sock < 0) {
       g_log<<Logger::Error<<msgPrefix<<"Error creating socket: "<<stringerror()<<endl;
@@ -646,7 +643,7 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
         closesocket(sock);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Error<<"Error closing master forwarding socket after a timeout occured: "<<e.reason<<endl;
+        g_log<<Logger::Error<<"Error closing master forwarding socket after a timeout occurred: "<<e.reason<<endl;
       }
       continue;
     }
@@ -656,7 +653,7 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
         closesocket(sock);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Error<<"Error closing master forwarding socket after an error occured: "<<e.reason<<endl;
+        g_log<<Logger::Error<<"Error closing master forwarding socket after an error occurred: "<<e.reason<<endl;
       }
       continue;
     }
@@ -751,20 +748,10 @@ int PacketHandler::processUpdate(DNSPacket& p) {
         return RCode::Refused;
       }
 
-      if (p.d_tsig_algo == TSIG_GSS) {
-        GssName inputname(p.d_peer_principal); // match against principal since GSS
-        for(const auto& key: tsigKeys) {
-          if (inputname.match(key)) {
-            validKey = true;
-            break;
-          }
-        }
-      } else {
-        for(const auto& key: tsigKeys) {
-          if (inputkey == DNSName(key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid.
-            validKey=true;
-            break;
-          }
+      for(const auto& key: tsigKeys) {
+        if (inputkey == DNSName(key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid.
+          validKey=true;
+          break;
         }
       }
 
@@ -823,7 +810,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
   }
 
 
-  Lock l(&s_rfc2136lock); //TODO: i think this lock can be per zone, not for everything
+  std::lock_guard<std::mutex> l(s_rfc2136lock); //TODO: i think this lock can be per zone, not for everything
   g_log<<Logger::Info<<msgPrefix<<"starting transaction."<<endl;
   if (!di.backend->startTransaction(p.qdomain, -1)) { // Not giving the domain_id means that we do not delete the existing records.
     g_log<<Logger::Error<<msgPrefix<<"Backend for domain "<<p.qdomain<<" does not support transaction. Can't do Update packet."<<endl;
@@ -836,7 +823,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
     if (rr->d_place == DNSResourceRecord::ANSWER) {
       int res = checkUpdatePrerequisites(rr, &di);
       if (res>0) {
-        g_log<<Logger::Error<<msgPrefix<<"Failed PreRequisites check for "<<rr->d_name.toLogString()<<", returning "<<RCode::to_s(res)<<endl;
+        g_log<<Logger::Error<<msgPrefix<<"Failed PreRequisites check for "<<rr->d_name<<", returning "<<RCode::to_s(res)<<endl;
         di.backend->abortTransaction();
         return res;
       }
@@ -913,6 +900,8 @@ int PacketHandler::processUpdate(DNSPacket& p) {
     bool narrow=false;
     bool haveNSEC3 = d_dk.getNSEC3PARAM(di.zone, &ns3pr, &narrow);
     bool isPresigned = d_dk.isPresigned(di.zone);
+    string soaEditSetting;
+    d_dk.getSoaEdit(di.zone, soaEditSetting);
 
     // 3.4.2 - Perform the updates.
     // There's a special condition where deleting the last NS record at zone apex is never deleted (3.4.2.4)
@@ -1015,7 +1004,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
 
     // Section 3.6 - Update the SOA serial - outside of performUpdate because we do a SOA update for the complete update message
     if (changedRecords > 0 && !updatedSerial) {
-      increaseSerial(msgPrefix, &di, haveNSEC3, narrow, &ns3pr);
+      increaseSerial(msgPrefix, &di, soaEditSetting, haveNSEC3, narrow, &ns3pr);
       changedRecords++;
     }
 
@@ -1027,6 +1016,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
 
       S.deposit("dnsupdate-changes", changedRecords);
 
+      d_dk.clearMetaCache(di.zone);
       // Purge the records!
       string zone(di.zone.toString());
       zone.append("$");
@@ -1076,7 +1066,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
   }
 }
 
-void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di, bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr) {
+void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di, const string& soaEditSetting,  bool haveNSEC3, bool narrow, const NSEC3PARAMRecordContent *ns3pr) {
   SOAData sd;
   if (!di->backend->getSOA(di->zone, sd)) {
     throw PDNSException("SOA-Serial update failed because there was no SOA. Wowie.");
@@ -1091,10 +1081,8 @@ void PacketHandler::increaseSerial(const string &msgPrefix, const DomainInfo *di
   if (!soaEdit2136Setting.empty()) {
     soaEdit2136 = soaEdit2136Setting[0];
     if (pdns_iequals(soaEdit2136, "SOA-EDIT") || pdns_iequals(soaEdit2136,"SOA-EDIT-INCREASE") ){
-      string soaEditSetting;
-      d_dk.getSoaEdit(di->zone, soaEditSetting);
       if (soaEditSetting.empty()) {
-        g_log<<Logger::Error<<msgPrefix<<"Using "<<soaEdit2136<<" for SOA-EDIT-DNSUPDATE increase on DNS update, but SOA-EDIT is not set for domain \""<< di->zone.toLogString() <<"\". Using DEFAULT for SOA-EDIT-DNSUPDATE"<<endl;
+        g_log<<Logger::Error<<msgPrefix<<"Using "<<soaEdit2136<<" for SOA-EDIT-DNSUPDATE increase on DNS update, but SOA-EDIT is not set for domain \""<< di->zone <<"\". Using DEFAULT for SOA-EDIT-DNSUPDATE"<<endl;
         soaEdit2136 = "DEFAULT";
       } else
         soaEdit = soaEditSetting;
index 53cad7286056dbec5129967a2bbea051e72bb3c1..212c926052229a6786372cb8b8629bfd3b4161bf 100644 (file)
@@ -3,12 +3,13 @@
 #include "dnsrecords.hh"
 #include "ixfr.hh"
 #include "syncres.hh"
-#include "resolver.hh"
+#include "axfr-retriever.hh"
 #include "logger.hh"
 #include "rec-lua-conf.hh"
 #include "rpzloader.hh"
 #include "zoneparser-tng.hh"
 #include "threadname.hh"
+#include "query-local-address.hh"
 
 Netmask makeNetmaskFromRPZ(const DNSName& name)
 {
@@ -146,14 +147,14 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
   if(dr.d_name.isPartOf(rpzNSDname)) {
     DNSName filt=dr.d_name.makeRelative(rpzNSDname);
     if(addOrRemove)
-      zone->addNSTrigger(filt, std::move(pol));
+      zone->addNSTrigger(filt, std::move(pol), defpolApplied);
     else
       zone->rmNSTrigger(filt, std::move(pol));
   } else if(dr.d_name.isPartOf(rpzClientIP)) {
     DNSName filt=dr.d_name.makeRelative(rpzClientIP);
     auto nm=makeNetmaskFromRPZ(filt);
     if(addOrRemove)
-      zone->addClientTrigger(nm, std::move(pol));
+      zone->addClientTrigger(nm, std::move(pol), defpolApplied);
     else
       zone->rmClientTrigger(nm, std::move(pol));
     
@@ -162,14 +163,14 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
     DNSName filt=dr.d_name.makeRelative(rpzIP);
     auto nm=makeNetmaskFromRPZ(filt);
     if(addOrRemove)
-      zone->addResponseTrigger(nm, std::move(pol));
+      zone->addResponseTrigger(nm, std::move(pol), defpolApplied);
     else
       zone->rmResponseTrigger(nm, std::move(pol));
   } else if(dr.d_name.isPartOf(rpzNSIP)) {
     DNSName filt=dr.d_name.makeRelative(rpzNSIP);
     auto nm=makeNetmaskFromRPZ(filt);
     if(addOrRemove)
-      zone->addNSIPTrigger(nm, std::move(pol));
+      zone->addNSIPTrigger(nm, std::move(pol), defpolApplied);
     else
       zone->rmNSIPTrigger(nm, std::move(pol));
   } else {
@@ -193,7 +194,7 @@ static shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master
 
   ComboAddress local(localAddress);
   if (local == ComboAddress())
-    local = getQueryLocalAddress(master.sin4.sin_family, 0);
+    local = pdns::getQueryLocalAddress(master.sin4.sin_family, 0);
 
   AXFRRetriever axfr(master, zoneName, tt, &local, maxReceivedBytes, axfrTimeout);
   unsigned int nrecords=0;
@@ -306,14 +307,14 @@ static bool dumpZoneToDisk(const DNSName& zoneName, const std::shared_ptr<DNSFil
   std::string temp = dumpZoneFileName + "XXXXXX";
   int fd = mkstemp(&temp.at(0));
   if (fd < 0) {
-    g_log<<Logger::Warning<<"Unable to open a file to dump the content of the RPZ zone "<<zoneName.toLogString()<<endl;
+    g_log<<Logger::Warning<<"Unable to open a file to dump the content of the RPZ zone "<<zoneName<<endl;
     return false;
   }
 
   auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "w+"), fclose);
   if (!fp) {
     close(fd);
-    g_log<<Logger::Warning<<"Unable to open a file pointer to dump the content of the RPZ zone "<<zoneName.toLogString()<<endl;
+    g_log<<Logger::Warning<<"Unable to open a file pointer to dump the content of the RPZ zone "<<zoneName<<endl;
     return false;
   }
   fd = -1;
@@ -322,27 +323,27 @@ static bool dumpZoneToDisk(const DNSName& zoneName, const std::shared_ptr<DNSFil
     newZone->dump(fp.get());
   }
   catch(const std::exception& e) {
-    g_log<<Logger::Warning<<"Error while dumping the content of the RPZ zone "<<zoneName.toLogString()<<": "<<e.what()<<endl;
+    g_log<<Logger::Warning<<"Error while dumping the content of the RPZ zone "<<zoneName<<": "<<e.what()<<endl;
     return false;
   }
 
   if (fflush(fp.get()) != 0) {
-    g_log<<Logger::Warning<<"Error while flushing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl;
+    g_log<<Logger::Warning<<"Error while flushing the content of the RPZ zone "<<zoneName<<" to the dump file: "<<stringerror()<<endl;
     return false;
   }
 
   if (fsync(fileno(fp.get())) != 0) {
-    g_log<<Logger::Warning<<"Error while syncing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl;
+    g_log<<Logger::Warning<<"Error while syncing the content of the RPZ zone "<<zoneName<<" to the dump file: "<<stringerror()<<endl;
     return false;
   }
 
   if (fclose(fp.release()) != 0) {
-    g_log<<Logger::Warning<<"Error while writing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl;
+    g_log<<Logger::Warning<<"Error while writing the content of the RPZ zone "<<zoneName<<" to the dump file: "<<stringerror()<<endl;
     return false;
   }
 
   if (rename(temp.c_str(), dumpZoneFileName.c_str()) != 0) {
-    g_log<<Logger::Warning<<"Error while moving the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<stringerror()<<endl;
+    g_log<<Logger::Warning<<"Error while moving the content of the RPZ zone "<<zoneName<<" to the dump file: "<<stringerror()<<endl;
     return false;
   }
 
@@ -364,7 +365,7 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
 
   time_t refresh;
   DNSName zoneName = oldZone->getDomain();
-  std::string polName = oldZone->getName() ? *(oldZone->getName()) : zoneName.toString();
+  std::string polName = oldZone->getName().empty() ? oldZone->getName() : zoneName.toString();
 
   while (!sr) {
     /* if we received an empty sr, the zone was not really preloaded */
@@ -377,6 +378,8 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
         sr = loadRPZFromServer(master, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout);
         newZone->setSerial(sr->d_st.serial);
         newZone->setRefresh(sr->d_st.refresh);
+        // This period gets used below this loop
+        oldZone->setRefresh(sr->d_st.refresh);
         setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), true);
 
         g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
@@ -433,7 +436,7 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
 
       ComboAddress local(localAddress);
       if (local == ComboAddress()) {
-        local = getQueryLocalAddress(master.sin4.sin_family, 0);
+        local = pdns::getQueryLocalAddress(master.sin4.sin_family, 0);
       }
 
       try {
@@ -452,73 +455,91 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
       continue;
     }
 
-    g_log<<Logger::Info<<"Processing "<<deltas.size()<<" delta"<<addS(deltas)<<" for RPZ "<<zoneName<<endl;
-
-    oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
-    /* we need to make a _full copy_ of the zone we are going to work on */
-    std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
+    try {
+      g_log<<Logger::Info<<"Processing "<<deltas.size()<<" delta"<<addS(deltas)<<" for RPZ "<<zoneName<<endl;
+
+      oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
+      /* we need to make a _full copy_ of the zone we are going to work on */
+      std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
+      /* initialize the current serial to the last one */
+      std::shared_ptr<SOARecordContent> currentSR = sr;
+
+      int totremove=0, totadd=0;
+      bool fullUpdate = false;
+      for(const auto& delta : deltas) {
+        const auto& remove = delta.first;
+        const auto& add = delta.second;
+        if(remove.empty()) {
+          g_log<<Logger::Warning<<"IXFR update is a whole new zone"<<endl;
+          newZone->clear();
+          fullUpdate = true;
+        }
+        for(const auto& rr : remove) { // should always contain the SOA
+          if(rr.d_type == QType::NS)
+            continue;
+          if(rr.d_type == QType::SOA) {
+            auto oldsr = getRR<SOARecordContent>(rr);
+            if (oldsr && oldsr->d_st.serial == currentSR->d_st.serial) {
+              //           cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
+            }
+            else {
+              if (!oldsr) {
+                throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
+              }
+              else {
+                throw std::runtime_error("Received an unexpected serial (" + std::to_string(oldsr->d_st.serial) + ", expecting " + std::to_string(currentSR->d_st.serial) + ") from SOA record while processing the removal part of an update");
+              }
+            }
+          }
+          else {
+            totremove++;
+            g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl;
+            RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL);
+          }
+        }
 
-    int totremove=0, totadd=0;
-    bool fullUpdate = false;
-    for(const auto& delta : deltas) {
-      const auto& remove = delta.first;
-      const auto& add = delta.second;
-      if(remove.empty()) {
-        g_log<<Logger::Warning<<"IXFR update is a whole new zone"<<endl;
-        newZone->clear();
-        fullUpdate = true;
+        for(const auto& rr : add) { // should always contain the new SOA
+          if(rr.d_type == QType::NS)
+            continue;
+          if(rr.d_type == QType::SOA) {
+            auto tempSR = getRR<SOARecordContent>(rr);
+            //   g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<currentSR->d_st.serial<<endl;
+            if (tempSR) {
+              currentSR = tempSR;
+            }
+          }
+          else {
+            totadd++;
+            g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl;
+            RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL);
+          }
+        }
       }
-      for(const auto& rr : remove) { // should always contain the SOA
-        if(rr.d_type == QType::NS)
-          continue;
-       if(rr.d_type == QType::SOA) {
-         auto oldsr = getRR<SOARecordContent>(rr);
-         if(oldsr && oldsr->d_st.serial == sr->d_st.serial) {
-           //      cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
-         }
-         else
-           g_log<<Logger::Error<<"GOT WRONG SOA SERIAL REMOVAL, SHOULD TRIGGER WHOLE RELOAD"<<endl;
-       }
-       else {
-          totremove++;
-         g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl;
-         RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL);
-       }
+
+      /* only update sr now that all changes have been converted */
+      if (currentSR) {
+        sr = currentSR;
       }
+      g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl;
+      newZone->setSerial(sr->d_st.serial);
+      newZone->setRefresh(sr->d_st.refresh);
+      setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), fullUpdate);
+
+      /* we need to replace the existing zone with the new one,
+         but we don't want to touch anything else, especially other zones,
+         since they might have been updated by another RPZ IXFR tracker thread.
+      */
+      g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
+                          lci.dfe.setZone(zoneIdx, newZone);
+                        });
 
-      for(const auto& rr : add) { // should always contain the new SOA
-        if(rr.d_type == QType::NS)
-          continue;
-       if(rr.d_type == QType::SOA) {
-         auto newsr = getRR<SOARecordContent>(rr);
-         //      g_log<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<newsr->d_st.serial<<endl;
-         if (newsr) {
-           sr = newsr;
-         }
-       }
-       else {
-          totadd++;
-         g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl;
-         RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL);
-       }
+      if (!dumpZoneFileName.empty()) {
+        dumpZoneToDisk(zoneName, newZone, dumpZoneFileName);
       }
+      refresh = std::max(refreshFromConf ? refreshFromConf :  newZone->getRefresh(), 1U);
     }
-    g_log<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<sr->d_st.serial<<endl;
-    newZone->setSerial(sr->d_st.serial);
-    newZone->setRefresh(sr->d_st.refresh);
-    setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), fullUpdate);
-
-    /* we need to replace the existing zone with the new one,
-       but we don't want to touch anything else, especially other zones,
-       since they might have been updated by another RPZ IXFR tracker thread.
-    */
-    g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
-                        lci.dfe.setZone(zoneIdx, newZone);
-                      });
-
-    if (!dumpZoneFileName.empty()) {
-      dumpZoneToDisk(zoneName, newZone, dumpZoneFileName);
+    catch (const std::exception& e) {
+      g_log << Logger::Error << "Error while applying the update received over XFR for "<<zoneName<<", skipping the update: "<< e.what() <<endl;
     }
-    refresh = std::max(refreshFromConf ? refreshFromConf :  newZone->getRefresh(), 1U);
   }
 }
index 1a48cfac7be451f60f5b78fe3c6d3643ff332da9..a7517804f0c5b19d733c2db7752dfbeb94ed7bf4 100644 (file)
@@ -12,7 +12,6 @@
 #include "dnssecinfra.hh"
 
 #include "dns_random.hh"
-#include "gss_context.hh"
 
 StatBag S;
 
@@ -20,14 +19,13 @@ int main(int argc, char** argv)
 try
 {
   if(argc < 4) {
-    cerr<<"Syntax: saxfr IP-address port zone [showdetails] [showflags] [unhash] [gss:remote-principal] [tsig:keyname:algo:secret]"<<endl;
+    cerr<<"Syntax: saxfr IP-address port zone [showdetails] [showflags] [unhash] [tsig:keyname:algo:secret]"<<endl;
     exit(EXIT_FAILURE);
   }
 
   bool showdetails=false;
   bool showflags=false;
   bool unhash=false;
-  bool gss=false;
   bool tsig=false;
   TSIGHashEnum tsig_algo;
   DNSName tsig_key;
@@ -43,16 +41,6 @@ try
         showflags=true;
       if (strcmp(argv[i], "unhash") == 0)
         unhash=true;
-      if (strncmp(argv[i], "gss:",4) == 0) {
-        gss=true;
-        tsig=true;
-        tsig_algo=TSIG_GSS;
-        remote_principal = string(argv[i]+4);
-        if (remote_principal.empty()) {
-          cerr<<"Remote principal is required"<<endl;
-          exit(EXIT_FAILURE);
-        }
-      }
       if (strncmp(argv[i], "tsig:",5) == 0) {
         vector<string> parts;
         tsig=true;
@@ -90,75 +78,6 @@ try
   Socket sock(dest.sin4.sin_family, SOCK_STREAM);
   sock.connect(dest);
 
-  if (gss) {
-#ifndef ENABLE_GSS_TSIG
-    cerr<<"No GSS support compiled in"<<endl;
-    exit(EXIT_FAILURE);
-#else
-    string input,output;
-    GssContext gssctx;
-    gssctx.generateLabel(argv[3]);
-    gssctx.setPeerPrincipal(remote_principal);
-
-    while(gssctx.init(input, output) && gssctx.valid() == false) {
-      input="";
-      DNSPacketWriter pwtkey(packet, gssctx.getLabel(), QType::TKEY, QClass::ANY);
-      TKEYRecordContent tkrc;
-      tkrc.d_algo = DNSName("gss-tsig.");
-      tkrc.d_inception = time((time_t*)NULL);
-      tkrc.d_expiration = tkrc.d_inception+15;
-      tkrc.d_mode = 3;
-      tkrc.d_error = 0;
-      tkrc.d_keysize = output.size();
-      tkrc.d_key = output;
-      tkrc.d_othersize = 0;
-      pwtkey.getHeader()->id = dns_random_uint16();
-      pwtkey.startRecord(gssctx.getLabel(), QType::TKEY, 3600, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
-      tkrc.toPacket(pwtkey);
-      pwtkey.commit();
-      for(const string& msg :  gssctx.getErrorStrings()) {
-        cerr<<msg<<endl;
-      }
-
-      len = htons(packet.size());
-      if(sock.write((char *) &len, 2) != 2)
-        throw PDNSException("tcp write failed");
-      sock.writen(string((char*)&packet[0], packet.size()));
-      if(sock.read((char *) &len, 2) != 2)
-        throw PDNSException("tcp read failed");
-
-      len=ntohs(len);
-      std::unique_ptr<char[]> creply(new char[len]);
-      int n=0;
-      int numread;
-      while(n<len) {
-        numread=sock.read(creply.get()+n, len-n);
-        if(numread<0)
-          throw PDNSException("tcp read failed");
-        n+=numread;
-      }
-
-      MOADNSParser mdp(false, string(creply.get(), len));
-       if (mdp.d_header.rcode != 0) {
-         throw PDNSException(string("Remote server refused: ") + std::to_string(mdp.d_header.rcode));
-       }
-       for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
-         if(i->first.d_type != QType::TKEY) continue;
-         // recover TKEY record
-         tkrc = TKEYRecordContent(i->first.d_content->getZoneRepresentation());
-         input = tkrc.d_key;
-       }
-    }
-
-    if (gssctx.valid() == false) {
-      cerr<<"Could not create GSS context"<<endl;
-      exit(EXIT_FAILURE);
-    }
-
-    tsig_key = DNSName(gssctx.getLabel());
-#endif
-  }
-
   DNSPacketWriter pw(packet, DNSName(argv[3]), 252);
 
   pw.getHeader()->id = dns_random_uint16();
index 1279d91ffe599674ddbac8f143fca127204d9823..ac89a2a6d7779e50e3ea3fa6c9b47625867e0fe3 100644 (file)
@@ -7,6 +7,7 @@
 #include "ednsoptions.hh"
 #include "ednssubnet.hh"
 #include "misc.hh"
+#include "proxy-protocol.hh"
 #include "sstuff.hh"
 #include "statbag.hh"
 #include <boost/array.hpp>
@@ -17,9 +18,9 @@
 
 StatBag S;
 
-bool hidettl = false;
+static bool hidettl = false;
 
-string ttl(uint32_t ttl)
+static string ttl(uint32_t ttl)
 {
   if (hidettl)
     return "[ttl]";
@@ -27,16 +28,17 @@ string ttl(uint32_t ttl)
     return std::to_string(ttl);
 }
 
-void usage()
+static void usage()
 {
   cerr << "sdig" << endl;
   cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
           "[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] "
-          "[recurse] [showflags] [tcp] [xpf XPFDATA] [class CLASSNUM]"
+          "[recurse] [showflags] [tcp] [xpf XPFDATA] [class CLASSNUM] "
+          "[proxy UDP(0)/TCP(1) SOURCE-IP-ADDRESS-AND-PORT DESTINATION-IP-ADDRESS-AND-PORT]"
        << endl;
 }
 
-const string nameForClass(uint16_t qclass, uint16_t qtype)
+static const string nameForClass(uint16_t qclass, uint16_t qtype)
 {
   if (qtype == QType::OPT)
     return "IN";
@@ -55,11 +57,13 @@ const string nameForClass(uint16_t qclass, uint16_t qtype)
   }
 }
 
-void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
-  bool dnssec, const boost::optional<Netmask> ednsnm,
-  bool recurse, uint16_t xpfcode, uint16_t xpfversion,
-  uint64_t xpfproto, char* xpfsrc, char* xpfdst,
-  uint16_t qclass)
+static std::unordered_set<uint16_t> s_expectedIDs;
+
+static void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
+                       bool dnssec, const boost::optional<Netmask> ednsnm,
+                       bool recurse, uint16_t xpfcode, uint16_t xpfversion,
+                       uint64_t xpfproto, char* xpfsrc, char* xpfdst,
+                       uint16_t qclass, uint16_t qid)
 {
   DNSPacketWriter pw(packet, DNSName(q), DNSRecordContent::TypeToNumber(t), qclass);
 
@@ -98,11 +102,18 @@ void fillPacket(vector<uint8_t>& packet, const string& q, const string& t,
   if (recurse) {
     pw.getHeader()->rd = true;
   }
+
+  pw.getHeader()->id = htons(qid);
 }
 
-void printReply(const string& reply, bool showflags, bool hidesoadetails)
+static void printReply(const string& reply, bool showflags, bool hidesoadetails)
 {
   MOADNSParser mdp(false, reply);
+  if (!s_expectedIDs.count(ntohs(mdp.d_header.id))) {
+    cout << "ID " << ntohs(mdp.d_header.id) << " was not expected, this response was not meant for us!"<<endl;
+  }
+  s_expectedIDs.erase(ntohs(mdp.d_header.id));
+
   cout << "Reply to question for qname='" << mdp.d_qname.toString()
        << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl;
   cout << "Rcode: " << mdp.d_header.rcode << " ("
@@ -195,6 +206,7 @@ try {
   uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
   char *xpfsrc = NULL, *xpfdst = NULL;
   uint16_t qclass = QClass::IN;
+  string proxyheader;
 
   for (int i = 1; i < argc; i++) {
     if ((string)argv[i] == "--help") {
@@ -254,6 +266,16 @@ try {
         }
         qclass = atoi(argv[++i]);
       }
+      if (strcmp(argv[i], "proxy") == 0) {
+        if(argc < i+4) {
+          cerr<<"proxy needs three arguments"<<endl;
+          exit(EXIT_FAILURE);
+        }
+        bool ptcp = atoi(argv[++i]);
+        ComboAddress src(argv[++i]);
+        ComboAddress dest(argv[++i]);
+        proxyheader = makeProxyHeader(ptcp, src, dest, {});
+      }
     }
   }
 
@@ -288,13 +310,15 @@ try {
   if (doh) {
 #ifdef HAVE_LIBCURL
     vector<uint8_t> packet;
+    s_expectedIDs.insert(0);
     fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
-      xpfproto, xpfsrc, xpfdst, qclass);
+               xpfproto, xpfsrc, xpfdst, qclass, 0);
     MiniCurl mc;
     MiniCurl::MiniCurlHeaders mch;
     mch.insert(std::make_pair("Content-Type", "application/dns-message"));
     mch.insert(std::make_pair("Accept", "application/dns-message"));
     string question(packet.begin(), packet.end());
+    // FIXME: how do we use proxyheader here?
     reply = mc.postURL(argv[1], question, mch);
     printReply(reply, showflags, hidesoadetails);
 #else
@@ -303,14 +327,33 @@ try {
   } else if (fromstdin) {
     std::istreambuf_iterator<char> begin(std::cin), end;
     reply = string(begin, end);
+
+    ComboAddress source, destination;
+    bool wastcp;
+    bool proxy = false;
+    std::vector<ProxyProtocolValue> ignoredValues;
+    ssize_t offset = parseProxyHeader(reply, proxy, source, destination, wastcp, ignoredValues);
+    if (offset && proxy) {
+      cout<<"proxy "<<(wastcp ? "tcp" : "udp")<<" headersize="<<offset<<" source="<<source.toStringWithPort()<<" destination="<<destination.toStringWithPort()<<endl;
+      reply = reply.substr(offset);
+    }
+
+    if (tcp) {
+      reply = reply.substr(2);
+    }
+
     printReply(reply, showflags, hidesoadetails);
   } else if (tcp) {
+    uint16_t counter = 0;
     Socket sock(dest.sin4.sin_family, SOCK_STREAM);
     sock.connect(dest);
+    sock.writen(proxyheader);
     for (const auto& it : questions) {
       vector<uint8_t> packet;
+      s_expectedIDs.insert(counter);
       fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, xpfcode,
-        xpfversion, xpfproto, xpfsrc, xpfdst, qclass);
+                 xpfversion, xpfproto, xpfsrc, xpfdst, qclass, counter);
+      counter++;
 
       uint16_t len = htons(packet.size());
       if (sock.write((const char *)&len, 2) != 2)
@@ -341,10 +384,12 @@ try {
   } else // udp
   {
     vector<uint8_t> packet;
+    s_expectedIDs.insert(0);
     fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
-      xpfproto, xpfsrc, xpfdst, qclass);
+               xpfproto, xpfsrc, xpfdst, qclass, 0);
     string question(packet.begin(), packet.end());
     Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
+    question = proxyheader + question;
     sock.sendTo(question, dest);
     int result = waitForData(sock.getHandle(), 10);
     if (result < 0)
index 65ab81d3521f93594413bec4e62a88ed194842f0..435051a136da4649e871bab2ceda85b2851c3935 100644 (file)
@@ -63,7 +63,7 @@ void doSecPoll(bool first)
     processSecPoll(res, ret, security_status, security_message);
   } catch(const PDNSException &pe) {
     S.set("security-status", security_status);
-    g_log<<Logger::Warning<<"Could not retrieve security status update for '" + pkgv + "' on '"+ query + "': "<<pe.reason<<endl;
+    g_log<<Logger::Warning<<"Failed to retrieve security status update for '" + pkgv + "' on '"+ query + "': "<<pe.reason<<endl;
     return;
   }
 
index 2e53ec0d474c846f902c669aad3f540cc9d069aa..eacaef09287c15ebac1f6afef6f77b0e52536f01 100644 (file)
@@ -47,7 +47,7 @@ void doSecPoll(time_t* last_secpoll)
   boost::replace_all(qstring, "+", "_");
   boost::replace_all(qstring, "~", "_");
 
-  vState state = Indeterminate;
+  vState state = vState::Indeterminate;
   DNSName query(qstring);
   int res = sr.beginResolve(query, QType(QType::TXT), 1, ret);
 
@@ -55,8 +55,8 @@ void doSecPoll(time_t* last_secpoll)
     state = sr.getValidationState();
   }
 
-  if(state == Bogus) {
-    g_log<<Logger::Error<<"Could not retrieve security status update for '" +pkgv+ "' on '"<<query<<"', DNSSEC validation result was Bogus!"<<endl;
+  if(state == vState::Bogus) {
+    g_log<<Logger::Error<<"Failed to retrieve security status update for '" +pkgv+ "' on '"<<query<<"', DNSSEC validation result was Bogus!"<<endl;
     if(g_security_status == 1) // If we were OK, go to unknown
       g_security_status = 0;
     return;
@@ -74,7 +74,7 @@ void doSecPoll(time_t* last_secpoll)
     processSecPoll(res, ret, security_status, security_message);
   } catch(const PDNSException &pe) {
     g_security_status = security_status;
-    g_log<<Logger::Warning<<"Could not retrieve security status update for '" << pkgv << "' on '"<< query << "': "<<pe.reason<<endl;
+    g_log<<Logger::Warning<<"Failed to retrieve security status update for '" << pkgv << "' on '"<< query << "': "<<pe.reason<<endl;
     return;
   }
 
index 31299d7c0a1c9e10a3339291770ed5d39f7b8b47..e798a3b61fe3f2d0f3e2aeec11981a6f8f95b3ef 100644 (file)
 #include "dnsrecords.hh"
 #include "pdnsexception.hh"
 #include "misc.hh"
+#include "secpoll.hh"
 
 bool isReleaseVersion(const std::string &version) {
   return std::count(version.begin(), version.end(), '.') == 2;
 }
 
-void setSecPollToUnknownOnOK(int &secPollStatus) {
+static void setSecPollToUnknownOnOK(int &secPollStatus) {
   if(secPollStatus == 1) // it was ok, now it is unknown
     secPollStatus = 0;
 }
@@ -39,7 +40,7 @@ void processSecPoll(const int res, const std::vector<DNSRecord> &ret, int &secPo
   secPollMessage.clear();
   if (res != 0) { // not NOERROR
     setSecPollToUnknownOnOK(secPollStatus);
-    throw PDNSException("RCODE was not NOERROR but " + RCode::to_s(res));
+    throw PDNSException("RCODE was " + RCode::to_s(res));
   }
 
   if (ret.empty()) { // empty NOERROR... wat?
diff --git a/pdns/shuffle.cc b/pdns/shuffle.cc
new file mode 100644 (file)
index 0000000..1dd1353
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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 <string>
+
+#include "shuffle.hh"
+#include "dns_random.hh"
+#include "dnsparser.hh"
+
+// shuffle, maintaining some semblance of order
+void pdns::shuffle(std::vector<DNSZoneRecord>& rrs)
+{
+  std::vector<DNSZoneRecord>::iterator first, second;
+
+  // We assume the CNAMES are listed first in the ANSWER section and the the other records
+  // and we want to shuffle the other records only
+
+  // First we scan for the first non-CNAME ANSWER record
+  for (first = rrs.begin(); first != rrs.end(); ++first) {
+    if (first->dr.d_place == DNSResourceRecord::ANSWER && first->dr.d_type != QType::CNAME) {
+      break;
+    }
+  }
+  // And then for one past the last ANSWER record
+  for (second = first; second != rrs.end(); ++second)
+    if (second->dr.d_place != DNSResourceRecord::ANSWER)
+      break;
+
+  // Now shuffle the non-CNAME ANSWER records
+  dns_random_engine r;
+  if (second - first > 1) {
+    shuffle(first, second, r);
+  }
+
+  // now shuffle the ADDITIONAL records in the same manner as the ANSWER records
+  for (first = second; first != rrs.end(); ++first) {
+    if (first->dr.d_place == DNSResourceRecord::ADDITIONAL && first->dr.d_type != QType::CNAME) {
+      break;
+    }
+  }
+  for (second = first; second != rrs.end(); ++second) {
+    if (second->dr.d_place != DNSResourceRecord::ADDITIONAL) {
+      break;
+    }
+  }
+
+  if (second - first > 1) {
+    shuffle(first, second, r);
+  }
+  // we don't shuffle the rest
+}
+
+// shuffle, maintaining some semblance of order
+static void shuffle(std::vector<DNSRecord>& rrs)
+{
+  // This shuffles in the same style as the above method, keeping CNAME in the front and RRSIGs at the end
+  std::vector<DNSRecord>::iterator first, second;
+  for (first = rrs.begin(); first != rrs.end(); ++first) {
+    if (first->d_place == DNSResourceRecord::ANSWER && first->d_type != QType::CNAME) {
+      break;
+    }
+  }
+  for (second = first; second != rrs.end(); ++second) {
+    if (second->d_place != DNSResourceRecord::ANSWER || second->d_type == QType::RRSIG) {
+      break;
+    }
+  }
+
+  pdns::dns_random_engine r;
+  if (second - first > 1) {
+    shuffle(first, second, r);
+  }
+
+  // now shuffle the additional records
+  for (first = second; first != rrs.end(); ++first) {
+    if (first->d_place == DNSResourceRecord::ADDITIONAL && first->d_type != QType::CNAME) {
+      break;
+    }
+  }
+  for (second = first; second != rrs.end(); ++second) {
+    if (second->d_place != DNSResourceRecord::ADDITIONAL) {
+      break;
+    }
+  }
+
+  if (second - first > 1) {
+    shuffle(first, second, r);
+  }
+  // we don't shuffle the rest
+}
+
+static uint16_t mapTypesToOrder(uint16_t type)
+{
+  if (type == QType::CNAME)
+    return 0;
+  if (type == QType::RRSIG)
+    return 65535;
+  else
+    return 1;
+}
+
+// make sure rrs is sorted in d_place order to avoid surprises later
+// then shuffle the parts that desire shuffling
+void pdns::orderAndShuffle(vector<DNSRecord>& rrs)
+{
+  std::stable_sort(rrs.begin(), rrs.end(), [](const DNSRecord& a, const DNSRecord& b) {
+    return std::make_tuple(a.d_place, mapTypesToOrder(a.d_type)) < std::make_tuple(b.d_place, mapTypesToOrder(b.d_type));
+  });
+  shuffle(rrs);
+}
diff --git a/pdns/shuffle.hh b/pdns/shuffle.hh
new file mode 100644 (file)
index 0000000..8917204
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 <vector>
+
+struct DNSRecord;
+struct DNSZoneRecord;
+
+namespace pdns
+{
+void shuffle(std::vector<DNSZoneRecord>& rrs);
+void orderAndShuffle(std::vector<DNSRecord>& rrs);
+}
index 83beaa8fd69b206f6678853af309e9b2b7fc6fc8..c129a4193a58313b957d12d52d250837631b81b5 100644 (file)
@@ -3,6 +3,7 @@
 #endif
 #include "signingpipe.hh"
 #include "misc.hh"
+#include "dns_random.hh"
 #include <poll.h>
 
 #include <sys/socket.h>
@@ -196,7 +197,7 @@ void ChunkedSigningPipe::sendRRSetToWorker() // it sounds so socialist!
   rwVect = waitForRW(wantRead, wantWrite, -1); // wait for something to happen
   
   if(wantWrite && !rwVect.second.empty()) {
-    random_shuffle(rwVect.second.begin(), rwVect.second.end()); // pick random available worker
+    shuffle(rwVect.second.begin(), rwVect.second.end(), pdns::dns_random_engine()); // pick random available worker
     auto ptr = d_rrsetToSign.release();
     writen2(*rwVect.second.begin(), &ptr, sizeof(ptr));
     d_rrsetToSign = make_unique<rrset_t>();
@@ -245,7 +246,7 @@ void ChunkedSigningPipe::sendRRSetToWorker() // it sounds so socialist!
   
   if(wantWrite) {  // our optimization above failed, we now wait synchronously
     rwVect = waitForRW(false, wantWrite, -1); // wait for something to happen
-    random_shuffle(rwVect.second.begin(), rwVect.second.end()); // pick random available worker
+    shuffle(rwVect.second.begin(), rwVect.second.end(), pdns::dns_random_engine()); // pick random available worker
     auto ptr = d_rrsetToSign.release();
     writen2(*rwVect.second.begin(), &ptr, sizeof(ptr));
     d_rrsetToSign = make_unique<rrset_t>();
index 8a55e4125ba34e8ebe082c634b982cbfc7b835f7..d2d8892b86810106ef374a98f64ec605f74aa01c 100644 (file)
@@ -34,7 +34,7 @@
 #include "dnsbackend.hh"
 #include "ueberbackend.hh"
 #include "packethandler.hh"
-#include "resolver.hh"
+#include "axfr-retriever.hh"
 #include "logger.hh"
 #include "dns.hh"
 #include "arguments.hh"
 #include "inflighter.cc"
 #include "namespaces.hh"
 #include "common_startup.hh"
+#include "query-local-address.hh"
 
 #include "ixfr.hh"
 
-void CommunicatorClass::addSuckRequest(const DNSName &domain, const ComboAddress& master)
+void CommunicatorClass::addSuckRequest(const DNSName &domain, const ComboAddress& master, bool force)
 {
-  Lock l(&d_lock);
+  std::lock_guard<std::mutex> l(d_lock);
   SuckRequest sr;
   sr.domain = domain;
   sr.master = master;
+  sr.force = force;
   pair<UniQueue::iterator, bool>  res;
 
   res=d_suckdomains.push_back(sr);
@@ -135,12 +137,12 @@ void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, c
         grouped[{x.d_name, x.d_type}].second.push_back(x);
 
       di.backend->startTransaction(domain, -1);
-      for(const auto g : grouped) {
+      for(const auto& g : grouped) {
         vector<DNSRecord> rrset;
         {
           DNSZoneRecord zrr;
-          B.lookup(QType(g.first.second), g.first.first+domain, di.id);
-          while(B.get(zrr)) {
+          di.backend->lookup(QType(g.first.second), g.first.first+domain, di.id);
+          while(di.backend->get(zrr)) {
             zrr.dr.d_name.makeUsRelative(domain);
             rrset.push_back(zrr.dr);
           }
@@ -292,10 +294,10 @@ static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName
 }   
 
 
-void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote)
+void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote, bool force)
 {
   {
-    Lock l(&d_lock);
+    std::lock_guard<std::mutex> l(d_lock);
     if(d_inprogress.count(domain)) {
       return; 
     }
@@ -313,7 +315,7 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote)
     DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper
     bool wrongDomainKind = false;
     // this checks three error conditions & sets wrongDomainKind if we hit the third
-    if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical
+    if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, !force && di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical
       if(wrongDomainKind)
         g_log<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"', not configured as slave"<<endl;
       else
@@ -373,15 +375,13 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote)
         return;
       }
     } else {
-      if(remote.sin4.sin_family == AF_INET && !::arg()["query-local-address"].empty()) {
-        laddr = ComboAddress(::arg()["query-local-address"]);
-      } else if(remote.sin4.sin_family == AF_INET6 && !::arg()["query-local-address6"].empty()) {
-        laddr = ComboAddress(::arg()["query-local-address6"]);
-      } else {
-        bool isv6 = remote.sin4.sin_family == AF_INET6;
-        g_log<<Logger::Error<<"Unable to AXFR, destination address is IPv" << (isv6 ? "6" : "4") << ", but query-local-address"<< (isv6 ? "6" : "") << " is unset!"<<endl;
+      if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
+        bool isV6 = remote.sin4.sin_family == AF_INET6;
+        g_log<<Logger::Error<<"Unable to AXFR, destination address is "<<remote<<" (IPv"<< (isV6 ? "6" : "4") <<
+          ", but that address family is not enabled for outgoing traffic (query-local-address)"<<endl;
         return;
       }
+      laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
     }
 
     bool hadDnssecZone = false;
@@ -634,7 +634,7 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote)
   }
   catch(ResolverException &re) {
     {
-      Lock l(&d_lock);
+      std::lock_guard<std::mutex> l(d_lock);
       // The AXFR probably failed due to a problem on the master server. If SOA-checks against this master
       // still succeed, we would constantly try to AXFR the zone. To avoid this, we add the zone to the list of
       // failed slave-checks. This will suspend slave-checks (and subsequent AXFR) for this zone for some time.
@@ -694,7 +694,7 @@ struct SlaveSenderReceiver
 
   Identifier send(DomainNotificationInfo& dni)
   {
-    random_shuffle(dni.di.masters.begin(), dni.di.masters.end());
+    shuffle(dni.di.masters.begin(), dni.di.masters.end(), pdns::dns_random_engine());
     try {
       return std::make_tuple(dni.di.zone,
                              *dni.di.masters.begin(),
@@ -713,13 +713,10 @@ struct SlaveSenderReceiver
 
   bool receive(Identifier& id, Answer& a)
   {
-    if(d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)))) {
-      return 1;
-    }
-    return 0;
+    return d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)));
   }
 
-  void deliverAnswer(DomainNotificationInfo& dni, const Answer& a, unsigned int usec)
+  void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int usec)
   {
     d_freshness[dni.di.id]=a;
   }
@@ -729,7 +726,7 @@ struct SlaveSenderReceiver
 
 void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote)
 {
-  Lock l(&d_lock);
+  std::lock_guard<std::mutex> l(d_lock);
   DomainInfo ours = di;
   ours.backend = 0;
 
@@ -750,7 +747,7 @@ void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAd
 
 void CommunicatorClass::addTrySuperMasterRequest(const DNSPacket& p)
 {
-  Lock l(&d_lock);
+  std::lock_guard<std::mutex> l(d_lock);
   DNSPacket ours = p;
   if(d_potentialsupermasters.insert(ours).second)
     d_any_sem.post(); // kick the loop!
@@ -766,7 +763,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
   vector<DomainNotificationInfo> sdomains;
   set<DNSPacket, cmp> trysuperdomains;
   {
-    Lock l(&d_lock);
+    std::lock_guard<std::mutex> l(d_lock);
     set<DomainInfo> requeue;
     rdomains.reserve(d_tocheck.size());
     for(const auto& di: d_tocheck) {
@@ -798,7 +795,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
     TSIGRecordContent trc;
     DNSName tsigkeyname;
     dp.getTSIGDetails(&trc, &tsigkeyname);
-    P->trySuperMasterSynchronous(dp, tsigkeyname); // FIXME could use some error loging
+    P->trySuperMasterSynchronous(dp, tsigkeyname); // FIXME could use some error logging
   }
   if(rdomains.empty()) { // if we have priority domains, check them first
     B->getUnfreshSlaveInfos(&rdomains);
@@ -806,7 +803,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
   sdomains.reserve(rdomains.size());
   DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
   {
-    Lock l(&d_lock);
+    std::lock_guard<std::mutex> l(d_lock);
     domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains);
     time_t now = time(0);
 
@@ -867,14 +864,14 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
   if(sdomains.empty())
   {
     if(d_slaveschanged) {
-      Lock l(&d_lock);
+      std::lock_guard<std::mutex> l(d_lock);
       g_log<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already, "<<d_inprogress.size()<<" in progress"<<endl;
     }
     d_slaveschanged = !rdomains.empty();
     return;
   }
   else {
-    Lock l(&d_lock);
+    std::lock_guard<std::mutex> l(d_lock);
     g_log<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<<
       (sdomains.size()>1 ? "" : "s")<<
       " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl;
@@ -900,14 +897,14 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
   }
   g_log<<Logger::Warning<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zone"<<addS(ssr.d_freshness.size())<<", had "<<ifl.getTimeouts()<<" timeout"<<addS(ifl.getTimeouts())<<endl;
 
-  typedef DomainNotificationInfo val_t;
   time_t now = time(0);
-  for(val_t& val :  sdomains) {
+  for(auto& val : sdomains) {
     DomainInfo& di(val.di);
-    DomainInfo tempdi;
     // might've come from the packethandler
-    // Please do not overwrite received DI just to make sure it exists in backend.
     if(!di.backend) {
+      // Do not overwrite received DI just to make sure it exists in backend:
+      // di.masters should contain the picked master (as first entry)!
+      DomainInfo tempdi;
       if (!B->getDomainInfo(di.zone, tempdi)) {
         g_log<<Logger::Warning<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl;
         continue;
@@ -919,7 +916,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
 
     if(!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain
       uint64_t newCount = 1;
-      Lock l(&d_lock);
+      std::lock_guard<std::mutex> l(d_lock);
       const auto failedEntry = d_failedSlaveRefresh.find(di.zone);
       if (failedEntry != d_failedSlaveRefresh.end())
         newCount = d_failedSlaveRefresh[di.zone].first + 1;
@@ -936,7 +933,7 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
     }
 
     {
-      Lock l(&d_lock);
+      std::lock_guard<std::mutex> l(d_lock);
       const auto wasFailedDomain = d_failedSlaveRefresh.find(di.zone);
       if (wasFailedDomain != d_failedSlaveRefresh.end())
         d_failedSlaveRefresh.erase(di.zone);
@@ -949,10 +946,12 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
     }
     catch(...) {}
 
-    uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = sd.serial;
+    uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
+    uint32_t ourserial = sd.serial;
+    const ComboAddress remote = *di.masters.begin();
 
     if(rfc1982LessThan(theirserial, ourserial) && ourserial != 0 && !::arg().mustDo("axfr-lower-serial"))  {
-      g_log<<Logger::Error<<"Domain '"<<di.zone<<"' more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl;
+      g_log<<Logger::Error<<"Domain '" << di.zone << "' more recent than master " << remote.toStringWithPortExcept(53) << ", our serial "<< ourserial<< " > their serial "<< theirserial << endl;
       di.backend->setFresh(di.id);
     }
     else if(hasSOA && theirserial == ourserial) {
@@ -969,38 +968,38 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
         }
       }
       if(! maxInception && ! ssr.d_freshness[di.id].theirInception) {
-        g_log<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh (no DNSSEC), serial is "<<ourserial<<endl;
+        g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked master " << remote.toStringWithPortExcept(53) << ")" << endl;
         di.backend->setFresh(di.id);
       }
       else if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
-        g_log<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh and SOA RRSIGs match, serial is "<<ourserial<<endl;
+        g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh and SOA RRSIGs match, serial is " << ourserial << " (checked master " << remote.toStringWithPortExcept(53) << ")" << endl;
         di.backend->setFresh(di.id);
       }
       else if(maxExpire >= now && ! ssr.d_freshness[di.id].theirInception ) {
-        g_log<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh, master is no longer signed but (some) signatures are still vallid, serial is "<<ourserial<<endl;
+        g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh, master " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still vallid, serial is " << ourserial << endl;
         di.backend->setFresh(di.id);
       }
       else if(maxInception && ! ssr.d_freshness[di.id].theirInception ) {
-        g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master is no longer signed and all signatures have expired, serial is "<<ourserial<<endl;
-        addSuckRequest(di.zone, *di.masters.begin());
+        g_log<<Logger::Warning<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl;
+        addSuckRequest(di.zone, remote);
       }
       else if(dk.doesDNSSEC() && ! maxInception && ssr.d_freshness[di.id].theirInception) {
-        g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master has signed, serial is "<<ourserial<<endl;
-        addSuckRequest(di.zone, *di.masters.begin());
+        g_log<<Logger::Warning<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl;
+        addSuckRequest(di.zone, remote);
       }
       else {
-        g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh, but RRSIGs differ, so DNSSEC is stale, serial is "<<ourserial<<endl;
-        addSuckRequest(di.zone, *di.masters.begin());
+        g_log<<Logger::Warning<<"Domain '"<< di.zone << "' is fresh, but RRSIGs differ on master" << remote.toStringWithPortExcept(53)<<", so DNSSEC is stale, serial is " << ourserial << endl;
+        addSuckRequest(di.zone, remote);
       }
     }
     else {
       if(hasSOA) {
-        g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl;
+        g_log<<Logger::Warning<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl;
       }
       else {
-        g_log<<Logger::Warning<<"Domain '"<< di.zone<<"' is empty, master serial "<<theirserial<<endl;
+        g_log<<Logger::Warning<<"Domain '"<< di.zone << "' is empty, master " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
       }
-      addSuckRequest(di.zone, *di.masters.begin());
+      addSuckRequest(di.zone, remote);
     }
   }
 }
index c304aad47c3c4b1dfd673e4c0bc16ae5fa71863d..db8d44559f91e3b69dfe6ef98bdcddf97c0817eb 100644 (file)
@@ -24,6 +24,7 @@ public:
   virtual ~SNMPAgent()
   {
 #ifdef HAVE_NET_SNMP
+    
     close(d_trapPipe[0]);
     close(d_trapPipe[1]);
 #endif /* HAVE_NET_SNMP */
@@ -33,6 +34,7 @@ public:
   {
 #ifdef HAVE_NET_SNMP
   d_thread = std::thread(&SNMPAgent::worker, this);
+  d_thread.detach();
 #endif /* HAVE_NET_SNMP */
   }
 
index 567db8674a031d6851fdd14c8d23dfdd01dc97df..967efc87f2576dfbc27920611e43defd5fb2caec 100644 (file)
@@ -113,7 +113,7 @@ bool sodIsValidKey(const std::string& key)
 #include <inttypes.h>
 
 namespace anonpdns {
-char B64Decode1(char cInChar)
+static char B64Decode1(char cInChar)
 {
   // The incoming character will be A-Z, a-z, 0-9, +, /, or =.
   // The idea is to quickly determine which grouping the
@@ -178,7 +178,7 @@ char B64Decode1(char cInChar)
   return iIndex;
 }
 
-inline char B64Encode1(unsigned char uc)
+static inline char B64Encode1(unsigned char uc)
 {
   if (uc < 26)
     {
index 5d21667afee87b89f148aebc2150c167467e1d96..27205d5ffdfeba27ea81c29c8df72074c15ad12f 100644 (file)
@@ -105,7 +105,7 @@ struct GetTimeTest
   }
 };
 
-pthread_mutex_t s_testlock=PTHREAD_MUTEX_INITIALIZER;
+std::mutex s_testlock;
 
 struct GetLockUncontendedTest
 {
@@ -116,8 +116,8 @@ struct GetLockUncontendedTest
 
   void operator()() const
   {
-    pthread_mutex_lock(&s_testlock);
-    pthread_mutex_unlock(&s_testlock);
+    s_testlock.lock();
+    s_testlock.unlock();
   }
 };
 
index 0c0eb207aa0901f21fc73d99b9516f5ac258d2ca..8fc9466a436fdcc1ce2e10a89885efc95fe2d6e4 100644 (file)
@@ -37,7 +37,8 @@
 *
 * copied from sqlite 3.3.6 // cmouse
 */
-int pdns_sqlite3_clear_bindings(sqlite3_stmt *pStmt){
+#if SQLITE_VERSION_NUMBER < 3003009
+static int pdns_sqlite3_clear_bindings(sqlite3_stmt *pStmt){
   int i;
   int rc = SQLITE_OK;
   for(i=1; rc==SQLITE_OK && i<=sqlite3_bind_parameter_count(pStmt); i++){
@@ -45,6 +46,7 @@ int pdns_sqlite3_clear_bindings(sqlite3_stmt *pStmt){
   }
   return rc;
 }
+#endif
 
 static string SSQLite3ErrorString(sqlite3 *db)
 {
@@ -232,19 +234,26 @@ std::unique_ptr<SSqlStatement> SSQLite3::prepare(const string& query, int nparam
 
 void SSQLite3::execute(const string& query) {
   char *errmsg;
-  int rc;
-  if (sqlite3_exec(m_pDB, query.c_str(), NULL, NULL, &errmsg) == SQLITE_BUSY) {
+  std::string errstr1;
+  int rc = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
+  if (rc != SQLITE_OK) {
+    errstr1 = errmsg;
+    sqlite3_free(errmsg);
+  }
+  if (rc == SQLITE_BUSY) {
     if (m_in_transaction) {
-      std::string errstr(errmsg);
-      sqlite3_free(errmsg);
-      throw("Failed to execute query: " + errstr);
+      throw SSqlException("Failed to execute query: " + errstr1);
     } else {
-      if ((rc = sqlite3_exec(m_pDB, query.c_str(), NULL, NULL, &errmsg) != SQLITE_OK) && rc != SQLITE_DONE && rc != SQLITE_ROW) {
-        std::string errstr(errmsg);
+      rc = sqlite3_exec(m_pDB, query.c_str(), NULL, NULL, &errmsg);
+      std::string errstr2;
+      if (rc != SQLITE_OK)  {
+        errstr2 = errmsg;
         sqlite3_free(errmsg);
-        throw("Failed to execute query: " + errstr);
+        throw SSqlException("Failed to execute query: " + errstr2);
       }
     }
+  } else if (rc != SQLITE_OK) {
+    throw SSqlException("Failed to execute query: " + errstr1);
   }
 }
 
index 6aad23b1d2fdf951a07780d6fb8b7235d223eeee..521530d6a2661a53136e0da13e3ec1367bd927e1 100644 (file)
@@ -38,6 +38,7 @@
 StatBag::StatBag()
 {
   d_doRings=false;
+  d_allowRedeclare=false;
 }
 
 void StatBag::exists(const string &key)
@@ -97,18 +98,39 @@ string StatBag::getDescrip(const string &item)
   return d_keyDescrips[item];
 }
 
-void StatBag::declare(const string &key, const string &descrip)
+StatType StatBag::getStatType(const string &item)
 {
+  exists(item);
+  return d_statTypes[item];
+}
+
+void StatBag::declare(const string &key, const string &descrip, StatType statType)
+{
+  if(d_stats.count(key)) {
+    if (d_allowRedeclare) {
+      *d_stats[key] = 0;
+      return;
+    }
+    else {
+      throw PDNSException("Attempt to re-declare statbag '"+key+"'");
+    }
+  }
+
   auto i=make_unique<AtomicCounter>(0);
   d_stats[key]=std::move(i);
   d_keyDescrips[key]=descrip;
+  d_statTypes[key]=statType;
 }
 
-void StatBag::declare(const string &key, const string &descrip, StatBag::func_t func)
+void StatBag::declare(const string &key, const string &descrip, StatBag::func_t func, StatType statType)
 {
+  if(d_funcstats.count(key) && !d_allowRedeclare) {
+    throw PDNSException("Attempt to re-declare func statbag '"+key+"'");
+  }
 
   d_funcstats[key]=func;
   d_keyDescrips[key]=descrip;
+  d_statTypes[key]=statType;
 }
 
           
@@ -237,8 +259,8 @@ vector<pair<T, unsigned int> >StatRing<T,Comp>::get() const
 
 void StatBag::registerRingStats(const string& name)
 {
-  declare("ring-" + name + "-size", "Number of entries in the " + name + " ring", [this,name](const std::string&) { return static_cast<uint64_t>(getRingEntriesCount(name)); });
-  declare("ring-" + name + "-capacity", "Maximum number of entries in the " + name + " ring", [this,name](const std::string&) { return static_cast<uint64_t>(getRingSize(name)); });
+  declare("ring-" + name + "-size", "Number of entries in the " + name + " ring", [this,name](const std::string&) { return static_cast<uint64_t>(getRingEntriesCount(name)); }, StatType::gauge);
+  declare("ring-" + name + "-capacity", "Maximum number of entries in the " + name + " ring", [this,name](const std::string&) { return static_cast<uint64_t>(getRingSize(name)); }, StatType::gauge);
 }
 
 void StatBag::declareRing(const string &name, const string &help, unsigned int size)
@@ -250,8 +272,8 @@ void StatBag::declareRing(const string &name, const string &help, unsigned int s
 
 void StatBag::declareComboRing(const string &name, const string &help, unsigned int size)
 {
-  d_comborings.emplace(name, size);
-  d_comborings[name].setHelp(help);
+  d_comboRings.emplace(name, size);
+  d_comboRings[name].setHelp(help);
   registerRingStats(name);
 }
 
@@ -269,9 +291,9 @@ vector<pair<string, unsigned int> > StatBag::getRing(const string &name)
   }
   vector<pair<string, unsigned int> > ret;
 
-  if (d_comborings.count(name)) {
+  if (d_comboRings.count(name)) {
     typedef pair<SComboAddress, unsigned int> stor_t;
-    vector<stor_t> raw =d_comborings[name].get();
+    vector<stor_t> raw =d_comboRings[name].get();
     for(const stor_t& stor :  raw) {
       ret.push_back(make_pair(stor.first.ca.toString(), stor.second));
     }
@@ -295,8 +317,8 @@ void StatBag::resetRing(const string &name)
 {
   if(d_rings.count(name))
     d_rings[name].reset();
-  if(d_comborings.count(name))
-    d_comborings[name].reset();
+  if(d_comboRings.count(name))
+    d_comboRings[name].reset();
   if(d_dnsnameqtyperings.count(name))
     d_dnsnameqtyperings[name].reset();
 }
@@ -305,8 +327,8 @@ void StatBag::resizeRing(const string &name, unsigned int newsize)
 {
   if(d_rings.count(name))
     d_rings[name].resize(newsize);
-  if(d_comborings.count(name))
-    d_comborings[name].resize(newsize);
+  if(d_comboRings.count(name))
+    d_comboRings[name].resize(newsize);
   if(d_dnsnameqtyperings.count(name))
     return d_dnsnameqtyperings[name].resize(newsize);
 }
@@ -316,8 +338,8 @@ uint64_t StatBag::getRingSize(const string &name)
 {
   if(d_rings.count(name))
     return d_rings[name].getSize();
-  if(d_comborings.count(name))
-    return d_comborings[name].getSize();
+  if(d_comboRings.count(name))
+    return d_comboRings[name].getSize();
   if(d_dnsnameqtyperings.count(name))
     return d_dnsnameqtyperings[name].getSize();
   return 0;
@@ -327,8 +349,8 @@ uint64_t StatBag::getRingEntriesCount(const string &name)
 {
   if(d_rings.count(name))
     return d_rings[name].getEntriesCount();
-  if(d_comborings.count(name))
-    return d_comborings[name].getEntriesCount();
+  if(d_comboRings.count(name))
+    return d_comboRings[name].getEntriesCount();
   if(d_dnsnameqtyperings.count(name))
     return d_dnsnameqtyperings[name].getEntriesCount();
   return 0;
@@ -338,8 +360,8 @@ string StatBag::getRingTitle(const string &name)
 {
   if(d_rings.count(name))
     return d_rings[name].getHelp();
-  if(d_comborings.count(name))
-    return d_comborings[name].getHelp();
+  if(d_comboRings.count(name))
+    return d_comboRings[name].getHelp();
   if(d_dnsnameqtyperings.count(name))
     return d_dnsnameqtyperings[name].getHelp();
   return "";
@@ -350,7 +372,7 @@ vector<string>StatBag::listRings()
   vector<string> ret;
   for(auto i=d_rings.begin();i!=d_rings.end();++i)
     ret.push_back(i->first);
-  for(auto i=d_comborings.begin();i!=d_comborings.end();++i)
+  for(auto i=d_comboRings.begin();i!=d_comboRings.end();++i)
     ret.push_back(i->first);
   for(const auto &i : d_dnsnameqtyperings)
     ret.push_back(i.first);
@@ -360,7 +382,7 @@ vector<string>StatBag::listRings()
 
 bool StatBag::ringExists(const string &name)
 {
-  return d_rings.count(name) || d_comborings.count(name) || d_dnsnameqtyperings.count(name);
+  return d_rings.count(name) || d_comboRings.count(name) || d_dnsnameqtyperings.count(name);
 }
 
 void StatBag::blacklist(const string& str) {
index 0ee875917c22a4ba982c003e4b2769cf2956cdae..611712c223a8bfa6ab05c3b48dc83be0eecc3aa7 100644 (file)
@@ -37,7 +37,7 @@ class StatRing
 {
 public:
   StatRing(unsigned int size=10000);
-  // Some older C++ libs have trouble emplacing without a copy-contructor, so provide one
+  // Some older C++ libs have trouble emplacing without a copy-constructor, so provide one
   StatRing(const StatRing &);
   StatRing & operator=(const StatRing &) = delete;
   
@@ -62,19 +62,25 @@ private:
   string d_help;
 };
 
+enum class StatType : uint8_t {
+  counter = 1,
+  gauge = 2,
+};
 
 //! use this to gather and query statistics
 class StatBag
 {
   map<string, std::unique_ptr<AtomicCounter>> d_stats;
   map<string, string> d_keyDescrips;
+  map<string, StatType> d_statTypes;
   map<string,StatRing<string, CIStringCompare> >d_rings;
-  map<string,StatRing<SComboAddress> >d_comborings;
+  map<string,StatRing<SComboAddress> >d_comboRings;
   map<string,StatRing<std::tuple<DNSName, QType> > >d_dnsnameqtyperings;
   typedef boost::function<uint64_t(const std::string&)> func_t;
   typedef map<string, func_t> funcstats_t;
   funcstats_t d_funcstats;
   bool d_doRings;
+
   std::set<string> d_blacklist;
 
   void registerRingStats(const string& name);
@@ -82,8 +88,8 @@ class StatBag
 public:
   StatBag(); //!< Naked constructor. You need to declare keys before this class becomes useful
   ~StatBag();
-  void declare(const string &key, const string &descrip=""); //!< Before you can store or access a key, you need to declare it
-  void declare(const string &key, const string &descrip, func_t func); //!< Before you can store or access a key, you need to declare it
+  void declare(const string &key, const string &descrip="", StatType statType=StatType::counter); //!< Before you can store or access a key, you need to declare it
+  void declare(const string &key, const string &descrip, func_t func, StatType statType); //!< Before you can store or access a key, you need to declare it
 
   void declareRing(const string &name, const string &title, unsigned int size=10000);
   void declareComboRing(const string &name, const string &help, unsigned int size=10000);
@@ -102,9 +108,9 @@ public:
   void ringAccount(const char* name, const ComboAddress &item)
   {
     if(d_doRings) {
-      if(!d_comborings.count(name))
-       throw runtime_error("Attempting to account to non-existent comboring '"+std::string(name)+"'");
-      d_comborings[name].account(item);
+      if(!d_comboRings.count(name))
+       throw runtime_error("Attempting to account to non-existent comboRing '"+std::string(name)+"'");
+      d_comboRings[name].account(item);
     }
   }
   void ringAccount(const char* name, const DNSName &dnsname, const QType &qtype)
@@ -131,6 +137,7 @@ public:
   string directory(); //!< Returns a list of all data stored
   vector<string> getEntries(); //!< returns a vector with datums (items)
   string getDescrip(const string &item); //!< Returns the description of this datum/item
+  StatType getStatType(const string &item); //!< Returns the stats type for the metrics endpoint
   void exists(const string &key); //!< call this function to throw an exception in case a key does not exist
   inline void deposit(const string &key, int value); //!< increment the statistics behind this key by value amount
   inline void inc(const string &key); //!< increase this key's value by one
@@ -141,6 +148,8 @@ public:
   string getValueStr(const string &key); //!< read a value behind a key, and return it as a string
   string getValueStrZero(const string &key); //!< read a value behind a key, and return it as a string, and zero afterwards
   void blacklist(const string &str);
+
+  bool d_allowRedeclare; // only set this true during tests, never in production code
 };
 
 inline void StatBag::deposit(const string &key, int value)
index 1da9644a2d9b7bebac56d2eedecba2d558ad2099..27954eead18e5fcc63538d35a84beebb3daf15fe 100644 (file)
@@ -15,7 +15,7 @@ ArgvMap &arg()
   return theArg;
 }
 
-void usage() {
+static void usage() {
   cerr<<"stubquery"<<endl;
   cerr<<"Syntax: stubquery QUESTION [QUESTION-TYPE]"<<endl;
 }
index 985d96ea55004c909abbfa0145011ab0f6651d35..98cd70fe546d61fbf84fa5e30d0838b2d07bcd89 100644 (file)
@@ -23,7 +23,7 @@
 // s_resolversForStub contains the ComboAddresses that are used by
 // stubDoResolve
 static vector<ComboAddress> s_resolversForStub;
-static pthread_rwlock_t s_resolversForStubLock = PTHREAD_RWLOCK_INITIALIZER;
+static ReadWriteLock s_resolversForStubLock;
 static bool s_stubResolvConfigured = false;
 
 // /etc/resolv.conf last modification time
index b24a52d60f93e98db1ef6b6be1fa13d516002cc4..8ca13cd752f346cfff23b80504ed11df9fc83236 100644 (file)
@@ -53,6 +53,7 @@ unsigned int SyncRes::s_maxnegttl;
 unsigned int SyncRes::s_maxbogusttl;
 unsigned int SyncRes::s_maxcachettl;
 unsigned int SyncRes::s_maxqperq;
+unsigned int SyncRes::s_maxnsaddressqperq;
 unsigned int SyncRes::s_maxtotusec;
 unsigned int SyncRes::s_maxdepth;
 unsigned int SyncRes::s_minimumTTL;
@@ -84,6 +85,7 @@ uint8_t SyncRes::s_ecsipv6limit;
 uint8_t SyncRes::s_ecsipv4cachelimit;
 uint8_t SyncRes::s_ecsipv6cachelimit;
 
+bool SyncRes::s_doIPv4;
 bool SyncRes::s_doIPv6;
 bool SyncRes::s_nopacketcache;
 bool SyncRes::s_rootNXTrust;
@@ -130,21 +132,21 @@ SyncRes::SyncRes(const struct timeval& now) :  d_authzonequeries(0), d_outquerie
 }
 
 /** everything begins here - this is the entry point just after receiving a packet */
-int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret)
+int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret, unsigned int depth)
 {
-  vState state = Indeterminate;
+  vState state = vState::Indeterminate;
   s_queries++;
   d_wasVariable=false;
   d_wasOutOfBand=false;
 
   if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
-    d_queryValidationState = Insecure; // this could fool our stats into thinking a validation took place
+    d_queryValidationState = vState::Insecure; // this could fool our stats into thinking a validation took place
     return 0;                          // so do check before updating counters (we do now)
   }
 
   auto qtypeCode = qtype.getCode();
   /* rfc6895 section 3.1 */
-  if ((qtypeCode >= 128 && qtypeCode <= 254) || qtypeCode == QType::RRSIG || qtypeCode == QType::NSEC3 || qtypeCode == QType::OPT || qtypeCode == 65535) {
+  if (qtypeCode == 0 || (qtypeCode >= 128 && qtypeCode <= 254) || qtypeCode == QType::RRSIG || qtypeCode == QType::NSEC3 || qtypeCode == QType::OPT || qtypeCode == 65535) {
     return -1;
   }
 
@@ -154,11 +156,11 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
     return -1;
 
   set<GetBestNSAnswer> beenthere;
-  int res=doResolve(qname, qtype, ret, 0, beenthere, state);
+  int res=doResolve(qname, qtype, ret, depth, beenthere, state);
   d_queryValidationState = state;
 
   if (shouldValidate()) {
-    if (d_queryValidationState != Indeterminate) {
+    if (d_queryValidationState != vState::Indeterminate) {
       g_stats.dnssecValidations++;
     }
     increaseDNSSECStateCounter(d_queryValidationState);
@@ -405,10 +407,10 @@ bool SyncRes::doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSR
   return doOOBResolve(iter->second, qname, qtype, ret, res);
 }
 
-bool SyncRes::isForwardOrAuth(const DNSName &qname) const {
+bool SyncRes::isRecursiveForwardOrAuth(const DNSName &qname) const {
   DNSName authname(qname);
   domainmap_t::const_iterator iter = getBestAuthZone(&authname);
-  return iter != t_sstorage.domainmap->end();
+  return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || iter->second.shouldRecurse());
 }
 
 uint64_t SyncRes::doEDNSDump(int fd)
@@ -479,7 +481,7 @@ uint64_t SyncRes::doDumpThrottleMap(int fd)
   uint64_t count=0;
 
   const auto& throttleMap = t_sstorage.throttle.getThrottleMap();
-  for(const auto i : throttleMap)
+  for(const auto& i : throttleMap)
   {
     count++;
     char tmp[26];
@@ -640,7 +642,25 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
 
 int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state) {
 
-  if (!getQNameMinimization() || isForwardOrAuth(qname)) {
+  string prefix = d_prefix;
+  prefix.append(depth, ' ');
+  auto luaconfsLocal = g_luaconfs.getLocal();
+
+  /* Apply qname (including CNAME chain) filtering policies */
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+    if (luaconfsLocal->dfe.getQueryPolicy(qname, d_discardedPolicies, d_appliedPolicy)) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      bool done = false;
+      int rcode = RCode::NoError;
+      handlePolicyHit(prefix, qname, qtype, ret, done, rcode, depth);
+      if (done) {
+        return rcode;
+      }
+    }
+  }
+
+  // In the auth or recursive forward case, it does not make sense to do qname-minimization
+  if (!getQNameMinimization() || isRecursiveForwardOrAuth(qname)) {
     return doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
   }
 
@@ -660,8 +680,6 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
   // moves to three labels per iteration after three iterations.
 
   DNSName child;
-  string prefix = d_prefix;
-  prefix.append(depth, ' ');
   prefix.append(string("QM ") + qname.toString() + "|" + qtype.getName());
 
   QLOG("doResolve");
@@ -670,17 +688,30 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
   vector<DNSRecord> retq;
   bool old = setCacheOnly(true);
   bool fromCache = false;
-  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth + 1, beenthere, state, &fromCache);
+  // For cache peeking, we tell doResolveNoQNameMinimization not to consider the (non-recursive) forward case.
+  // Otherwise all queries in a forward domain will be forwarded, while we want to consult the cache.
+  // The out-of-band cases for doResolveNoQNameMinimization() should be reconsidered and redone some day.
+  int res = doResolveNoQNameMinimization(qname, qtype, retq, depth, beenthere, state, &fromCache, nullptr, false);
   setCacheOnly(old);
   if (fromCache) {
     QLOG("Step0 Found in cache");
+    if (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && (d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NXDOMAIN || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NODATA)) {
+      ret.clear();
+    }
     ret.insert(ret.end(), retq.begin(), retq.end());
+
     return res;
   }
   QLOG("Step0 Not cached");
 
   const unsigned int qnamelen = qname.countLabels();
 
+  DNSName fwdomain(qname);
+  const bool forwarded = getBestAuthZone(&fwdomain) != t_sstorage.domainmap->end();
+  if (forwarded) {
+    QLOG("Step0 qname is in a forwarded domain " << fwdomain);
+  }
+
   for (unsigned int i = 0; i <= qnamelen; ) {
 
     // Step 1
@@ -694,19 +725,29 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
     for (int tries = 0; tries < 2 && bestns.empty(); ++tries) {
       bool flawedNSSet = false;
       set<GetBestNSAnswer> beenthereIgnored;
-      getBestNSFromCache(nsdomain, qtype, bestns, &flawedNSSet, depth + 1, beenthereIgnored);
+      getBestNSFromCache(nsdomain, qtype, bestns, &flawedNSSet, depth, beenthereIgnored, boost::make_optional(forwarded, fwdomain));
+      if (forwarded) {
+        break;
+      }
     }
 
     if (bestns.size() == 0) {
-      // Something terrible is wrong
-      QLOG("Step1 No ancestor found return ServFail");
-      return RCode::ServFail;
+      if (!forwarded) {
+        // Something terrible is wrong
+        QLOG("Step1 No ancestor found return ServFail");
+        return RCode::ServFail;
+      }
+      child = fwdomain;
+    } else {
+      QLOG("Step1 Ancestor from cache is " << bestns[0].d_name);
+      if (forwarded) {
+        child = bestns[0].d_name.isPartOf(fwdomain) ? bestns[0].d_name : fwdomain;
+        QLOG("Step1 Final Ancestor (using forwarding info) is " << child);
+      } else {
+        child = bestns[0].d_name;
+      }
     }
 
-    const DNSName& ancestor(bestns[0].d_name);
-    QLOG("Step1 Ancestor from cache is " << ancestor.toString());
-    child = ancestor;
-
     unsigned int targetlen = std::min(child.countLabels() + (i > 3 ? 3 : 1), qnamelen);
 
     for (; i <= qnamelen; i++) {
@@ -722,7 +763,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
       // Step 3 resolve
       if (child == qname) {
         QLOG("Step3 Going to do final resolve");
-        res = doResolveNoQNameMinimization(qname, qtype, ret, depth + 1, beenthere, state);
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, state);
         QLOG("Step3 Final resolve: " << RCode::to_s(res) << "/" << ret.size());
         return res;
       }
@@ -731,7 +772,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
       QLOG("Step4 Resolve A for child");
       retq.resize(0);
       StopAtDelegation stopAtDelegation = Stop;
-      res = doResolveNoQNameMinimization(child, QType::A, retq, depth + 1, beenthere, state, NULL, &stopAtDelegation);
+      res = doResolveNoQNameMinimization(child, QType::A, retq, depth, beenthere, state, NULL, &stopAtDelegation);
       QLOG("Step4 Resolve A result is " << RCode::to_s(res) << "/" << retq.size() << "/" << stopAtDelegation);
       if (stopAtDelegation == Stopped) {
         QLOG("Delegation seen, continue at step 1");
@@ -742,7 +783,10 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
         // Case 5: unexpected answer
         QLOG("Step5: other rcode, last effort final resolve");
         setQNameMinimization(false);
-        res = doResolveNoQNameMinimization(qname, qtype, ret, depth + 1, beenthere, state);
+        // We might have hit a depth level check, but we still want to allow some recursion levels in the fallback
+        // no-qname-minimization case. This has the effect that a qname minimization fallback case might reach 150% of
+        // maxdepth.
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth/2, beenthere, state);
 
         if(res == RCode::NoError) {
           s_qnameminfallbacksuccess++;
@@ -770,7 +814,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
  * \param stopAtDelegation if non-nullptr and pointed-to value is Stop requests the callee to stop at a delegation, if so pointed-to value is set to Stopped
  * \return DNS RCODE or -1 (Error)
  */
-int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool *fromCache, StopAtDelegation *stopAtDelegation)
+int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool *fromCache, StopAtDelegation *stopAtDelegation, bool considerforwards)
 {
   string prefix;
   if(doLog()) {
@@ -780,11 +824,13 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
 
   LOG(prefix<<qname<<": Wants "<< (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing, "<<(d_requireAuthData ? "" : "NO ")<<"auth data in query for "<<qtype.getName()<<endl);
 
-  state = Indeterminate;
-
-  if(s_maxdepth && depth > s_maxdepth)
-    throw ImmediateServFailException("More than "+std::to_string(s_maxdepth)+" (max-recursion-depth) levels of recursion needed while resolving "+qname.toLogString());
+  state = vState::Indeterminate;
 
+  if (s_maxdepth && depth > s_maxdepth) {
+    string msg = "More than " + std::to_string(s_maxdepth) + " (max-recursion-depth) levels of recursion needed while resolving " + qname.toLogString();
+    LOG(prefix << qname << ": " << msg << endl);
+    throw ImmediateServFailException(msg);
+  }
   int res=0;
 
   // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
@@ -802,7 +848,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
             *fromCache = d_wasOutOfBand;
           return res;
         }
-        else {
+        else if (considerforwards) {
           const vector<ComboAddress>& servers = iter->second.d_servers;
           const ComboAddress remoteIP = servers.front();
           LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
@@ -847,22 +893,59 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
       }
     }
 
-    if(!d_skipCNAMECheck && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
+    if (doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
       d_wasOutOfBand = wasAuthZone;
+      // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
+      // are in QM Step0) we might have a CNAME but not the corresponding target.
+      // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+      // we will get the records from the cache, resulting in a small overhead.
+      // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+      // RPZ rules will not be evaluated anymore (we already matched).
+      const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+
+      if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+        *fromCache = true;
+      }
+      /* Apply Post filtering policies */
+
+      if (d_wantsRPZ && !stoppedByPolicyHit) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+          if (done && fromCache) {
+            *fromCache = true;
+          }
+        }
+      }
+
       return res;
     }
 
-    if(doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
+    if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
       // we done
       d_wasOutOfBand = wasAuthZone;
-      if (fromCache)
+      if (fromCache) {
         *fromCache = true;
+      }
+
+      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+        auto luaLocal = g_luaconfs.getLocal();
+        if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          bool done = false;
+          handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+        }
+      }
+
       return res;
     }
   }
 
-  if(d_cacheonly)
+  if (d_cacheonly) {
     return 0;
+  }
 
   LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype.getName()<<"', trying to find an appropriate NS record"<<endl);
 
@@ -883,10 +966,23 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
 
   state = getValidationStatus(qname, false);
 
-  LOG(prefix<<qname<<": initial validation status for "<<qname<<" is "<<vStates[state]<<endl);
+  LOG(prefix<<qname<<": initial validation status for "<<qname<<" is "<<state<<endl);
+
+  res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation);
 
-  if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation)))
+  /* Apply Post filtering policies */
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+    auto luaLocal = g_luaconfs.getLocal();
+    if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      bool done = false;
+      handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+    }
+  }
+
+  if (!res) {
     return 0;
+  }
 
   LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
 
@@ -913,7 +1009,7 @@ struct speedOrderCA
 
 /** This function explicitly goes out for A or AAAA addresses
 */
-vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly)
+vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS)
 {
   typedef vector<DNSRecord> res_t;
   typedef vector<ComboAddress> ret_t;
@@ -922,14 +1018,15 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   bool oldCacheOnly = setCacheOnly(cacheOnly);
   bool oldRequireAuthData = d_requireAuthData;
   bool oldValidationRequested = d_DNSSECValidationRequested;
+  const unsigned int startqueries = d_outqueries;
   d_requireAuthData = false;
   d_DNSSECValidationRequested = false;
 
   try {
-    vState newState = Indeterminate;
+    vState newState = vState::Indeterminate;
     res_t resv4;
     // If IPv4 ever becomes second class, we should revisit this
-    if (doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
+    if (s_doIPv4 && doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
       for (auto const &i : resv4) {
         if (i.d_type == QType::A) {
           if (auto rec = getRR<ARecordContent>(i)) {
@@ -938,10 +1035,10 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
         }
       }
     }
-    if (s_doIPv6) {
+    if (s_doIPv6) { // s_doIPv6 **IMPLIES** pdns::isQueryLocalAddressFamilyEnabled(AF_INET6) returned true
       if (ret.empty()) {
         // We did not find IPv4 addresses, try to get IPv6 ones
-        newState = Indeterminate;
+        newState = vState::Indeterminate;
         res_t resv6;
         if (doResolve(qname, QType::AAAA, resv6, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
           for (const auto &i : resv6) {
@@ -955,7 +1052,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
         // We have some IPv4 records, don't bother with going out to get IPv6, but do consult the cache
         // Once IPv6 adoption matters, this needs to be revisited
         res_t cset;
-        if (s_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
+        if (g_recCache->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote, d_routingTag) > 0) {
           for (const auto &i : cset) {
             if (i.d_ttl > (unsigned int)d_now.tv_sec ) {
               if (auto rec = getRR<AAAARecordContent>(i)) {
@@ -972,6 +1069,10 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
        of a NS and keep processing the current query */
   }
 
+  if (ret.empty() && d_outqueries > startqueries) {
+    // We did 1 or more outgoing queries to resolve this NS name but returned empty handed
+    addressQueriesForNS++;
+  }
   d_requireAuthData = oldRequireAuthData;
   d_DNSSECValidationRequested = oldValidationRequested;
   setCacheOnly(oldCacheOnly);
@@ -990,7 +1091,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   t_sstorage.nsSpeeds[qname].purge(speeds);
 
   if(ret.size() > 1) {
-    random_shuffle(ret.begin(), ret.end());
+    shuffle(ret.begin(), ret.end(), pdns::dns_random_engine());
     speedOrderCA so(speeds);
     stable_sort(ret.begin(), ret.end(), so);
 
@@ -1015,7 +1116,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   return ret;
 }
 
-void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere)
+void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain)
 {
   string prefix;
   DNSName subdomain(qname);
@@ -1026,22 +1127,31 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
   bestns.clear();
   bool brokeloop;
   do {
+    if (cutOffDomain && (subdomain == *cutOffDomain || !subdomain.isPartOf(*cutOffDomain))) {
+      break;
+    }
     brokeloop=false;
     LOG(prefix<<qname<<": Checking if we have NS in cache for '"<<subdomain<<"'"<<endl);
     vector<DNSRecord> ns;
     *flawedNSSet = false;
 
-    if(s_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_cacheRemote) > 0) {
+    if(g_recCache->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_cacheRemote, d_routingTag) > 0) {
       bestns.reserve(ns.size());
 
       for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
         if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
           vector<DNSRecord> aset;
+          QType nsqt{QType::ADDR};
+          if (s_doIPv4 && !s_doIPv6) {
+            nsqt = QType::A;
+          } else if (!s_doIPv4 && s_doIPv6) {
+            nsqt = QType::AAAA;
+          }
 
           const DNSRecord& dr=*k;
          auto nrr = getRR<NSRecordContent>(dr);
-          if(nrr && (!nrr->getNS().isPartOf(subdomain) || s_RC->get(d_now.tv_sec, nrr->getNS(), s_doIPv6 ? QType(QType::ADDR) : QType(QType::A),
-                                                                    false, doLog() ? &aset : 0, d_cacheRemote) > 5)) {
+          if(nrr && (!nrr->getNS().isPartOf(subdomain) || g_recCache->get(d_now.tv_sec, nrr->getNS(), nsqt,
+                                                                    false, doLog() ? &aset : 0, d_cacheRemote, d_routingTag) > 5)) {
             bestns.push_back(dr);
             LOG(prefix<<qname<<": NS (with ip, or non-glue) in cache for '"<<subdomain<<"' -> '"<<nrr->getNS()<<"'"<<endl);
             LOG(prefix<<qname<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
@@ -1092,11 +1202,11 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
     if(subdomain.isRoot() && !brokeloop) {
       // We lost the root NS records
       primeHints();
-      primeRootNSZones(g_dnssecmode != DNSSECMode::Off);
+      primeRootNSZones(g_dnssecmode != DNSSECMode::Off, depth);
       LOG(prefix<<qname<<": reprimed the root"<<endl);
       /* let's prevent an infinite loop */
       if (!d_updatingRootNS) {
-        getRootNS(d_now, d_asyncResolve);
+        getRootNS(d_now, d_asyncResolve, depth);
       }
     }
   } while(subdomain.chopOff());
@@ -1120,49 +1230,99 @@ SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) co
 /** doesn't actually do the work, leaves that to getBestNSFromCache */
 DNSName SyncRes::getBestNSNamesFromCache(const DNSName &qname, const QType& qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere)
 {
-  DNSName authdomain(qname);
+  string prefix;
+  if (doLog()) {
+    prefix = d_prefix;
+    prefix.append(depth, ' ');
+  }
+  DNSName authOrForwDomain(qname);
 
-  domainmap_t::const_iterator iter=getBestAuthZone(&authdomain);
-  if(iter!=t_sstorage.domainmap->end()) {
-    if( iter->second.isAuth() )
+  domainmap_t::const_iterator iter = getBestAuthZone(&authOrForwDomain);
+  // We have an auth, forwarder of forwarder-recurse
+  if (iter != t_sstorage.domainmap->end()) {
+    if (iter->second.isAuth()) {
       // this gets picked up in doResolveAt, the empty DNSName, combined with the
       // empty vector means 'we are auth for this zone'
       nsset.insert({DNSName(), {{}, false}});
+      return authOrForwDomain;
+    }
     else {
-      // Again, picked up in doResolveAt. An empty DNSName, combined with a
-      // non-empty vector of ComboAddresses means 'this is a forwarded domain'
-      // This is actually picked up in retrieveAddressesForNS called from doResolveAt.
-      nsset.insert({DNSName(), {iter->second.d_servers, iter->second.shouldRecurse() }});
+      if (iter->second.shouldRecurse()) {
+        // Again, picked up in doResolveAt. An empty DNSName, combined with a
+        // non-empty vector of ComboAddresses means 'this is a forwarded domain'
+        // This is actually picked up in retrieveAddressesForNS called from doResolveAt.
+        nsset.insert({DNSName(), {iter->second.d_servers, true }});
+        return authOrForwDomain;
+      }
     }
-    return authdomain;
   }
 
-  DNSName subdomain(qname);
+  // We might have a (non-recursive) forwarder, but maybe the cache already contains
+  // a better NS
   vector<DNSRecord> bestns;
-  getBestNSFromCache(subdomain, qtype, bestns, flawedNSSet, depth, beenthere);
+  DNSName nsFromCacheDomain(g_rootdnsname);
+  getBestNSFromCache(qname, qtype, bestns, flawedNSSet, depth, beenthere);
+
+  // Pick up the auth domain
+  for (const auto& k : bestns) {
+    const auto nsContent = getRR<NSRecordContent>(k);
+    if (nsContent) {
+      nsFromCacheDomain = k.d_name;
+      break;
+    }
+  }
+
+  if (iter != t_sstorage.domainmap->end()) {
+    if (doLog()) {
+      LOG(prefix << qname << " authOrForwDomain: " << authOrForwDomain << " nsFromCacheDomain: " << nsFromCacheDomain << " isPartof: " << authOrForwDomain.isPartOf(nsFromCacheDomain) << endl);
+    }
 
-  for(auto k=bestns.cbegin() ; k != bestns.cend(); ++k) {
+    // If the forwarder is better or equal to what's found in the cache, use forwarder. Note that name.isPartOf(name).
+    // So queries that get NS for authOrForwDomain itself go to the forwarder
+    if (authOrForwDomain.isPartOf(nsFromCacheDomain)) {
+      if (doLog()) {
+        LOG(prefix << qname << ": using forwarder as NS" << endl);
+      }
+      nsset.insert({DNSName(), {iter->second.d_servers, false }});
+      return authOrForwDomain;
+    } else {
+      if (doLog()) {
+        LOG(prefix << qname << ": using NS from cache" << endl);
+      }
+    }
+  }
+  for (auto k = bestns.cbegin(); k != bestns.cend(); ++k) {
     // The actual resolver code will not even look at the ComboAddress or bool
     const auto nsContent = getRR<NSRecordContent>(*k);
     if (nsContent) {
       nsset.insert({nsContent->getNS(), {{}, false}});
-      if(k==bestns.cbegin())
-        subdomain=k->d_name;
     }
   }
-  return subdomain;
+  return nsFromCacheDomain;
 }
 
 void SyncRes::updateValidationStatusInCache(const DNSName &qname, const QType& qt, bool aa, vState newState) const
 {
-  if (newState == Bogus) {
-    s_RC->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, aa, newState, s_maxbogusttl + d_now.tv_sec);
+  if (newState == vState::Bogus) {
+    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, s_maxbogusttl + d_now.tv_sec);
   }
   else {
-    s_RC->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, aa, newState, boost::none);
+    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, boost::none);
   }
 }
 
+static bool scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& records)
+{
+  for (const auto& record: records) {
+    if (record.d_type == QType::CNAME && record.d_place == DNSResourceRecord::ANSWER) {
+      if (name == record.d_name) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse)
 {
   string prefix;
@@ -1187,7 +1347,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
 
   LOG(prefix<<qname<<": Looking for CNAME cache hit of '"<<qname<<"|CNAME"<<"'"<<endl);
   /* we don't require auth data for forward-recurse lookups */
-  if (s_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
+  if (g_recCache->get(d_now.tv_sec, qname, QType(QType::CNAME), !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
     foundName = qname;
     foundQT = QType(QType::CNAME);
   }
@@ -1204,7 +1364,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
       if (dnameName == qname && qtype != QType::DNAME) { // The client does not want a DNAME, but we've reached the QNAME already. So there is no match
         break;
       }
-      if (s_RC->get(d_now.tv_sec, dnameName, QType(QType::DNAME), !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
+      if (g_recCache->get(d_now.tv_sec, dnameName, QType(QType::DNAME), !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
         foundName = dnameName;
         foundQT = QType(QType::DNAME);
         break;
@@ -1216,7 +1376,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
     LOG(prefix<<qname<<": No CNAME or DNAME cache hit of '"<< qname <<"' found"<<endl);
     return false;
   }
-
+  
   for(auto const &record : cset) {
     if (record.d_class != QClass::IN) {
       continue;
@@ -1224,7 +1384,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
 
     if(record.d_ttl > (unsigned int) d_now.tv_sec) {
 
-      if (!wasAuthZone && shouldValidate() && (wasAuth || wasForwardRecurse) && state == Indeterminate && d_requireAuthData) {
+      if (!wasAuthZone && shouldValidate() && (wasAuth || wasForwardRecurse) && state == vState::Indeterminate && d_requireAuthData) {
         /* This means we couldn't figure out the state when this entry was cached,
            most likely because we hadn't computed the zone cuts yet. */
         /* make sure they are computed before validating */
@@ -1236,12 +1396,12 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
         computeZoneCuts(subdomain, g_rootdnsname, depth);
 
         vState recordState = getValidationStatus(foundName, false);
-        if (recordState == Secure) {
-          LOG(prefix<<qname<<": got Indeterminate state from the "<<foundQT.getName()<<" cache, validating.."<<endl);
+        if (recordState == vState::Secure) {
+          LOG(prefix<<qname<<": got vState::Indeterminate state from the "<<foundQT.getName()<<" cache, validating.."<<endl);
           state = SyncRes::validateRecordsWithSigs(depth, foundName, foundQT, foundName, cset, signatures);
-          if (state != Indeterminate) {
-            LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, new validation result is "<<vStates[state]<<endl);
-            if (state == Bogus) {
+          if (state != vState::Indeterminate) {
+            LOG(prefix<<qname<<": got vState::Indeterminate state from the CNAME cache, new validation result is "<<state<<endl);
+            if (state == vState::Bogus) {
               capTTL = s_maxbogusttl;
             }
             updateValidationStatusInCache(foundName, foundQT, wasAuth, state);
@@ -1249,7 +1409,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
         }
       }
 
-      LOG(prefix<<qname<<": Found cache "<<foundQT.getName()<<" hit for '"<< foundName << "|"<<foundQT.getName()<<"' to '"<<record.d_content->getZoneRepresentation()<<"', validation state is "<<vStates[state]<<endl);
+      LOG(prefix<<qname<<": Found cache "<<foundQT.getName()<<" hit for '"<< foundName << "|"<<foundQT.getName()<<"' to '"<<record.d_content->getZoneRepresentation()<<"', validation state is "<<state<<endl);
 
       DNSRecord dr = record;
       dr.d_ttl -= d_now.tv_sec;
@@ -1311,6 +1471,11 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
         return true;
       }
 
+      if (qtype == QType::DS || qtype == QType::DNSKEY) {
+        res = 0;
+        return true;
+      }
+
       // We have a DNAME _or_ CNAME cache hit and the client wants something else than those two.
       // Let's find the answer!
       if (foundQT == QType::CNAME) {
@@ -1321,10 +1486,25 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
         newTarget = cnameContent->getTarget();
       }
 
+      if (qname == newTarget) {
+        string msg = "got a CNAME referral (from cache) to self";
+        LOG(prefix<<qname<<": "<<msg<<endl);
+        throw ImmediateServFailException(msg);
+      }
+
+      // Check to see if we already have seen the new target as a previous target
+      if (scanForCNAMELoop(newTarget, ret)) {
+        string msg = "got a CNAME referral (from cache) that causes a loop";
+        LOG(prefix<<qname<<": status="<<msg<<endl);
+        throw ImmediateServFailException(msg);
+      }
+
       set<GetBestNSAnswer>beenthere;
-      vState cnameState = Indeterminate;
+      vState cnameState = vState::Indeterminate;
+      // Be aware that going out on the network might be disabled (cache-only), for example because we are in QM Step0,
+      // so you can't trust that a real lookup will have been made.
       res = doResolve(newTarget, qtype, ret, depth+1, beenthere, cnameState);
-      LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the DNAME/CNAME quest: "<<vStates[cnameState]<<endl);
+      LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<state<<" with the state from the DNAME/CNAME quest: "<<cnameState<<endl);
       updateValidationState(state, cnameState);
 
       return true;
@@ -1367,21 +1547,20 @@ static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const ve
 }
 
 /*!
- * Convience function to push the records from records into ret with a new TTL
+ * Convenience 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
+ * \param ret     The vector of DNSRecords that should contain 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);
+static void addTTLModifiedRecords(vector<DNSRecord>& records, const uint32_t ttl, vector<DNSRecord>& ret) {
+  for (auto& rec : records) {
+    rec.d_ttl = ttl;
+    ret.push_back(std::move(rec));
   }
 }
 
-void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry* ne, const DNSName& qname, const QType& qtype, const int res, vState& state, unsigned int depth)
+void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, const QType& qtype, const int res, vState& state, unsigned int depth)
 {
   DNSName subdomain(qname);
   /* if we are retrieving a DS, we only care about the state of the parent zone */
@@ -1391,10 +1570,10 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry* ne,
   computeZoneCuts(subdomain, g_rootdnsname, depth);
 
   tcache_t tcache;
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne->authoritySOA.records);
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne->authoritySOA.signatures);
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne->DNSSECRecords.records);
-  reapRecordsFromNegCacheEntryForValidation(tcache, ne->DNSSECRecords.signatures);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.records);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.signatures);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.records);
+  reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.signatures);
 
   for (const auto& entry : tcache) {
     // this happens when we did store signatures, but passed on the records themselves
@@ -1405,35 +1584,35 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry* ne,
     const DNSName& owner = entry.first.name;
 
     vState recordState = getValidationStatus(owner, false);
-    if (state == Indeterminate) {
+    if (state == vState::Indeterminate) {
       state = recordState;
     }
 
-    if (recordState == Secure) {
+    if (recordState == vState::Secure) {
       recordState = SyncRes::validateRecordsWithSigs(depth, qname, qtype, owner, entry.second.records, entry.second.signatures);
     }
 
-    if (recordState != Indeterminate && recordState != state) {
+    if (recordState != vState::Indeterminate && recordState != state) {
       updateValidationState(state, recordState);
-      if (state != Secure) {
+      if (state != vState::Secure) {
         break;
       }
     }
   }
 
-  if (state == Secure) {
-    vState neValidationState = ne->d_validationState;
-    dState expectedState = res == RCode::NXDomain ? NXDOMAIN : NXQTYPE;
-    dState denialState = getDenialValidationState(*ne, state, expectedState, false);
-    updateDenialValidationState(neValidationState, ne->d_name, state, denialState, expectedState, qtype == QType::DS || expectedState == NXDOMAIN);
+  if (state == vState::Secure) {
+    vState neValidationState = ne.d_validationState;
+    dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
+    dState denialState = getDenialValidationState(ne, state, expectedState, false);
+    updateDenialValidationState(neValidationState, ne.d_name, state, denialState, expectedState, qtype == QType::DS || expectedState == dState::NXDOMAIN);
   }
-  if (state != Indeterminate) {
+  if (state != vState::Indeterminate) {
     /* validation succeeded, let's update the cache entry so we don't have to validate again */
-    boost::optional<uint32_t> capTTD = boost::none;
-    if (state == Bogus) {
+    boost::optional<time_t> capTTD = boost::none;
+    if (state == vState::Bogus) {
       capTTD = d_now.tv_sec + s_maxbogusttl;
     }
-    t_sstorage.negcache.updateValidationStatus(ne->d_name, ne->d_qtype, state, capTTD);
+    g_negCache->updateValidationStatus(ne.d_name, ne.d_qtype, state, capTTD);
   }
 }
 
@@ -1447,38 +1626,38 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
     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)
+  // sqname and sqtype are used contain 'higher' names if we have them (e.g. powerdns.com|SOA when we find a negative entry for doesnotexist.powerdns.com|A)
   DNSName sqname(qname);
   QType sqt(qtype);
   uint32_t sttl=0;
   //  cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
   vState cachedState;
-  const NegCache::NegCacheEntry* ne = nullptr;
+  NegCache::NegCacheEntry ne;
 
   if(s_rootNXTrust &&
-      t_sstorage.negcache.getRootNXTrust(qname, d_now, &ne) &&
-      ne->d_auth.isRoot() &&
+      g_negCache->getRootNXTrust(qname, d_now, ne) &&
+      ne.d_auth.isRoot() &&
       !(wasForwardedOrAuthZone && !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);
+    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;
     giveNegative = true;
-    cachedState = ne->d_validationState;
-  } else if (t_sstorage.negcache.get(qname, qtype, d_now, &ne)) {
+    cachedState = ne.d_validationState;
+  } else if (g_negCache->get(qname, qtype, d_now, ne)) {
     /* If we are looking for a DS, discard NXD if auth == qname
        and ask for a specific denial instead */
-    if (qtype != QType::DS || ne->d_qtype.getCode() || ne->d_auth != qname ||
-        t_sstorage.negcache.get(qname, qtype, d_now, &ne, true))
+    if (qtype != QType::DS || ne.d_qtype.getCode() || ne.d_auth != qname ||
+        g_negCache->get(qname, qtype, d_now, ne, true))
     {
       res = RCode::NXDomain;
-      sttl = ne->d_ttd - d_now.tv_sec;
+      sttl = ne.d_ttd - d_now.tv_sec;
       giveNegative = true;
-      cachedState = ne->d_validationState;
-      if (ne->d_qtype.getCode()) {
-        LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ne->d_auth<<"' for another "<<sttl<<" seconds"<<endl);
+      cachedState = ne.d_validationState;
+      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);
+        LOG(prefix<<qname<<": Entire name '"<<qname<<"' is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
       }
     }
   } else if (s_hardenNXD != HardenNXD::No && !qname.isRoot() && !wasForwardedOrAuthZone) {
@@ -1487,19 +1666,19 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
     negCacheName.prependRawLabel(labels.back());
     labels.pop_back();
     while(!labels.empty()) {
-      if (t_sstorage.negcache.get(negCacheName, QType(0), d_now, &ne, true)) {
-        if (ne->d_validationState == Indeterminate && validationEnabled()) {
-          // LOG(prefix << negCacheName <<  " negatively cached and Indeterminate, trying to validate NXDOMAIN" << endl);
+      if (g_negCache->get(negCacheName, QType(0), d_now, ne, true)) {
+        if (ne.d_validationState == vState::Indeterminate && validationEnabled()) {
+          // LOG(prefix << negCacheName <<  " negatively cached and vState::Indeterminate, trying to validate NXDOMAIN" << endl);
           // ...
           // And get the updated ne struct
-          //t_sstorage.negcache.get(negCacheName, QType(0), d_now, &ne, true);
+          //t_sstorage.negcache.get(negCacheName, QType(0), d_now, ne, true);
         }
-        if ((s_hardenNXD == HardenNXD::Yes && ne->d_validationState != Bogus) || ne->d_validationState == Secure) {
+        if ((s_hardenNXD == HardenNXD::Yes && ne.d_validationState != vState::Bogus) || ne.d_validationState == vState::Secure) {
           res = RCode::NXDomain;
-          sttl = ne->d_ttd - d_now.tv_sec;
+          sttl = ne.d_ttd - d_now.tv_sec;
           giveNegative = true;
-          cachedState = ne->d_validationState;
-          LOG(prefix<<qname<<": Name '"<<negCacheName<<"' and below, is negatively cached via '"<<ne->d_auth<<"' for another "<<sttl<<" seconds"<<endl);
+          cachedState = ne.d_validationState;
+          LOG(prefix<<qname<<": Name '"<<negCacheName<<"' and below, is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
           break;
         }
       }
@@ -1512,24 +1691,24 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
 
     state = cachedState;
 
-    if (!wasAuthZone && shouldValidate() && state == Indeterminate) {
-      LOG(prefix<<qname<<": got Indeterminate state for records retrieved from the negative cache, validating.."<<endl);
+    if (!wasAuthZone && shouldValidate() && state == vState::Indeterminate) {
+      LOG(prefix<<qname<<": got vState::Indeterminate state for records retrieved from the negative cache, validating.."<<endl);
       computeNegCacheValidationStatus(ne, qname, qtype, res, state, depth);
 
-      if (state != cachedState && state == Bogus) {
+      if (state != cachedState && state == vState::Bogus) {
         sttl = std::min(sttl, s_maxbogusttl);
       }
     }
 
     // Transplant SOA to the returned packet
-    addTTLModifiedRecords(ne->authoritySOA.records, sttl, ret);
+    addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
     if(d_doDNSSEC) {
-      addTTLModifiedRecords(ne->authoritySOA.signatures, sttl, ret);
-      addTTLModifiedRecords(ne->DNSSECRecords.records, sttl, ret);
-      addTTLModifiedRecords(ne->DNSSECRecords.signatures, sttl, ret);
+      addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
+      addTTLModifiedRecords(ne.DNSSECRecords.records, sttl, ret);
+      addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
     }
 
-    LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[state]<<endl);
+    LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<state<<endl);
     return true;
   }
 
@@ -1540,11 +1719,12 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
   uint32_t ttl=0;
   uint32_t capTTL = std::numeric_limits<uint32_t>::max();
   bool wasCachedAuth;
-  if(s_RC->get(d_now.tv_sec, sqname, sqt, !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
+
+  if(g_recCache->get(d_now.tv_sec, sqname, sqt, !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
 
     LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
 
-    if (!wasAuthZone && shouldValidate() && (wasCachedAuth || wasForwardRecurse) && cachedState == Indeterminate && d_requireAuthData) {
+    if (!wasAuthZone && shouldValidate() && (wasCachedAuth || wasForwardRecurse) && cachedState == vState::Indeterminate && d_requireAuthData) {
 
       /* This means we couldn't figure out the state when this entry was cached,
          most likely because we hadn't computed the zone cuts yet. */
@@ -1557,17 +1737,22 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
       computeZoneCuts(subdomain, g_rootdnsname, depth);
 
       vState recordState = getValidationStatus(qname, false);
-      if (recordState == Secure) {
-        LOG(prefix<<sqname<<": got Indeterminate state from the cache, validating.."<<endl);
-        cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+      if (recordState == vState::Secure) {
+        LOG(prefix<<sqname<<": got vState::Indeterminate state from the cache, validating.."<<endl);
+        if (sqt == QType::DNSKEY) {
+          cachedState = validateDNSKeys(sqname, cset, signatures, depth);
+        }
+        else {
+          cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+        }
       }
       else {
         cachedState = recordState;
       }
 
-      if (cachedState != Indeterminate) {
-        LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
-        if (cachedState == Bogus) {
+      if (cachedState != vState::Indeterminate) {
+        LOG(prefix<<qname<<": got vState::Indeterminate state from the cache, validation result is "<<cachedState<<endl);
+        if (cachedState == vState::Bogus) {
           capTTL = s_maxbogusttl;
         }
         updateValidationStatusInCache(sqname, sqt, wasCachedAuth, cachedState);
@@ -1620,7 +1805,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
     if(found && !expired) {
       if (!giveNegative)
         res=0;
-      LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+      LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<cachedState<<endl);
       state = cachedState;
       return true;
     }
@@ -1655,7 +1840,7 @@ inline std::vector<std::pair<DNSName, float>> SyncRes::shuffleInSpeedOrder(NsSet
       return rnameservers;
   }
 
-  random_shuffle(rnameservers.begin(),rnameservers.end());
+  shuffle(rnameservers.begin(),rnameservers.end(), pdns::dns_random_engine());
   speedOrder so;
   stable_sort(rnameservers.begin(),rnameservers.end(), so);
 
@@ -1686,7 +1871,7 @@ inline vector<ComboAddress> SyncRes::shuffleForwardSpeed(const vector<ComboAddre
     speed=t_sstorage.nsSpeeds[nsName].get(d_now);
     speeds[val]=speed;
   }
-  random_shuffle(nameservers.begin(),nameservers.end());
+  shuffle(nameservers.begin(),nameservers.end(), pdns::dns_random_engine());
   speedOrderCA so(speeds);
   stable_sort(nameservers.begin(),nameservers.end(), so);
 
@@ -1796,28 +1981,160 @@ static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
   ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
 }
 
+static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType& qtype, const std::vector<DNSRecord>& records)
+{
+  if (qtype == QType::CNAME) {
+    return true;
+  }
+
+  for (const auto& record : records) {
+    if (record.d_type == QType::CNAME) {
+      if (auto content = getRR<CNAMERecordContent>(record)) {
+        if (qname == content->getTarget()) {
+          /* we have a CNAME whose target matches the entry we are about to
+             generate, so it will complete the current records, not replace
+             them
+          */
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+static void removeConflictingRecord(std::vector<DNSRecord>& records, const DNSName& name, uint16_t dtype)
+{
+  for (auto it = records.begin(); it != records.end(); ) {
+    bool remove = false;
+
+    if (it->d_class == QClass::IN &&
+        (it->d_type == QType::CNAME || dtype == QType::CNAME || it->d_type == dtype) &&
+        it->d_name == name) {
+      remove = true;
+    }
+    else if (it->d_class == QClass::IN &&
+             it->d_type == QType::RRSIG &&
+             it->d_name == name) {
+      if (auto rrc = getRR<RRSIGRecordContent>(*it)) {
+        if (rrc->d_type == QType::CNAME || rrc->d_type == dtype) {
+          /* also remove any RRSIG that could conflict */
+          remove = true;
+        }
+      }
+    }
+
+    if (remove) {
+      it = records.erase(it);
+    }
+    else {
+      ++it;
+    }
+  }
+}
+
+void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType& qtype, std::vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth)
+{
+  if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+    /* reset to no match */
+    d_appliedPolicy = DNSFilterEngine::Policy();
+    return;
+  }
+
+  /* don't account truncate actions for TCP queries, since they are not applied */
+  if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !d_queryReceivedOverTCP) {
+    ++g_stats.policyResults[d_appliedPolicy.d_kind];
+  }
+
+  if (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+    LOG(prefix << qname << "|" << qtype.getName() << d_appliedPolicy.getLogString() << endl);
+  }
+
+  switch (d_appliedPolicy.d_kind) {
+
+  case DNSFilterEngine::PolicyKind::NoAction:
+      return;
+
+  case DNSFilterEngine::PolicyKind::Drop:
+    ++g_stats.policyDrops;
+    throw ImmediateQueryDropException();
+
+  case DNSFilterEngine::PolicyKind::NXDOMAIN:
+    ret.clear();
+    rcode = RCode::NXDomain;
+    done = true;
+    return;
+
+  case DNSFilterEngine::PolicyKind::NODATA:
+    ret.clear();
+    rcode = RCode::NoError;
+    done = true;
+    return;
+
+  case DNSFilterEngine::PolicyKind::Truncate:
+    if (!d_queryReceivedOverTCP) {
+      ret.clear();
+      rcode = RCode::NoError;
+      throw SendTruncatedAnswerException();
+    }
+    return;
+
+  case DNSFilterEngine::PolicyKind::Custom:
+    {
+      if (rpzHitShouldReplaceContent(qname, qtype, ret)) {
+        ret.clear();
+      }
+
+      rcode = RCode::NoError;
+      done = true;
+      auto spoofed = d_appliedPolicy.getCustomRecords(qname, qtype.getCode());
+      for (auto& dr : spoofed) {
+        removeConflictingRecord(ret, dr.d_name, dr.d_type);
+      }
+
+      for (auto& dr : spoofed) {
+        ret.push_back(dr);
+
+        if (dr.d_name == qname && dr.d_type == QType::CNAME && qtype != QType::CNAME) {
+          if (auto content = getRR<CNAMERecordContent>(dr)) {
+            vState newTargetState = vState::Indeterminate;
+            handleNewTarget(prefix, qname, content->getTarget(), qtype.getCode(), ret, rcode, depth, {}, newTargetState);
+          }
+        }
+      }
+    }
+  }
+}
+
 bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
 {
   /* we skip RPZ processing if:
      - it was disabled (d_wantsRPZ is false) ;
      - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
      the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
-     process any further RPZ rules.
+     process any further RPZ rules. Except that we need to process rules of higher priority..
   */
-  if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
     for (auto const &ns : nameservers) {
       bool match = dfe.getProcessingPolicy(ns.first, d_discardedPolicies, d_appliedPolicy);
-      if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
-        LOG(", however nameserver "<<ns.first<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
-        return true;
+      if (match) {
+        mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+        if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
+          LOG(", however nameserver "<<ns.first<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
+          return true;
+        }
       }
 
       // Traverse all IP addresses for this NS to see if they have an RPN NSIP policy
       for (auto const &address : ns.second.first) {
         match = dfe.getProcessingPolicy(address, d_discardedPolicies, d_appliedPolicy);
-        if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
-          LOG(", however nameserver "<<ns.first<<" IP address "<<address.toString()<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
-          return true;
+        if (match) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
+            LOG(", however nameserver "<<ns.first<<" IP address "<<address.toString()<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
+            return true;
+          }
         }
       }
     }
@@ -1831,25 +2148,28 @@ bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAd
      - it was disabled (d_wantsRPZ is false) ;
      - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
      the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
-     process any further RPZ rules.
+     process any further RPZ rules. Except that we need to process rules of higher priority..
   */
-  if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+  if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
     bool match = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies, d_appliedPolicy);
-    if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
-      LOG(" (blocked by RPZ policy '"+(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")+"')");
-      return true;
+    if (match) {
+      mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+      if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
+        LOG(" (blocked by RPZ policy '" + d_appliedPolicy.getName() + "')");
+        return true;
+      }
     }
   }
   return false;
 }
 
-vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, std::vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly)
+vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, std::vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly, unsigned int &retrieveAddressesForNS)
 {
   vector<ComboAddress> result;
 
   if(!tns->first.empty()) {
     LOG(prefix<<qname<<": Trying to resolve NS '"<<tns->first<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
-    result = getAddrs(tns->first, depth+2, beenthere, cacheOnly);
+    result = getAddrs(tns->first, depth, beenthere, cacheOnly, retrieveAddressesForNS);
     pierceDontQuery=false;
   }
   else {
@@ -1918,26 +2238,26 @@ uint32_t SyncRes::computeLowestTTD(const std::vector<DNSRecord>& records, const
 
 void SyncRes::updateValidationState(vState& state, const vState stateUpdate)
 {
-  LOG(d_prefix<<"validation state was "<<std::string(vStates[state])<<", state update is "<<std::string(vStates[stateUpdate]));
+  LOG(d_prefix<<"validation state was "<<state<<", state update is "<<stateUpdate);
 
-  if (stateUpdate == TA) {
-    state = Secure;
+  if (stateUpdate == vState::TA) {
+    state = vState::Secure;
   }
-  else if (stateUpdate == NTA) {
-    state = Insecure;
+  else if (stateUpdate == vState::NTA) {
+    state = vState::Insecure;
   }
-  else if (stateUpdate == Bogus) {
-    state = Bogus;
+  else if (stateUpdate == vState::Bogus) {
+    state = vState::Bogus;
   }
-  else if (state == Indeterminate) {
+  else if (state == vState::Indeterminate) {
     state = stateUpdate;
   }
-  else if (stateUpdate == Insecure) {
-    if (state != Bogus) {
-      state = Insecure;
+  else if (stateUpdate == vState::Insecure) {
+    if (state != vState::Bogus) {
+      state = vState::Insecure;
     }
   }
-  LOG(", validation state is now "<<std::string(vStates[state])<<endl);
+  LOG(", validation state is now "<<state<<endl);
 }
 
 vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
@@ -1947,18 +2267,18 @@ vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
   if (luaLocal->dsAnchors.empty()) {
     LOG(d_prefix<<": No trust anchors configured, everything is Insecure"<<endl);
     /* We have no TA, everything is insecure */
-    return Insecure;
+    return vState::Insecure;
   }
 
   std::string reason;
   if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
     LOG(d_prefix<<": got NTA for '"<<zone<<"'"<<endl);
-    return NTA;
+    return vState::NTA;
   }
 
   if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
     LOG(d_prefix<<": got TA for '"<<zone<<"'"<<endl);
-    return TA;
+    return vState::TA;
   }
   else {
     LOG(d_prefix<<": no TA found for '"<<zone<<"' among "<< luaLocal->dsAnchors.size()<<endl);
@@ -1966,10 +2286,10 @@ vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
 
   if (zone.isRoot()) {
     /* No TA for the root */
-    return Insecure;
+    return vState::Insecure;
   }
 
-  return Indeterminate;
+  return vState::Indeterminate;
 }
 
 static size_t countSupportedDS(const dsmap_t& dsmap)
@@ -1989,36 +2309,38 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
 {
   vState result = getTA(zone, ds);
 
-  if (result != Indeterminate || taOnly) {
+  if (result != vState::Indeterminate || taOnly) {
     if (foundCut) {
-      *foundCut = (result != Indeterminate);
+      *foundCut = (result != vState::Indeterminate);
     }
 
-    if (result == TA) {
+    if (result == vState::TA) {
       if (countSupportedDS(ds) == 0) {
         ds.clear();
-        result = Insecure;
+        result = vState::Insecure;
       }
       else {
-        result = Secure;
+        result = vState::Secure;
       }
     }
-    else if (result == NTA) {
-      result = Insecure;
+    else if (result == vState::NTA) {
+      result = vState::Insecure;
     }
 
     return result;
   }
 
-  bool oldSkipCNAME = d_skipCNAMECheck;
-  d_skipCNAMECheck = true;
-
   std::set<GetBestNSAnswer> beenthere;
   std::vector<DNSRecord> dsrecords;
 
-  vState state = Indeterminate;
+  vState state = vState::Indeterminate;
+  const bool oldCacheOnly = setCacheOnly(false);
   int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth + 1, beenthere, state);
-  d_skipCNAMECheck = oldSkipCNAME;
+  setCacheOnly(oldCacheOnly);
+
+  if (rcode == RCode::ServFail) {
+    throw ImmediateServFailException("Server Failure while retrieving DS records for " + zone.toLogString());
+  }
 
   if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
     uint8_t bestDigestType = 0;
@@ -2080,7 +2402,7 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
            - a signed zone (Secure) to an unsigned one (Insecure)
            - an unsigned zone to another unsigned one (Insecure stays Insecure, Bogus stays Bogus)
         */
-        return state == Secure ? Insecure : state;
+        return state == vState::Secure ? vState::Insecure : state;
       } else {
         /* we have a DS */
         if (foundCut) {
@@ -2093,7 +2415,7 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   }
 
   LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
-  return Bogus;
+  return vState::Bogus;
 }
 
 bool SyncRes::haveExactValidationStatus(const DNSName& domain)
@@ -2110,7 +2432,7 @@ bool SyncRes::haveExactValidationStatus(const DNSName& domain)
 
 vState SyncRes::getValidationStatus(const DNSName& subdomain, bool allowIndeterminate)
 {
-  vState result = Indeterminate;
+  vState result = vState::Indeterminate;
 
   if (!shouldValidate()) {
     return result;
@@ -2119,8 +2441,8 @@ vState SyncRes::getValidationStatus(const DNSName& subdomain, bool allowIndeterm
   do {
     const auto& it = d_cutStates.find(name);
     if (it != d_cutStates.cend()) {
-      if (allowIndeterminate || it->second != Indeterminate) {
-        LOG(d_prefix<<": got status "<<vStates[it->second]<<" for name "<<subdomain<<" (from "<<name<<")"<<endl);
+      if (allowIndeterminate || it->second != vState::Indeterminate) {
+        LOG(d_prefix<<": got status "<<it->second<<" for name "<<subdomain<<" (from "<<name<<")"<<endl);
         return it->second;
       }
     }
@@ -2134,9 +2456,9 @@ bool SyncRes::lookForCut(const DNSName& qname, unsigned int depth, const vState
 {
   bool foundCut = false;
   dsmap_t ds;
-  vState dsState = getDSRecords(qname, ds, newState == Bogus || existingState == Insecure || existingState == Bogus, depth, false, &foundCut);
+  vState dsState = getDSRecords(qname, ds, newState == vState::Bogus || existingState == vState::Insecure || existingState == vState::Bogus, depth, false, &foundCut);
 
-  if (dsState != Indeterminate) {
+  if (dsState != vState::Indeterminate) {
     newState = dsState;
   }
 
@@ -2155,23 +2477,23 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
   }
 
   const bool oldCacheOnly = setCacheOnly(false);
+  const bool oldWantsRPZ = d_wantsRPZ;
+  d_wantsRPZ = false;
 
   dsmap_t ds;
   vState cutState = getDSRecords(end, ds, false, depth);
-  LOG(d_prefix<<": setting cut state for "<<end<<" to "<<vStates[cutState]<<endl);
+  LOG(d_prefix<<": setting cut state for "<<end<<" to "<<cutState<<endl);
   d_cutStates[end] = cutState;
 
   if (!shouldValidate()) {
     setCacheOnly(oldCacheOnly);
+    d_wantsRPZ = oldWantsRPZ;
     return;
   }
 
   DNSName qname(end);
   std::vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels();
 
-  bool oldSkipCNAME = d_skipCNAMECheck;
-  d_skipCNAMECheck = true;
-
   while(qname != begin) {
     if (labelsToAdd.empty())
       break;
@@ -2182,7 +2504,7 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
 
     const auto cutIt = d_cutStates.find(qname);
     if (cutIt != d_cutStates.cend()) {
-      if (cutIt->second != Indeterminate) {
+      if (cutIt->second != vState::Indeterminate) {
         LOG(d_prefix<<": - Cut already known at "<<qname<<endl);
         cutState = cutIt->second;
         continue;
@@ -2192,14 +2514,14 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
     /* no need to look for NS and DS if we are already insecure or bogus,
        just look for (N)TA
     */
-    if (cutState == Insecure || cutState == Bogus) {
+    if (cutState == vState::Insecure || cutState == vState::Bogus) {
       dsmap_t cutDS;
       vState newState = getDSRecords(qname, cutDS, true, depth);
-      if (newState == Indeterminate) {
+      if (newState == vState::Indeterminate) {
         continue;
       }
 
-      LOG(d_prefix<<": New state for "<<qname<<" is "<<vStates[newState]<<endl);
+      LOG(d_prefix<<": New state for "<<qname<<" is "<<newState<<endl);
       cutState = newState;
 
       d_cutStates[qname] = cutState;
@@ -2207,17 +2529,17 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
       continue;
     }
 
-    vState newState = Indeterminate;
-    /* temporarily mark as Indeterminate, so that we won't enter an endless loop
+    vState newState = vState::Indeterminate;
+    /* temporarily mark as vState::Indeterminate, so that we won't enter an endless loop
        trying to determine that zone cut again. */
     d_cutStates[qname] = newState;
-    bool foundCut = lookForCut(qname, depth + 1, cutState, newState);
+    bool foundCut = lookForCut(qname, depth, cutState, newState);
     if (foundCut) {
       LOG(d_prefix<<": - Found cut at "<<qname<<endl);
-      if (newState != Indeterminate) {
+      if (newState != vState::Indeterminate) {
         cutState = newState;
       }
-      LOG(d_prefix<<": New state for "<<qname<<" is "<<vStates[cutState]<<endl);
+      LOG(d_prefix<<": New state for "<<qname<<" is "<<cutState<<endl);
       d_cutStates[qname] = cutState;
     }
     else {
@@ -2227,15 +2549,14 @@ void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned
     }
   }
 
-  d_skipCNAMECheck = oldSkipCNAME;
-
   LOG(d_prefix<<": list of cuts from "<<begin<<" to "<<end<<endl);
   for (const auto& cut : d_cutStates) {
     if (cut.first.isRoot() || (begin.isPartOf(cut.first) && cut.first.isPartOf(end))) {
-      LOG(" - "<<cut.first<<": "<<vStates[cut.second]<<endl);
+      LOG(" - "<<cut.first<<": "<<cut.second<<endl);
     }
   }
   setCacheOnly(oldCacheOnly);
+  d_wantsRPZ = oldWantsRPZ;
 }
 
 vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth)
@@ -2247,7 +2568,7 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
     if (!signer.empty() && zone.isPartOf(signer)) {
       vState state = getDSRecords(signer, ds, false, depth);
 
-      if (state != Secure) {
+      if (state != vState::Secure) {
         return state;
       }
     }
@@ -2278,10 +2599,10 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
      covering this set, this looks Bogus. */
   if (validatedKeys.size() != tentativeKeys.size()) {
     LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
-    return Bogus;
+    return vState::Bogus;
   }
 
-  return Secure;
+  return vState::Secure;
 }
 
 vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth)
@@ -2290,15 +2611,17 @@ vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int
   std::set<GetBestNSAnswer> beenthere;
   LOG(d_prefix<<"Retrieving DNSKeys for "<<signer<<endl);
 
-  vState state = Indeterminate;
-  /* following CNAME might lead to us to the wrong DNSKEY */
-  bool oldSkipCNAME = d_skipCNAMECheck;
-  d_skipCNAMECheck = true;
+  vState state = vState::Indeterminate;
+  const bool oldCacheOnly = setCacheOnly(false);
   int rcode = doResolve(signer, QType(QType::DNSKEY), records, depth + 1, beenthere, state);
-  d_skipCNAMECheck = oldSkipCNAME;
+  setCacheOnly(oldCacheOnly);
+
+  if (rcode == RCode::ServFail) {
+    throw ImmediateServFailException("Server Failure while retrieving DNSKEY records for " + signer.toLogString());
+  }
 
   if (rcode == RCode::NoError) {
-    if (state == Secure) {
+    if (state == vState::Secure) {
       for (const auto& key : records) {
         if (key.d_type == QType::DNSKEY) {
           auto content = getRR<DNSKEYRecordContent>(key);
@@ -2308,12 +2631,12 @@ vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int
         }
       }
     }
-    LOG(d_prefix<<"Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
+    LOG(d_prefix<<"Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<state<<endl);
     return state;
   }
 
   LOG(d_prefix<<"Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
-  return Bogus;
+  return vState::Bogus;
 }
 
 vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType& qtype, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
@@ -2324,21 +2647,23 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname
     if (!signer.empty() && name.isPartOf(signer)) {
       if ((qtype == QType::DNSKEY || qtype == QType::DS) && signer == qname) {
         /* we are already retrieving those keys, sorry */
-        if (qtype == QType::DS) {
-          /* something is very wrong */
+        if (qtype == QType::DS && !signer.isRoot()) {
+          /* Unless we are getting the DS of the root zone, we should never see a
+             DS (or a denial of a DS) signed by the DS itself, since we should be
+             requesting it from the parent zone. Something is very wrong */
           LOG(d_prefix<<"The DS for "<<qname<<" is signed by itself, going Bogus"<<endl);
-          return Bogus;
+          return vState::Bogus;
         }
-        return Indeterminate;
+        return vState::Indeterminate;
       }
       vState state = getDNSKeys(signer, keys, depth);
-      if (state != Secure) {
+      if (state != vState::Secure) {
         return state;
       }
     }
   } else {
     LOG(d_prefix<<"Bogus!"<<endl);
-    return Bogus;
+    return vState::Bogus;
   }
 
   sortedRecords_t recordcontents;
@@ -2349,11 +2674,11 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname
   LOG(d_prefix<<"Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
   if (validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, false)) {
     LOG(d_prefix<<"Secure!"<<endl);
-    return Secure;
+    return vState::Secure;
   }
 
   LOG(d_prefix<<"Bogus!"<<endl);
-  return Bogus;
+  return vState::Bogus;
 }
 
 static bool allowAdditionalEntry(std::unordered_set<DNSName>& allowedAdditionals, const DNSRecord& rec)
@@ -2529,11 +2854,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
   const unsigned int labelCount = qname.countLabels();
   bool isCNAMEAnswer = false;
   bool isDNAMEAnswer = false;
-  for(const auto& rec : lwr.d_records) {
-    if (rec.d_class != QClass::IN) {
+  for (auto& rec : lwr.d_records) {
+    if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
       continue;
     }
 
+    rec.d_ttl = min(s_maxcachettl, rec.d_ttl);
+
     if(!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname && !isDNAMEAnswer) {
       isCNAMEAnswer = true;
     }
@@ -2542,7 +2869,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
       isCNAMEAnswer = false;
     }
 
-    /* if we have a positive answer synthetized from a wildcard,
+    /* if we have a positive answer synthesized from a wildcard,
        we need to store the corresponding NSEC/NSEC3 records proving
        that the exact name did not exist in the negative cache */
     if(gatherWildcardProof) {
@@ -2556,12 +2883,12 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
     }
-    if(rec.d_type == QType::RRSIG) {
+    if (rec.d_type == QType::RRSIG) {
       auto rrsig = getRR<RRSIGRecordContent>(rec);
       if (rrsig) {
         /* As illustrated in rfc4035's Appendix B.6, the RRSIG label
            count can be lower than the name's label count if it was
-           synthetized from the wildcard. Note that the difference might
+           synthesized from the wildcard. Note that the difference might
            be > 1. */
         if (rec.d_name == qname && isWildcardExpanded(labelCount, rrsig)) {
           gatherWildcardProof = true;
@@ -2571,11 +2898,11 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
                We still want to gather the corresponding NSEC/NSEC3 records
                to pass them to our client in case it wants to validate by itself.
             */
-            LOG(prefix<<qname<<": RRSIG indicates the name was synthetized from a wildcard, we need a wildcard proof"<<endl);
+            LOG(prefix<<qname<<": RRSIG indicates the name was synthesized from a wildcard, we need a wildcard proof"<<endl);
             needWildcardProof = true;
           }
           else {
-            LOG(prefix<<qname<<": RRSIG indicates the name was synthetized from a wildcard expanded onto itself, we need to gather wildcard proof"<<endl);
+            LOG(prefix<<qname<<": RRSIG indicates the name was synthesized from a wildcard expanded onto itself, we need to gather wildcard proof"<<endl);
           }
           wildcardLabelsCount = rrsig->d_labels;
         }
@@ -2614,10 +2941,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
     }
 
     if(rec.d_name.isPartOf(auth)) {
-      if(rec.d_type == QType::RRSIG) {
+      if (rec.d_type == QType::RRSIG) {
         LOG("RRSIG - separate"<<endl);
       }
-      else if(lwr.d_aabit && lwr.d_rcode==RCode::NoError && rec.d_place==DNSResourceRecord::ANSWER && ((rec.d_type != QType::DNSKEY && rec.d_type != QType::DS) || rec.d_name != auth) && s_delegationOnly.count(auth)) {
+      else if (rec.d_type == QType::DS && rec.d_name == auth) {
+        LOG("NO - DS provided by child zone"<<endl);
+      }
+      else if (lwr.d_aabit && lwr.d_rcode==RCode::NoError && rec.d_place==DNSResourceRecord::ANSWER && ((rec.d_type != QType::DNSKEY && rec.d_type != QType::DS) || rec.d_name != auth) && s_delegationOnly.count(auth)) {
         LOG("NO! Is from delegation-only zone"<<endl);
         s_nodelegated++;
         return RCode::NXDomain;
@@ -2631,6 +2961,10 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         if (!t_sstorage.domainmap->empty()) {
           // Check if we are authoritative for a zone in this answer
           DNSName tmp_qname(rec.d_name);
+          // We may be auth for domain example.com, but the DS record needs to come from the parent (.com) nameserver
+          if (rec.d_type == QType::DS) {
+            tmp_qname.chopOff();
+          }
           auto auth_domain_iter=getBestAuthZone(&tmp_qname);
           if(auth_domain_iter!=t_sstorage.domainmap->end() &&
              auth.countLabels() <= auth_domain_iter->first.countLabels()) {
@@ -2728,9 +3062,9 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
     }
 
     vState recordState = getValidationStatus(i->first.name, false);
-    LOG(d_prefix<<": got initial zone status "<<vStates[recordState]<<" for record "<<i->first.name<<"|"<<DNSRecordContent::NumberToType(i->first.type)<<endl);
+    LOG(d_prefix<<": got initial zone status "<<recordState<<" for record "<<i->first.name<<"|"<<DNSRecordContent::NumberToType(i->first.type)<<endl);
 
-    if (shouldValidate() && recordState == Secure) {
+    if (shouldValidate() && recordState == vState::Secure) {
       vState initialState = recordState;
 
       if (expectSignature) {
@@ -2760,15 +3094,15 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
               LOG(d_prefix<<"Validating non-additional record for "<<i->first.name<<endl);
               recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, i->second.records, i->second.signatures);
               /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
-              if (qtype == QType::NS && i->second.signatures.empty() && recordState == Bogus && haveExactValidationStatus(i->first.name) && getValidationStatus(i->first.name) == Indeterminate) {
-                recordState = Indeterminate;
+              if (qtype == QType::NS && i->second.signatures.empty() && recordState == vState::Bogus && haveExactValidationStatus(i->first.name) && getValidationStatus(i->first.name) == vState::Indeterminate) {
+                recordState = vState::Indeterminate;
               }
             }
           }
         }
       }
       else {
-        recordState = Indeterminate;
+        recordState = vState::Indeterminate;
 
         /* in a non authoritative answer, we only care about the DS record (or lack of)  */
         if ((i->first.type == QType::DS || i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && i->first.place == DNSResourceRecord::AUTHORITY) {
@@ -2777,17 +3111,17 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
 
-      if (initialState == Secure && state != recordState && expectSignature) {
+      if (initialState == vState::Secure && state != recordState && expectSignature) {
         updateValidationState(state, recordState);
       }
     }
     else {
       if (shouldValidate()) {
-        LOG(d_prefix<<"Skipping validation because the current state is "<<vStates[recordState]<<endl);
+        LOG(d_prefix<<"Skipping validation because the current state is "<<recordState<<endl);
       }
     }
 
-    if (recordState == Bogus) {
+    if (recordState == vState::Bogus) {
       /* this is a TTD by now, be careful */
       for(auto& record : i->second.records) {
         record.d_ttl = std::min(record.d_ttl, static_cast<uint32_t>(s_maxbogusttl + d_now.tv_sec));
@@ -2827,7 +3161,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
       if (doCache) {
-        s_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
+        g_recCache->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState);
       }
     }
 
@@ -2841,10 +3175,10 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
 void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool allowOptOut)
 {
   if (denialState == expectedState) {
-    neValidationState = Secure;
+    neValidationState = vState::Secure;
   }
   else {
-    if (denialState == OPTOUT && allowOptOut) {
+    if (denialState == dState::OPTOUT && allowOptOut) {
       LOG(d_prefix<<"OPT-out denial found for "<<neName<<endl);
       /* rfc5155 states:
          "The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
@@ -2860,15 +3194,15 @@ void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSNa
          At best the Opt-Out NSEC3 RR proves that there is no signed DS (so no
          secure delegation).
       */
-      neValidationState = Insecure;
+      neValidationState = vState::Insecure;
     }
-    else if (denialState == INSECURE) {
+    else if (denialState == dState::INSECURE) {
       LOG(d_prefix<<"Insecure denial found for "<<neName<<", returning Insecure"<<endl);
-      neValidationState = Insecure;
+      neValidationState = vState::Insecure;
     }
     else {
       LOG(d_prefix<<"Invalid denial found for "<<neName<<", returning Bogus, res="<<denialState<<", expectedState="<<expectedState<<endl);
-      neValidationState = Bogus;
+      neValidationState = vState::Bogus;
     }
     updateValidationState(state, neValidationState);
   }
@@ -2877,10 +3211,10 @@ void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSNa
 dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned)
 {
   cspmap_t csp = harvestCSPFromNE(ne);
-  return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == NXQTYPE);
+  return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE);
 }
 
-bool 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, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount)
+bool 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, vState& state, const bool needWildcardProof, const bool gatherWildcardProof, const unsigned int wildcardLabelsCount, int& rcode, unsigned int depth)
 {
   bool done = false;
   DNSName dnameTarget, dnameOwner;
@@ -2903,8 +3237,10 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       LOG(prefix<<qname<<": got negative caching indication for name '"<<qname<<"' (accept="<<rec.d_name.isPartOf(auth)<<"), newtarget='"<<newtarget<<"'"<<endl);
 
       rec.d_ttl = min(rec.d_ttl, s_maxnegttl);
-      if(newtarget.empty()) // only add a SOA if we're not going anywhere after this
+      // only add a SOA if we're not going anywhere after this
+      if (newtarget.empty()) {
         ret.push_back(rec);
+      }
 
       NegCache::NegCacheEntry ne;
 
@@ -2916,15 +3252,15 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       ne.d_auth = rec.d_name;
       harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-      if (state == Secure) {
-        dState denialState = getDenialValidationState(ne, state, NXDOMAIN, false);
-        updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, NXDOMAIN, true);
+      if (state == vState::Secure) {
+        dState denialState = getDenialValidationState(ne, state, dState::NXDOMAIN, false);
+        updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN, true);
       }
       else {
         ne.d_validationState = state;
       }
 
-      if (ne.d_validationState == Bogus) {
+      if (ne.d_validationState == vState::Bogus) {
         lowestTTL = min(lowestTTL, s_maxbogusttl);
       }
 
@@ -2935,10 +3271,10 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
          We have a regression test making sure we do exactly that.
       */
       if(!wasVariable() && newtarget.empty()) {
-        t_sstorage.negcache.add(ne);
+        g_negCache->add(ne);
         if(s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
           ne.d_name = ne.d_name.getLastLabel();
-          t_sstorage.negcache.add(ne);
+          g_negCache->add(ne);
         }
       }
 
@@ -2952,7 +3288,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         }
         ret.push_back(rec);
         if (auto content = getRR<CNAMERecordContent>(rec)) {
-          newtarget=content->getTarget();
+          newtarget=DNSName(content->getTarget());
         }
       } else if (rec.d_type == QType::DNAME && qname.isPartOf(rec.d_name)) { // DNAME
         ret.push_back(rec);
@@ -2982,7 +3318,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         }
       }
     }
-    /* if we have a positive answer synthetized from a wildcard, we need to
+    /* if we have a positive answer synthesized from a wildcard, we need to
        return the corresponding NSEC/NSEC3 records from the AUTHORITY section
        proving that the exact name did not exist */
     else if(gatherWildcardProof && (rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::AUTHORITY) {
@@ -2997,10 +3333,11 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     {
       LOG(prefix<<qname<<": answer is in: resolved to '"<< rec.d_content->getZoneRepresentation()<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"'"<<endl);
 
-      done=true;
+      done = true;
+      rcode = RCode::NoError;
 
-      if (state == Secure && needWildcardProof) {
-        /* We have a positive answer synthetized from a wildcard, we need to check that we have
+      if (state == vState::Secure && needWildcardProof) {
+        /* We have a positive answer synthesized from a wildcard, we need to check that we have
            proof that the exact name doesn't exist so the wildcard can be used,
            as described in section 5.3.4 of RFC 4035 and 5.3 of RFC 7129.
         */
@@ -3013,12 +3350,12 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
         cspmap_t csp = harvestCSPFromNE(ne);
         dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
-        if (res != NXDOMAIN) {
-          vState st = Bogus;
-          if (res == INSECURE) {
+        if (res != dState::NXDOMAIN) {
+          vState st = vState::Bogus;
+          if (res == dState::INSECURE) {
             /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
                this is not enough to warrant a Bogus, but go Insecure. */
-            st = Insecure;
+            st = vState::Insecure;
             LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
           }
           else {
@@ -3031,6 +3368,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
           updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
         }
       }
+
       ret.push_back(rec);
     }
     else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER) {
@@ -3061,7 +3399,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     }
     else if(realreferral && rec.d_place==DNSResourceRecord::AUTHORITY && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && newauth.isPartOf(auth)) {
       /* we might have received a denial of the DS, let's check */
-      if (state == Secure) {
+      if (state == vState::Secure) {
         NegCache::NegCacheEntry ne;
         ne.d_auth = auth;
         ne.d_name = newauth;
@@ -3070,18 +3408,18 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         uint32_t lowestTTL = rec.d_ttl;
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-        dState denialState = getDenialValidationState(ne, state, NXQTYPE, true);
+        dState denialState = getDenialValidationState(ne, state, dState::NXQTYPE, true);
 
-        if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
+        if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
           ne.d_ttd = lowestTTL + d_now.tv_sec;
-          ne.d_validationState = Secure;
-          if (denialState == OPTOUT) {
-            ne.d_validationState = Insecure;
+          ne.d_validationState = vState::Secure;
+          if (denialState == dState::OPTOUT) {
+            ne.d_validationState = vState::Insecure;
           }
           LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
 
           if(!wasVariable()) {
-            t_sstorage.negcache.add(ne);
+            g_negCache->add(ne);
           }
 
           if (qname == newauth && qtype == QType::DS) {
@@ -3092,8 +3430,8 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         }
       }
     }
-    else if(!done && rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::SOA &&
-            lwr.d_rcode==RCode::NoError && qname.isPartOf(rec.d_name)) {
+    else if (!done && rec.d_place == DNSResourceRecord::AUTHORITY && rec.d_type == QType::SOA &&
+            lwr.d_rcode == RCode::NoError && qname.isPartOf(rec.d_name)) {
       LOG(prefix<<qname<<": got negative caching indication for '"<< qname<<"|"<<qtype.getName()<<"'"<<endl);
 
       if(!newtarget.empty()) {
@@ -3109,14 +3447,14 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         ne.d_qtype = qtype;
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
 
-        if (state == Secure) {
-          dState denialState = getDenialValidationState(ne, state, NXQTYPE, false);
-          updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, NXQTYPE, qtype == QType::DS);
+        if (state == vState::Secure) {
+          dState denialState = getDenialValidationState(ne, state, dState::NXQTYPE, false);
+          updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS);
         } else {
           ne.d_validationState = state;
         }
 
-        if (ne.d_validationState == Bogus) {
+        if (ne.d_validationState == vState::Bogus) {
           lowestTTL = min(lowestTTL, s_maxbogusttl);
           rec.d_ttl = min(rec.d_ttl, s_maxbogusttl);
         }
@@ -3124,7 +3462,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
         if(!wasVariable()) {
           if(qtype.getCode()) {  // prevents us from blacking out a whole domain
-            t_sstorage.negcache.add(ne);
+            g_negCache->add(ne);
           }
         }
 
@@ -3141,7 +3479,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     cnamerec.d_type = QType::CNAME;
     cnamerec.d_ttl = dnameTTL;
     cnamerec.d_content = std::make_shared<CNAMERecordContent>(CNAMERecordContent(newtarget));
-    ret.push_back(cnamerec);
+    ret.push_back(std::move(cnamerec));
   }
   return done;
 }
@@ -3294,6 +3632,49 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
   return true;
 }
 
+void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, uint16_t qtype, std::vector<DNSRecord>& ret, int& rcode, int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state)
+{
+  if (newtarget == qname) {
+    LOG(prefix<<qname<<": status=got a CNAME referral to self, returning SERVFAIL"<<endl);
+    ret.clear();
+    rcode = RCode::ServFail;
+    return;
+  }
+
+  if (depth > 10) {
+    LOG(prefix<<qname<<": status=got a CNAME referral, but recursing too deep, returning SERVFAIL"<<endl);
+    rcode = RCode::ServFail;
+    return;
+  }
+
+  // Check to see if we already have seen the new target as a previous target
+  if (scanForCNAMELoop(newtarget, ret)) {
+    LOG(prefix<<qname<<": status=got a CNAME referral that causes a loop, returning SERVFAIL"<<endl);
+    ret.clear();
+    rcode = RCode::ServFail;
+    return;
+  }
+
+  if (qtype == QType::DS || qtype == QType::DNSKEY) {
+    LOG(prefix<<qname<<": status=got a CNAME referral, but we are looking for a DS or DNSKEY"<<endl);
+
+    if (d_doDNSSEC) {
+      addNXNSECS(ret, recordsFromAnswer);
+    }
+
+    rcode = RCode::NoError;
+    return;
+  }
+
+  LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
+
+  set<GetBestNSAnswer> beenthere;
+  vState cnameState = vState::Indeterminate;
+  rcode = doResolve(newtarget, QType(qtype), ret, depth + 1, beenthere, cnameState);
+  LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<state<<" with the state from the CNAME quest: "<<cnameState<<endl);
+  updateValidationState(state, cnameState);
+}
+
 bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state)
 {
   string prefix;
@@ -3333,52 +3714,27 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   DNSName newauth;
   DNSName newtarget;
 
-  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount);
+  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, gatherWildcardProof, wildcardLabelsCount, *rcode, depth);
 
-  if(done){
+  if (done){
     LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
-    LOG(prefix<<qname<<": validation status is "<<vStates[state]<<endl);
-    *rcode = RCode::NoError;
+    LOG(prefix<<qname<<": validation status is "<<state<<endl);
     return true;
   }
 
-  if(!newtarget.empty()) {
-    if(newtarget == qname) {
-      LOG(prefix<<qname<<": status=got a CNAME referral to self, returning SERVFAIL"<<endl);
-      *rcode = RCode::ServFail;
-      return true;
-    }
-
-    if(depth > 10) {
-      LOG(prefix<<qname<<": status=got a CNAME referral, but recursing too deep, returning SERVFAIL"<<endl);
-      *rcode = RCode::ServFail;
-      return true;
-    }
-
-    if (qtype == QType::DS) {
-      LOG(prefix<<qname<<": status=got a CNAME referral, but we are looking for a DS"<<endl);
-
-      if(d_doDNSSEC)
-        addNXNSECS(ret, lwr.d_records);
-
-      *rcode = RCode::NoError;
-      return true;
-    }
-    else {
-      LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
-
-      set<GetBestNSAnswer> beenthere2;
-      vState cnameState = Indeterminate;
-      *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
-      LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
-      updateValidationState(state, cnameState);
-      return true;
-    }
+  if (!newtarget.empty()) {
+    handleNewTarget(prefix, qname, newtarget, qtype.getCode(), ret, *rcode, depth, lwr.d_records, state);
+    return true;
   }
 
   if(lwr.d_rcode == RCode::NXDomain) {
     LOG(prefix<<qname<<": status=NXDOMAIN, we are done "<<(negindic ? "(have negative SOA)" : "")<<endl);
 
+    if (state == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
+      LOG(prefix<<qname<<": NXDOMAIN without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
+      updateValidationState(state, vState::Bogus);
+    }
+
     if(d_doDNSSEC)
       addNXNSECS(ret, lwr.d_records);
 
@@ -3389,8 +3745,9 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   if(nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
     LOG(prefix<<qname<<": status=noerror, other types may exist, but we are done "<<(negindic ? "(have negative SOA) " : "")<<(lwr.d_aabit ? "(have aa bit) " : "")<<endl);
 
-    if(state == Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
-      updateValidationState(state, Bogus);
+    if(state == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
+      LOG(prefix<<qname<<": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
+      updateValidationState(state, vState::Bogus);
     }
 
     if(d_doDNSSEC)
@@ -3405,11 +3762,20 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
 
     nameservers.clear();
     for (auto const &nameserver : nsset) {
-      if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+      if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
         bool match = dfe.getProcessingPolicy(nameserver, d_discardedPolicies, d_appliedPolicy);
-        if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
-          LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
-          throw PolicyHitException();
+        if (match) {
+          mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+          if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
+            if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+              /* reset to no match */
+              d_appliedPolicy = DNSFilterEngine::Policy();
+            }
+            else {
+              LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
+              throw PolicyHitException();
+            }
+          }
         }
       }
       nameservers.insert({nameserver, {{}, false}});
@@ -3443,21 +3809,41 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
 
   if (nameserversBlockedByRPZ(luaconfsLocal->dfe, nameservers)) {
     /* RPZ hit */
-    throw PolicyHitException();
+    if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+      /* reset to no match */
+      d_appliedPolicy = DNSFilterEngine::Policy();
+    }
+    else {
+      throw PolicyHitException();
+    }
   }
 
   LOG(endl);
 
+  unsigned int addressQueriesForNS = 0;
   for(;;) { // we may get more specific nameservers
     auto rnameservers = shuffleInSpeedOrder(nameservers, doLog() ? (prefix+qname.toString()+": ") : string() );
 
+    // We allow s_maxnsaddressqperq (default 10) queries with empty responses when resolving NS names.
+    // If a zone publishes many (more than s_maxnsaddressqperq) NS records, we allow less.
+    // This is to "punish" zones that publish many non-resolving NS names.
+    // We always allow 5 NS name resolving attempts with empty results.
+    unsigned int nsLimit = s_maxnsaddressqperq;
+    if (rnameservers.size() > nsLimit) {
+      int newLimit = static_cast<int>(nsLimit) - (rnameservers.size() - nsLimit);
+      nsLimit = std::max(5, newLimit);
+    }
+
     for(auto tns=rnameservers.cbegin();;++tns) {
+      if (addressQueriesForNS >= nsLimit) {
+        throw ImmediateServFailException(std::to_string(nsLimit)+" (adjusted max-ns-address-qperq) or more queries with empty results for NS addresses sent resolving "+qname.toLogString());
+      }
       if(tns==rnameservers.cend()) {
         LOG(prefix<<qname<<": Failed to resolve via any of the "<<(unsigned int)rnameservers.size()<<" offered NS at level '"<<auth<<"'"<<endl);
         if(!auth.isRoot() && flawedNSSet) {
           LOG(prefix<<qname<<": Ageing nameservers for level '"<<auth<<"', next query might succeed"<<endl);
 
-          if(s_RC->doAgeCache(d_now.tv_sec, auth, QType::NS, 10))
+          if(g_recCache->doAgeCache(d_now.tv_sec, auth, QType::NS, 10))
             g_stats.nsSetInvalidations++;
         }
         return -1;
@@ -3487,7 +3873,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
         LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
         /* setting state to indeterminate since validation is disabled for local auth zone,
            and Insecure would be misleading. */
-        state = Indeterminate;
+        state = vState::Indeterminate;
         d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
         lwr.d_tcbit=false;
         lwr.d_aabit=true;
@@ -3507,7 +3893,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
       }
       else {
         /* if tns is empty, retrieveAddressesForNS() knows we have hardcoded servers (i.e. "forwards") */
-        remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly);
+        remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly, addressQueriesForNS);
 
         if(remoteIPs.empty()) {
           LOG(prefix<<qname<<": Failed to get IP for NS "<<tns->first<<", trying next if available"<<endl);
@@ -3529,7 +3915,13 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
           LOG(endl);
           if (hitPolicy) { //implies d_wantsRPZ
             /* RPZ hit */
-            throw PolicyHitException();
+            if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+              /* reset to no match */
+              d_appliedPolicy = DNSFilterEngine::Policy();
+            }
+            else {
+              throw PolicyHitException();
+            }
           }
         }
 
@@ -3674,33 +4066,33 @@ int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<D
   SyncRes sr(now);
   int res = -1;
   try {
-    res = sr.beginResolve(qname, QType(qtype), qclass, ret);
+    res = sr.beginResolve(qname, QType(qtype), qclass, ret, 0);
   }
   catch(const PDNSException& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got pdns exception: "<<e.reason<<endl;
+    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got pdns exception: "<<e.reason<<endl;
     ret.clear();
   }
   catch(const ImmediateServFailException& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got ImmediateServFailException: "<<e.reason<<endl;
+    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got ImmediateServFailException: "<<e.reason<<endl;
     ret.clear();
   }
   catch(const PolicyHitException& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got a policy hit"<<endl;
+    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got a policy hit"<<endl;
     ret.clear();
   }
   catch(const std::exception& e) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got STL error: "<<e.what()<<endl;
+    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got STL error: "<<e.what()<<endl;
     ret.clear();
   }
   catch(...) {
-    g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got an exception"<<endl;
+    g_log<<Logger::Error<<"Failed to resolve "<<qname<<", got an exception"<<endl;
     ret.clear();
   }
   
   return res;
 }
 
-int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
+int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth) {
   SyncRes sr(now);
   sr.setDoEDNS0(true);
   sr.setUpdatingRootNS();
@@ -3711,10 +4103,10 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
   vector<DNSRecord> ret;
   int res=-1;
   try {
-    res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
+    res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret, depth + 1);
     if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
       auto state = sr.getValidationState();
-      if (state == Bogus)
+      if (state == vState::Bogus)
         throw PDNSException("Got Bogus validation result for .|NS");
     }
     return res;
index b8021447d7ac9ec90719f94de515d3a9243db2b3..8b1f1c72a55a8a61e26245c6306bb878bc446220 100644 (file)
@@ -49,6 +49,7 @@
 #include "ednssubnet.hh"
 #include "filterpo.hh"
 #include "negcache.hh"
+#include "proxy-protocol.hh"
 #include "sholder.hh"
 
 #ifdef HAVE_CONFIG_H
@@ -254,6 +255,8 @@ private:
   cont_t d_cont;
 };
 
+extern std::unique_ptr<NegCache> g_negCache;
+
 class SyncRes : public boost::noncopyable
 {
 public:
@@ -399,7 +402,6 @@ public:
   };
 
   struct ThreadLocalStorage {
-    NegCache negcache;
     nsspeeds_t nsSpeeds;
     throttle_t throttle;
     ednsstatus_t ednsstatus;
@@ -415,7 +417,7 @@ public:
   static uint64_t doDumpNSSpeeds(int fd);
   static uint64_t doDumpThrottleMap(int fd);
   static uint64_t doDumpFailedServers(int fd);
-  static int getRootNS(struct timeval now, asyncresolve_t asyncCallback);
+  static int getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth);
   static void clearDelegationOnly()
   {
     s_delegationOnly.clear();
@@ -551,32 +553,10 @@ public:
   {
     return t_sstorage.fails.value(server);
   }
-
-  static void clearNegCache()
-  {
-    t_sstorage.negcache.clear();
-  }
-
-  static uint64_t getNegCacheSize()
-  {
-    return t_sstorage.negcache.size();
-  }
-
-  static void pruneNegCache(unsigned int maxEntries)
-  {
-    t_sstorage.negcache.prune(maxEntries);
-  }
-
-  static uint64_t wipeNegCache(const DNSName& name, bool subtree = false)
-  {
-    return t_sstorage.negcache.wipe(name, subtree);
-  }
-
   static void setDomainMap(std::shared_ptr<domainmap_t> newMap)
   {
     t_sstorage.domainmap = newMap;
   }
-
   static const std::shared_ptr<domainmap_t> getDomainMap()
   {
     return t_sstorage.domainmap;
@@ -603,7 +583,8 @@ public:
 
   explicit SyncRes(const struct timeval& now);
 
-  int beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret);
+  int beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret, unsigned int depth = 0);
+
   void setId(int id)
   {
     if(doLog())
@@ -697,11 +678,6 @@ public:
     return d_now;
   }
 
-  void setSkipCNAMECheck(bool skip = false)
-  {
-    d_skipCNAMECheck = skip;
-  }
-
   void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
 
 #ifdef HAVE_PROTOBUF
@@ -733,6 +709,11 @@ public:
     return d_queryValidationState;
   }
 
+  void setQueryReceivedOverTCP(bool tcp)
+  {
+    d_queryReceivedOverTCP = tcp;
+  }
+
   static thread_local ThreadLocalStorage t_sstorage;
 
   static std::atomic<uint64_t> s_queries;
@@ -756,6 +737,7 @@ public:
   static unsigned int s_minimumTTL;
   static unsigned int s_minimumECSTTL;
   static unsigned int s_maxqperq;
+  static unsigned int s_maxnsaddressqperq;
   static unsigned int s_maxtotusec;
   static unsigned int s_maxdepth;
   static unsigned int s_maxnegttl;
@@ -770,6 +752,7 @@ public:
   static uint8_t s_ecsipv6limit;
   static uint8_t s_ecsipv4cachelimit;
   static uint8_t s_ecsipv6cachelimit;
+  static bool s_doIPv4;
   static bool s_doIPv6;
   static bool s_noEDNSPing;
   static bool s_noEDNS;
@@ -780,6 +763,9 @@ public:
 
   std::unordered_map<std::string,bool> d_discardedPolicies;
   DNSFilterEngine::Policy d_appliedPolicy;
+  std::unordered_set<std::string> d_policyTags;
+  boost::optional<string> d_routingTag;
+
   unsigned int d_authzonequeries;
   unsigned int d_outqueries;
   unsigned int d_tcpoutqueries;
@@ -822,30 +808,30 @@ private:
   bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state);
 
   int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
-  int doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL);
+  int doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL, bool considerforwards = true);
   bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res);
   bool doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
-  bool isForwardOrAuth(const DNSName &qname) const;
+  bool isRecursiveForwardOrAuth(const DNSName &qname) const;
   domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
   bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse);
   bool doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
-  void getBestNSFromCache(const DNSName &qname, const QType &qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere);
+  void getBestNSFromCache(const DNSName &qname, const QType &qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
   DNSName getBestNSNamesFromCache(const DNSName &qname, const QType &qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
 
   inline vector<std::pair<DNSName, float>> shuffleInSpeedOrder(NsSet &nameservers, const string &prefix);
   inline vector<ComboAddress> shuffleForwardSpeed(const vector<ComboAddress> &rnameservers, const string &prefix, const bool wasRd);
   bool moreSpecificThan(const DNSName& a, const DNSName &b) const;
-  vector<ComboAddress> getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly);
+  vector<ComboAddress> getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS);
 
   bool nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers);
   bool nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress&);
   bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType& qtype, bool pierceDontQuery);
 
-  vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly);
+  vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, float>>::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<std::pair<DNSName, float>>& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly, unsigned int& addressQueriesForNS);
 
   void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
   RCode::rcodes_ updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery);
-  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, vState& state, const bool needWildcardProof, const bool gatherwildcardProof, const unsigned int wildcardLabelsCount);
+  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, vState& state, const bool needWildcardProof, const bool gatherwildcardProof, const unsigned int wildcardLabelsCount, int& rcode, unsigned int depth);
 
   bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t qclass, vector<DNSRecord> &ret);
 
@@ -861,7 +847,7 @@ private:
   vState getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
   dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned);
   void updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool allowOptOut);
-  void computeNegCacheValidationStatus(const NegCache::NegCacheEntry* ne, const DNSName& qname, const QType& qtype, const int res, vState& state, unsigned int depth);
+  void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, const QType& qtype, const int res, vState& state, unsigned int depth);
   vState getTA(const DNSName& zone, dsmap_t& ds);
   bool haveExactValidationStatus(const DNSName& domain);
   vState getValidationStatus(const DNSName& subdomain, bool allowIndeterminate=true);
@@ -870,6 +856,10 @@ private:
   bool lookForCut(const DNSName& qname, unsigned int depth, const vState existingState, vState& newState);
   void computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth);
 
+  void handleNewTarget(const std::string& prefix, const DNSName& qname, const DNSName& newtarget, uint16_t qtype, std::vector<DNSRecord>& ret, int& rcode, int depth, const std::vector<DNSRecord>& recordsFromAnswer, vState& state);
+
+  void handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType& qtype, vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth);
+
   void setUpdatingRootNS()
   {
     d_updatingRootNS = true;
@@ -887,7 +877,7 @@ private:
   asyncresolve_t d_asyncResolve{nullptr};
   struct timeval d_now;
   string d_prefix;
-  vState d_queryValidationState{Indeterminate};
+  vState d_queryValidationState{vState::Indeterminate};
 
   /* 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
@@ -897,12 +887,12 @@ private:
   bool d_DNSSECValidationRequested{false};
   bool d_doEDNS0{true};
   bool d_requireAuthData{true};
-  bool d_skipCNAMECheck{false};
   bool d_updatingRootNS{false};
   bool d_wantsRPZ{true};
   bool d_wasOutOfBand{false};
   bool d_wasVariable{false};
   bool d_qNameMinimization{false};
+  bool d_queryReceivedOverTCP{false};
 
   LogMode d_lm;
 };
@@ -965,7 +955,7 @@ struct PacketIDBirthdayCompare: public std::binary_function<PacketID, PacketID,
     return a.domain < b.domain;
   }
 };
-extern std::unique_ptr<MemRecursorCache> s_RC;
+extern std::unique_ptr<MemRecursorCache> g_recCache;
 extern thread_local std::unique_ptr<RecursorPacketCache> t_packetCache;
 typedef MTasker<PacketID,string> MT_t;
 MT_t* getMT();
@@ -1018,6 +1008,7 @@ struct RecursorStats
   std::map<vState, std::atomic<uint64_t> > dnssecResults;
   std::map<DNSFilterEngine::PolicyKind, std::atomic<uint64_t> > policyResults;
   std::atomic<uint64_t> rebalancedQueries{0};
+  std::atomic<uint64_t> proxyProtocolInvalidCount{0};
 };
 
 //! represents a running TCP/IP client session
@@ -1032,10 +1023,15 @@ public:
     return d_fd;
   }
 
+  std::vector<ProxyProtocolValue> proxyProtocolValues;
   std::string data;
   const ComboAddress d_remote;
+  ComboAddress d_source;
+  ComboAddress d_destination;
   size_t queriesCount{0};
-  enum stateenum {BYTE0, BYTE1, GETQUESTION, DONE} state{BYTE0};
+  size_t proxyProtocolGot{0};
+  ssize_t proxyProtocolNeed{0};
+  enum stateenum {PROXYPROTOCOLHEADER, BYTE0, BYTE1, GETQUESTION, DONE} state{BYTE0};
   uint16_t qlen{0};
   uint16_t bytesread{0};
   uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
@@ -1059,6 +1055,14 @@ class PolicyHitException
 {
 };
 
+class ImmediateQueryDropException
+{
+};
+
+class SendTruncatedAnswerException
+{
+};
+
 typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
 extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
 
@@ -1077,13 +1081,14 @@ extern bool g_lowercaseOutgoing;
 
 std::string reloadAuthAndForwards();
 ComboAddress parseIPAndPort(const std::string& input, uint16_t port);
-ComboAddress getQueryLocalAddress(int family, uint16_t port);
 typedef boost::function<void*(void)> pipefunc_t;
 void broadcastFunction(const pipefunc_t& func);
 void distributeAsyncFunction(const std::string& question, const pipefunc_t& func);
 
 int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<DNSRecord>& ret);
 int followCNAMERecords(std::vector<DNSRecord>& ret, const QType& qtype);
+int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret);
+int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret);
 
 template<class T> T broadcastAccFunction(const boost::function<T*()>& func);
 
@@ -1100,8 +1105,8 @@ uint64_t* pleaseWipeCache(const DNSName& canon, bool subtree=false, uint16_t qty
 uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree, uint16_t qtype=0xffff);
 uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree=false);
 void doCarbonDump(void*);
-void primeHints(void);
-void primeRootNSZones(bool);
+bool primeHints(void);
+void primeRootNSZones(bool, unsigned int depth);
 
 extern __thread struct timeval g_now;
 
index 80cf3f9f4ca9ad0f9c7db036cf3042a38a71b449..6a56ce13b4546016522ee1128a68b483d235745c 100644 (file)
@@ -53,7 +53,6 @@
 #include "common_startup.hh"
 #include "packethandler.hh"
 #include "statbag.hh"
-#include "resolver.hh"
 #include "communicator.hh"
 #include "namespaces.hh"
 #include "signingpipe.hh"
@@ -66,7 +65,7 @@ extern StatBag S;
 \brief This file implements the tcpreceiver that receives and answers questions over TCP/IP
 */
 
-pthread_mutex_t TCPNameserver::s_plock = PTHREAD_MUTEX_INITIALIZER;
+std::mutex TCPNameserver::s_plock;
 std::unique_ptr<Semaphore> TCPNameserver::d_connectionroom_sem{nullptr};
 std::unique_ptr<PacketHandler> TCPNameserver::s_P{nullptr};
 unsigned int TCPNameserver::d_maxTCPConnections = 0;
@@ -88,13 +87,9 @@ void TCPNameserver::go()
   catch(PDNSException &ae) {
     g_log<<Logger::Error<<"TCP server is unable to launch backends - will try again when questions come in: "<<ae.reason<<endl;
   }
-  pthread_create(&d_tid, 0, launcher, static_cast<void *>(this));
-}
 
-void *TCPNameserver::launcher(void *data)
-{
-  static_cast<TCPNameserver *>(data)->thread();
-  return 0;
+  std::thread th(std::bind(&TCPNameserver::thread, this));
+  th.detach();
 }
 
 // throws PDNSException if things didn't go according to plan, returns 0 if really 0 bytes were read
@@ -173,33 +168,6 @@ static void writenWithTimeout(int fd, const void *buffer, unsigned int n, unsign
   }
 }
 
-void connectWithTimeout(int fd, struct sockaddr* remote, size_t socklen)
-{
-  int err;
-  Utility::socklen_t len=sizeof(err);
-
-  if((err=connect(fd, remote, socklen))<0 && errno!=EINPROGRESS)
-    throw NetworkError("connect: "+stringerror());
-
-  if(!err)
-    goto done;
-  
-  err=waitForRWData(fd, false, 5, 0);
-  if(err == 0)
-    throw NetworkError("Timeout connecting to remote");
-  if(err < 0)
-    throw NetworkError("Error connecting to remote");
-
-  if(getsockopt(fd, SOL_SOCKET,SO_ERROR,(char *)&err,&len)<0)
-    throw NetworkError("Error connecting to remote: "+stringerror()); // Solaris
-
-  if(err)
-    throw NetworkError("Error connecting to remote: "+string(strerror(err)));
-
- done:
-  ;
-}
-
 void TCPNameserver::sendPacket(std::unique_ptr<DNSPacket>& p, int outsock)
 {
   g_rs.submitResponse(*p, false);
@@ -252,12 +220,10 @@ void TCPNameserver::decrementClientCount(const ComboAddress& remote)
   }
 }
 
-void *TCPNameserver::doConnection(void *data)
+void TCPNameserver::doConnection(int fd)
 {
   setThreadName("pdns/tcpConnect");
   std::unique_ptr<DNSPacket> packet;
-  // Fix gcc-4.0 error (on AMD64)
-  int fd=(int)(long)data; // gotta love C (generates a harmless warning on opteron)
   ComboAddress remote;
   socklen_t remotelen=sizeof(remote);
   size_t transactions = 0;
@@ -266,7 +232,6 @@ void *TCPNameserver::doConnection(void *data)
     start = time(NULL);
   }
 
-  pthread_detach(pthread_self());
   if(getpeername(fd, (struct sockaddr *)&remote, &remotelen) < 0) {
     g_log<<Logger::Warning<<"Received question from socket which had no remote address, dropping ("<<stringerror()<<")"<<endl;
     d_connectionroom_sem->post();
@@ -276,7 +241,7 @@ void *TCPNameserver::doConnection(void *data)
     catch(const PDNSException& e) {
       g_log<<Logger::Error<<"Error closing TCP socket: "<<e.reason<<endl;
     }
-    return 0;
+    return;
   }
 
   setNonBlocking(fd);
@@ -379,7 +344,7 @@ void *TCPNameserver::doConnection(void *data)
         }
       }
       {
-        Lock l(&s_plock);
+        std::lock_guard<std::mutex> l(s_plock);
         if(!s_P) {
           g_log<<Logger::Error<<"TCP server is without backend connections, launching"<<endl;
           s_P=make_unique<PacketHandler>();
@@ -395,7 +360,7 @@ void *TCPNameserver::doConnection(void *data)
     }
   }
   catch(PDNSException &ae) {
-    Lock l(&s_plock);
+    std::lock_guard<std::mutex> l(s_plock);
     s_P.reset(); // on next call, backend will be recycled
     g_log<<Logger::Error<<"TCP nameserver had error, cycling backend: "<<ae.reason<<endl;
   }
@@ -419,8 +384,6 @@ void *TCPNameserver::doConnection(void *data)
     g_log<<Logger::Error<<"Error closing TCP socket: "<<e.reason<<endl;
   }
   decrementClientCount(remote);
-
-  return 0;
 }
 
 
@@ -438,29 +401,9 @@ bool TCPNameserver::canDoAXFR(std::unique_ptr<DNSPacket>& q)
       return false;
     } else {
       getTSIGHashEnum(trc.d_algoName, q->d_tsig_algo);
-      if (q->d_tsig_algo == TSIG_GSS) {
-        GssContext gssctx(keyname);
-        if (!gssctx.getPeerPrincipal(q->d_peer_principal)) {
-          g_log<<Logger::Warning<<"Failed to extract peer principal from GSS context with keyname '"<<keyname<<"'"<<endl;
-        }
-      }
     }
 
     DNSSECKeeper dk(s_P->getBackend());
-
-    if (q->d_tsig_algo == TSIG_GSS) {
-      vector<string> princs;
-      s_P->getBackend()->getDomainMetadata(q->qdomain, "GSS-ALLOW-AXFR-PRINCIPAL", princs);
-      for(const std::string& princ :  princs) {
-        if (q->d_peer_principal == princ) {
-          g_log<<Logger::Warning<<"AXFR of domain '"<<q->qdomain<<"' allowed: TSIG signed request with authorized principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig'"<<endl;
-          return true;
-        }
-      }
-      g_log<<Logger::Warning<<"AXFR of domain '"<<q->qdomain<<"' denied: TSIG signed request with principal '"<<q->d_peer_principal<<"' and algorithm 'gss-tsig' is not permitted"<<endl;
-      return false;
-    }
-
     if(!dk.TSIGGrantsAccess(q->qdomain, keyname)) {
       g_log<<Logger::Error<<"AXFR '"<<q->qdomain<<"' denied: key with name '"<<keyname<<"' and algorithm '"<<getTSIGAlgoName(q->d_tsig_algo)<<"' does not grant access to zone"<<endl;
       return false;
@@ -565,7 +508,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
   // determine if zone exists and AXFR is allowed using existing backend before spawning a new backend.
   SOAData sd;
   {
-    Lock l(&s_plock);
+    std::lock_guard<std::mutex> l(s_plock);
     DLOG(g_log<<"Looking for SOA"<<endl);    // find domain_id via SOA and list complete domain. No SOA, no AXFR
     if(!s_P) {
       g_log<<Logger::Error<<"TCP server is without backend connections in doAXFR, launching"<<endl;
@@ -632,15 +575,14 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     DNSName algorithm=trc.d_algoName; // FIXME400: check
     if (algorithm == DNSName("hmac-md5.sig-alg.reg.int"))
       algorithm = DNSName("hmac-md5");
-    if (algorithm != DNSName("gss-tsig")) {
-      if(!db.getTSIGKey(tsigkeyname, &algorithm, &tsig64)) {
-        g_log<<Logger::Error<<"TSIG key '"<<tsigkeyname<<"' for domain '"<<target<<"' not found"<<endl;
-        return 0;
-      }
-      if (B64Decode(tsig64, tsigsecret) == -1) {
-        g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname<<"' for domain '"<<target<<"'"<<endl;
-        return 0;
-      }
+
+    if(!db.getTSIGKey(tsigkeyname, &algorithm, &tsig64)) {
+      g_log<<Logger::Error<<"TSIG key '"<<tsigkeyname<<"' for domain '"<<target<<"' not found"<<endl;
+      return 0;
+    }
+    if (B64Decode(tsig64, tsigsecret) == -1) {
+      g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname<<"' for domain '"<<target<<"'"<<endl;
+      return 0;
     }
   }
   
@@ -1094,10 +1036,12 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
 
   g_log<<Logger::Error<<"IXFR of domain '"<<q->qdomain<<"' initiated by "<<q->getRemote()<<" with serial "<<serial<<endl;
 
-  // determine if zone exists and AXFR is allowed using existing backend before spawning a new backend.
+  // determine if zone exists, XFR is allowed, and if IXFR can proceed using existing backend before spawning a new backend.
   SOAData sd;
+  bool securedZone;
+  bool serialPermitsIXFR;
   {
-    Lock l(&s_plock);
+    std::lock_guard<std::mutex> l(s_plock);
     DLOG(g_log<<"Looking for SOA"<<endl); // find domain_id via SOA and list complete domain. No SOA, no IXFR
     if(!s_P) {
       g_log<<Logger::Error<<"TCP server is without backend connections in doIXFR, launching"<<endl;
@@ -1111,39 +1055,33 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
       sendPacket(outpacket,outsock);
       return 0;
     }
-  }
 
-  DNSSECKeeper dk;
-  NSEC3PARAMRecordContent ns3pr;
-  bool narrow;
-
-  DNSSECKeeper::clearCaches(q->qdomain);
-  bool securedZone = dk.isSecuredZone(q->qdomain);
-  if(dk.getNSEC3PARAM(q->qdomain, &ns3pr, &narrow)) {
-    if(narrow) {
-      g_log<<Logger::Error<<"Not doing IXFR of an NSEC3 narrow zone."<<endl;
-      g_log<<Logger::Error<<"IXFR of domain '"<<q->qdomain<<"' denied to "<<q->getRemote()<<endl;
-      outpacket->setRcode(RCode::Refused);
-      sendPacket(outpacket,outsock);
-      return 0;
+    DNSSECKeeper dk(s_P->getBackend());
+    DNSSECKeeper::clearCaches(q->qdomain);
+    bool narrow;
+    securedZone = dk.isSecuredZone(q->qdomain);
+    if(dk.getNSEC3PARAM(q->qdomain, nullptr, &narrow)) {
+      if(narrow) {
+        g_log<<Logger::Error<<"Not doing IXFR of an NSEC3 narrow zone."<<endl;
+        g_log<<Logger::Error<<"IXFR of domain '"<<q->qdomain<<"' denied to "<<q->getRemote()<<endl;
+        outpacket->setRcode(RCode::Refused);
+        sendPacket(outpacket,outsock);
+        return 0;
+      }
     }
-  }
 
-  DNSName target = q->qdomain;
-
-  UeberBackend db;
-  if(!db.getSOAUncached(target, sd)) {
-    g_log<<Logger::Error<<"IXFR of domain '"<<target<<"' failed: not authoritative in second instance"<<endl;
-    outpacket->setRcode(RCode::NotAuth);
-    sendPacket(outpacket,outsock);
-    return 0;
+    serialPermitsIXFR = !rfc1982LessThan(serial, calculateEditSOA(sd.serial, dk, sd.qname));
   }
 
-  if (!rfc1982LessThan(serial, calculateEditSOA(sd.serial, dk, sd.qname))) {
+  if (serialPermitsIXFR) {
+    DNSName target = q->qdomain;
     TSIGRecordContent trc;
     DNSName tsigkeyname;
     string tsigsecret;
 
+    UeberBackend db;
+    DNSSECKeeper dk(&db);
+
     bool haveTSIGDetails = q->getTSIGDetails(&trc, &tsigkeyname);
 
     if(haveTSIGDetails && !tsigkeyname.empty()) {
@@ -1151,8 +1089,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
       DNSName algorithm=trc.d_algoName; // FIXME400: was toLowerCanonic, compare output
       if (algorithm == DNSName("hmac-md5.sig-alg.reg.int"))
         algorithm = DNSName("hmac-md5");
-      Lock l(&s_plock);
-      if(!s_P->getBackend()->getTSIGKey(tsigkeyname, &algorithm, &tsig64)) {
+      if(!db.getTSIGKey(tsigkeyname, &algorithm, &tsig64)) {
         g_log<<Logger::Error<<"TSIG key '"<<tsigkeyname<<"' for domain '"<<target<<"' not found"<<endl;
         return 0;
       }
@@ -1162,8 +1099,6 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
       }
     }
 
-    UeberBackend signatureDB;
-
     // SOA *must* go out first, our signing pipe might reorder
     DLOG(g_log<<"Sending out SOA"<<endl);
     DNSZoneRecord soa = makeEditedDNSZRFromSOAData(dk, sd);
@@ -1171,7 +1106,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
     if(securedZone && outpacket->d_dnssecOk) {
       set<DNSName> authSet;
       authSet.insert(target);
-      addRRSigs(dk, signatureDB, authSet, outpacket->getRRS());
+      addRRSigs(dk, db, authSet, outpacket->getRRS());
     }
 
     if(haveTSIGDetails && !tsigkeyname.empty())
@@ -1184,7 +1119,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
     return 1;
   }
 
-  g_log<<Logger::Error<<"IXFR fallback to AXFR for domain '"<<target<<"' our serial "<<sd.serial<<endl;
+  g_log<<Logger::Error<<"IXFR fallback to AXFR for domain '"<<q->qdomain<<"' our serial "<<sd.serial<<endl;
   return doAXFR(q->qdomain, q, outsock);
 }
 
@@ -1202,7 +1137,6 @@ TCPNameserver::TCPNameserver()
 //  sem_init(&d_connectionroom_sem,0,::arg().asNum("max-tcp-connections"));
   d_connectionroom_sem = make_unique<Semaphore>( ::arg().asNum( "max-tcp-connections" ));
   d_maxTCPConnections = ::arg().asNum( "max-tcp-connections" );
-  d_tid=0;
 
   vector<string>locals;
   stringtok(locals,::arg()["local-ipv6"]," ,");
@@ -1311,7 +1245,6 @@ void TCPNameserver::thread()
               s_clientsCount[remote]++;
             }
 
-            pthread_t tid;
             d_connectionroom_sem->wait(); // blocks if no connections are available
 
             int room;
@@ -1319,9 +1252,12 @@ void TCPNameserver::thread()
             if(room<1)
               g_log<<Logger::Warning<<"Limit of simultaneous TCP connections reached - raise max-tcp-connections"<<endl;
 
-            int err;
-            if((err = pthread_create(&tid, 0, &doConnection, reinterpret_cast<void*>(fd)))) {
-              g_log<<Logger::Error<<"Error creating thread: "<<stringerror(err)<<endl;
+            try {
+              std::thread connThread(doConnection, fd);
+              connThread.detach();
+            }
+            catch (std::exception& e) {
+              g_log<<Logger::Error<<"Error creating thread: "<<e.what()<<endl;
               d_connectionroom_sem->post();
               close(fd);
               decrementClientCount(remote);
index cafe7943312eab80c44ac3138220526dbf09fed7..5220d5b5f2ef8e78cad0641375159506b8657aaf 100644 (file)
@@ -54,15 +54,13 @@ private:
   static int doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock);
   static int doIXFR(std::unique_ptr<DNSPacket>& q, int outsock);
   static bool canDoAXFR(std::unique_ptr<DNSPacket>& q);
-  static void *doConnection(void *data);
-  static void *launcher(void *data);
+  static void doConnection(int fd);
   static void decrementClientCount(const ComboAddress& remote);
   void thread(void);
-  static pthread_mutex_t s_plock;
+  static std::mutex s_plock;
   static std::mutex s_clientsCountMutex;
   static std::map<ComboAddress,size_t,ComboAddress::addressOnlyLessThan> s_clientsCount;
   static std::unique_ptr<PacketHandler> s_P;
-  pthread_t d_tid;
   static std::unique_ptr<Semaphore> d_connectionroom_sem;
   static unsigned int d_maxTCPConnections;
   static NetmaskGroup d_ng;
index 8482991e4b55618d1e8745e4e289ee5566f7bb72..13a31597a535e5a186fbf665c7a76c130a8560b1 100644 (file)
@@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(test_dns_random_getrandom_average) {
 #endif
 
 #if defined(HAVE_ARC4RANDOM)
-BOOST_AUTO_TEST_CASE(test_dns_random_getrandom_average) {
+BOOST_AUTO_TEST_CASE(test_dns_random_arc4random_average) {
 
   ::arg().set("rng")="arc4random";
   ::arg().set("entropy-source")="/dev/urandom";
index c54c1a365a9e20714ae6f1ef6a5d0cf6d52a8767..e32522433d9d4ac7c2e6824d6ac6a808c7218b0b 100644 (file)
@@ -1985,4 +1985,75 @@ BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) {
+  const ComboAddress remote("192.168.1.25");
+  const DNSName name("www.powerdns.com.");
+  const ComboAddress origRemote("127.0.0.1");
+  const ComboAddress v4("192.0.2.1");
+
+  {
+    /* no EDNS and no other additional record */
+    vector<uint8_t> query;
+    DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.commit();
+
+    /* large enough packet */
+    char packet[1500];
+    memcpy(packet, query.data(), query.size());
+
+    unsigned int consumed = 0;
+    uint16_t qtype;
+    uint16_t qclass;
+    DNSName qname(packet, query.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    DNSQuestion dq(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast<dnsheader*>(packet), sizeof(packet), query.size(), false, nullptr);
+
+    BOOST_CHECK(!parseEDNSOptions(dq));
+  }
+
+  {
+    /* nothing in additional (so no EDNS) but a record in ANSWER */
+    vector<uint8_t> query;
+    DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER);
+    pw.xfrIP(v4.sin4.sin_addr.s_addr);
+    pw.commit();
+
+    /* large enough packet */
+    char packet[1500];
+    memcpy(packet, query.data(), query.size());
+
+    unsigned int consumed = 0;
+    uint16_t qtype;
+    uint16_t qclass;
+    DNSName qname(packet, query.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    DNSQuestion dq(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast<dnsheader*>(packet), sizeof(packet), query.size(), false, nullptr);
+
+    BOOST_CHECK(!parseEDNSOptions(dq));
+  }
+
+  {
+    /* nothing in additional (so no EDNS) but a record in AUTHORITY */
+    vector<uint8_t> query;
+    DNSPacketWriter pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::AUTHORITY);
+    pw.xfrIP(v4.sin4.sin_addr.s_addr);
+    pw.commit();
+
+    /* large enough packet */
+    char packet[1500];
+    memcpy(packet, query.data(), query.size());
+
+    unsigned int consumed = 0;
+    uint16_t qtype;
+    uint16_t qclass;
+    DNSName qname(packet, query.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    DNSQuestion dq(&qname, qtype, qclass, consumed, nullptr, &remote, reinterpret_cast<dnsheader*>(packet), sizeof(packet), query.size(), false, nullptr);
+
+    BOOST_CHECK(!parseEDNSOptions(dq));
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END();
index 8e41798cb99d13f56045a8692a45d30a41471119..3ea30998c13a2fd557644ec7ba5f4e93137cde3c 100644 (file)
@@ -11,6 +11,7 @@
 #include "dnswriter.hh"
 #include "dnsdist-cache.hh"
 #include "gettime.hh"
+#include "packetcache.hh"
 
 BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
 
@@ -297,14 +298,13 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
 
 static DNSDistPacketCache g_PC(500000);
 
-static void *threadMangler(void* off)
+static void threadMangler(unsigned int offset)
 {
   struct timespec queryTime;
   gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
   try {
     ComboAddress remote;
     bool dnssecOK = false;
-    unsigned int offset=(unsigned int)(unsigned long)off;
     for(unsigned int counter=0; counter < 100000; ++counter) {
       DNSName a=DNSName("hello ")+DNSName(std::to_string(counter+offset));
       vector<uint8_t> query;
@@ -337,19 +337,17 @@ static void *threadMangler(void* off)
     cerr<<"Had error: "<<e.reason<<endl;
     throw;
   }
-  return 0;
 }
 
 AtomicCounter g_missing;
 
-static void *threadReader(void* off)
+static void threadReader(unsigned int offset)
 {
   bool dnssecOK = false;
   struct timespec queryTime;
   gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
   try
   {
-    unsigned int offset=(unsigned int)(unsigned long)off;
     vector<DNSResourceRecord> entry;
     ComboAddress remote;
     for(unsigned int counter=0; counter < 100000; ++counter) {
@@ -373,25 +371,31 @@ static void *threadReader(void* off)
     cerr<<"Had error in threadReader: "<<e.reason<<endl;
     throw;
   }
-  return 0;
 }
 
 BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
   try {
-    pthread_t tid[4];
-    for(int i=0; i < 4; ++i)
-      pthread_create(&tid[i], 0, threadMangler, (void*)(i*1000000UL));
-    void* res;
-    for(int i=0; i < 4 ; ++i)
-      pthread_join(tid[i], &res);
+    std::vector<std::thread> threads;
+    for (int i = 0; i < 4; ++i) {
+      threads.push_back(std::thread(threadMangler, i*1000000UL));
+    }
+
+    for (auto& t : threads) {
+      t.join();
+    }
+
+    threads.clear();
 
     BOOST_CHECK_EQUAL(g_PC.getSize() + g_PC.getDeferredInserts() + g_PC.getInsertCollisions(), 400000U);
     BOOST_CHECK_SMALL(1.0*g_PC.getInsertCollisions(), 10000.0);
 
-    for(int i=0; i < 4; ++i)
-      pthread_create(&tid[i], 0, threadReader, (void*)(i*1000000UL));
-    for(int i=0; i < 4 ; ++i)
-      pthread_join(tid[i], &res);
+    for (int i = 0; i < 4; ++i) {
+      threads.push_back(std::thread(threadReader, i*1000000UL));
+    }
+
+    for (auto& t : threads) {
+      t.join();
+    }
 
     BOOST_CHECK((g_PC.getDeferredInserts() + g_PC.getDeferredLookups() + g_PC.getInsertCollisions()) >= g_missing);
   }
@@ -415,7 +419,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
   boost::optional<Netmask> subnetOut;
   bool dnssecOK = false;
 
-  /* lookup for a query with an ECS value of 10.0.118.46/32,
+  /* lookup for a query with a first ECS value,
      insert a corresponding response */
   {
     vector<uint8_t> query;
@@ -424,7 +428,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
     pwQ.getHeader()->id = qid;
     DNSPacketWriter::optvect_t ednsOptions;
     EDNSSubnetOpts opt;
-    opt.source = Netmask("10.0.118.46/32");
+    opt.source = Netmask("10.0.59.220/32");
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     pwQ.addOpt(512, 0, 0, ednsOptions);
     pwQ.commit();
@@ -460,7 +464,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
     BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
   }
 
-  /* now lookup for the same query with an ECS value of 10.0.123.193/32
+  /* now lookup for the same query with a different ECS value,
      we should get the same key (collision) but no match */
   {
     vector<uint8_t> query;
@@ -469,7 +473,7 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
     pwQ.getHeader()->id = qid;
     DNSPacketWriter::optvect_t ednsOptions;
     EDNSSubnetOpts opt;
-    opt.source = Netmask("10.0.123.193/32");
+    opt.source = Netmask("10.0.167.48/32");
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     pwQ.addOpt(512, 0, 0, ednsOptions);
     pwQ.commit();
@@ -487,6 +491,47 @@ BOOST_AUTO_TEST_CASE(test_PCCollision) {
     BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
     BOOST_CHECK_EQUAL(PC.getLookupCollisions(), 1U);
   }
+
+#if 0
+  /* to be able to compute a new collision if the packet cache hashing code is updated */
+  {
+    DNSDistPacketCache pc(10000);
+    DNSPacketWriter::optvect_t ednsOptions;
+    EDNSSubnetOpts opt;
+    std::map<uint32_t, Netmask> colMap;
+    size_t collisions = 0;
+    size_t total = 0;
+    //qname = DNSName("collision-with-ecs-parsing.cache.tests.powerdns.com.");
+
+    for (size_t idxA = 0; idxA < 256; idxA++) {
+      for (size_t idxB = 0; idxB < 256; idxB++) {
+        for (size_t idxC = 0; idxC < 256; idxC++) {
+          vector<uint8_t> secondQuery;
+          DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+          pwFQ.getHeader()->rd = 1;
+          pwFQ.getHeader()->qr = false;
+          pwFQ.getHeader()->id = 0x42;
+          opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+          ednsOptions.clear();
+          ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+          pwFQ.addOpt(512, 0, 0, ednsOptions);
+          pwFQ.commit();
+          secondKey = pc.getKey(qname.toDNSString(), qname.wirelength(), secondQuery.data(), secondQuery.size(), false);
+          auto pair = colMap.insert(std::make_pair(secondKey, opt.source));
+          total++;
+          if (!pair.second) {
+            collisions++;
+            cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+            goto done;
+          }
+        }
+      }
+    }
+  done:
+    cerr<<"collisions: "<<collisions<<endl;
+    cerr<<"total: "<<total<<endl;
+  }
+#endif
 }
 
 BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
index 4688c90b4c12b9af3b4ae6df8792ee4c44743087..a17738253aaa51ed35956655fbc8cc6c4994adb8 100644 (file)
@@ -26,7 +26,7 @@ namespace {
 // CASE_L can be used where this is not the case. See LOC below for a good example why this might happen
 
 /*   (CASE_S(QType::NAME, "zone format", "line format")) */
-/*   (CASE_L(QType::NAME, "zone format", "canonic zone format", "line format")) */
+/*   (CASE_L(QType::NAME, "zone format", "canonical zone format", "line format")) */
 
 #define _CASE_L(type, inval, zoneval, lineval, broken) case_t(type, BINARY(inval), BINARY(zoneval), BINARY(lineval), broken)
 #define CASE_L(type, inval, zoneval, lineval) _CASE_L(type, inval, zoneval, lineval, broken_marker::WORKING)
@@ -132,6 +132,25 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
 
 // X.509 as per PKIX
      (CASE_S(QType::CERT, "1 0 0 MIIB9DCCAV2gAwIBAgIJAKxUfFVXhw7HMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNVBAMMCHJlYy50ZXN0MB4XDTEzMDUxMjE5NDgwOVoXDTEzMDYxMTE5NDgwOVowEzERMA8GA1UEAwwIcmVjLnRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANKCu5aN/ewOXRPfzAo27XMXhYFCThCjfInTAUIEkzs6jBFZ/eyyIa/kFoiD0tAKwfFfykYU+9XgXeLjetD7rYt3SN3bzzCznoBGbGHHM0Fecrn0LV+tC/NfBB61Yx7e0AMUxmxIeLNRQW5ca5CW8qcIiiQ4fl0BScUjc5+E9QLHAgMBAAGjUDBOMB0GA1UdDgQWBBRzcVu/2bwrgkES+FhYbxZqr7mUgjAfBgNVHSMEGDAWgBRzcVu/2bwrgkES+FhYbxZqr7mUgjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAFVQ8dZBOasOhsWzA/xpAV0WdsqVkxBxrkGIRlbHHBFqOBOOz2MFSzUNx4mDy0qDKI28gcWmWaVsxoQ9VFLD6YRJuUoM8MDNcZDJbKpfDumjvvfnUAK+SiM2c4Ur3xpf0wanCA60/q2bOtFiB0tfAH6RVuIgMC3qjHAIaKEld+fE", "\x00\x01\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4"))
+     (CASE_S(QType::APL,"1:10.0.0.0/32", "\x00\x01\x20\x01\x0a"))
+     (CASE_S(QType::APL,"1:10.1.1.1/32", "\x00\x01\x20\x04\x0a\x01\x01\x01"))
+     (CASE_S(QType::APL,"1:10.1.1.0/24", "\x00\x01\x18\x03\x0a\x01\x01"))
+     (CASE_S(QType::APL,"1:60.0.0.0/8", "\x00\x01\x08\x01\x3c"))
+     (CASE_S(QType::APL,"1:10.1.1.1/32", "\x00\x01\x20\x04\x0a\x01\x01\x01"))
+     (CASE_S(QType::APL,"!1:10.1.1.1/32", "\x00\x01\x20\x84\x0a\x01\x01\x01"))
+     (CASE_S(QType::APL,"1:255.255.255.255/32", "\x00\x01\x20\x04\xff\xff\xff\xff"))
+     (CASE_S(QType::APL,"2:100::/8", "\x00\x02\x08\x01\x01"))
+     (CASE_S(QType::APL,"2:20::/16", "\x00\x02\x10\x02\x00\x20"))
+     (CASE_S(QType::APL,"2:2000::/8", "\x00\x02\x08\x01\x20"))
+     (CASE_S(QType::APL,"2:fe00::/8", "\x00\x02\x08\x01\xfe"))
+     (CASE_S(QType::APL,"2:fe80::/16", "\x00\x02\x10\x02\xfe\x80"))
+     (CASE_S(QType::APL,"2:2001::1/128", "\x00\x02\x80\x10\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"))
+     (CASE_S(QType::APL,"!2:2001::1/128", "\x00\x02\x80\x90\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"))
+     (CASE_S(QType::APL,"2:fe80:1234:5678:9910:8bc:3359:b2e8:720e/128", "\x00\x02\x80\x10\xfe\x80\x12\x34\x56\x78\x99\x10\x08\xbc\x33\x59\xb2\xe8\x72\x0e"))
+     (CASE_S(QType::APL,"2:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128","\x00\x02\x80\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"))
+     (CASE_S(QType::APL,"", ""))
+     (CASE_S(QType::APL,"1:10.0.0.0/32 1:10.1.1.1/32", "\x00\x01\x20\x01\x0a\x00\x01\x20\x04\x0a\x01\x01\x01"))
+     (CASE_S(QType::APL,"1:10.0.0.0/32 2:100::/8", "\x00\x01\x20\x01\x0a\x00\x02\x08\x01\x01"))
      (CASE_L(QType::DS, "20642 8 2 04443ABE7E94C3985196BEAE5D548C727B044DDA5151E60D7CD76A9F D931D00E", "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e"))
      (CASE_S(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88"))
      (CASE_L(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc253 8d88", "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88"))
@@ -233,8 +252,8 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
       }
       recData = rec->serialize(DNSName("rec.test"));
 
-      std::shared_ptr<DNSRecordContent> rec2 = DNSRecordContent::unserialize(DNSName("rec.test"),q.getCode(),recData);
-      BOOST_CHECK_MESSAGE(rec2 != NULL, "unserialize(rec.test, " << q.getCode() << ", recData) should not return NULL");
+      std::shared_ptr<DNSRecordContent> rec2 = DNSRecordContent::deserialize(DNSName("rec.test"),q.getCode(),recData);
+      BOOST_CHECK_MESSAGE(rec2 != NULL, "deserialize(rec.test, " << q.getCode() << ", recData) should not return NULL");
       if (rec2 == NULL) continue;
       // now verify the zone representation (here it can be different!)
       REC_CHECK_EQUAL(rec2->getZoneRepresentation(), zoneval);
@@ -250,7 +269,7 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
  }
 }
 
-bool test_dnsrecords_cc_predicate( std::exception const &ex ) { return true; }
+static bool test_dnsrecords_cc_predicate( std::exception const &ex ) { return true; }
 
 // these *MUST NOT* parse properly!
 BOOST_AUTO_TEST_CASE(test_record_types_bad_values) {
@@ -378,6 +397,41 @@ BOOST_AUTO_TEST_CASE(test_opt_record_out) {
   BOOST_CHECK_EQUAL(makeHexDump(std::string(pak.begin(),pak.end())), makeHexDump(packet));
 }
 
+// special record test, because Unknown record types are the worst
+BOOST_AUTO_TEST_CASE(test_unknown_records_in) {
+
+  auto validUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 42");
+
+  // we need at least two parts
+  BOOST_CHECK_THROW(auto notEnoughPartsUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\#"), MOADNSException);
+
+  // two parts are OK when the RDATA size is 0, not OK otherwise
+  auto validEmptyUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 0");
+  BOOST_CHECK_THROW(auto twoPartsNotZeroUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1"), MOADNSException);
+
+  // the first part has to be "\#"
+  BOOST_CHECK_THROW(auto invalidFirstPartUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\$ 0"), MOADNSException);
+
+  // RDATA length is not even
+  BOOST_CHECK_THROW(auto unevenUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 A"), MOADNSException);
+
+  // RDATA length is not equal to the expected size
+  BOOST_CHECK_THROW(auto wrongRDATASizeUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 2 AA"), MOADNSException);
+
+  // RDATA is invalid (invalid hex value)
+  try {
+    auto invalidRDATAUnknown = DNSRecordContent::mastermake(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 1 JJ");
+    // we should not reach that code
+    BOOST_CHECK(false);
+    // but if we do let's see what we got (likely what was left over on the stack)
+    BOOST_CHECK_EQUAL(invalidRDATAUnknown->getZoneRepresentation(), "\\# 1 jj");
+  }
+  catch (const MOADNSException& e)
+  {
+    // it's expected to end up there
+  }
+}
+
 // special record test, because EUI are odd
 BOOST_AUTO_TEST_CASE(test_eui_records_in) {
 
index 03e75541195044c611490990d3c1de2805a3efa0..46e844c60aa20905b8ae4258d8c9a3ce201865c1 100644 (file)
@@ -11,27 +11,20 @@ using namespace boost;
 
 BOOST_AUTO_TEST_SUITE(test_lock_hh)
 
-static std::vector<std::unique_ptr<pthread_rwlock_t> > g_locks;
+static std::vector<ReadWriteLock> g_locks(1000);
 
 static void lthread()
 {
   std::vector<ReadLock> rlocks;
   for(auto& pp : g_locks)
-    rlocks.emplace_back(&*pp);
-  
+    rlocks.emplace_back(pp);
 }
 
 BOOST_AUTO_TEST_CASE(test_pdns_lock)
 {
-  for(unsigned int n=0; n < 1000; ++n) {
-    auto p = make_unique<pthread_rwlock_t>();
-    pthread_rwlock_init(p.get(), 0);
-    g_locks.emplace_back(std::move(p));
-  }
-
   std::vector<ReadLock> rlocks;
   for(auto& pp : g_locks)
-    rlocks.emplace_back(&*pp);
+    rlocks.emplace_back(pp);
 
   std::thread thr(lthread);
   thr.join();
@@ -39,13 +32,13 @@ BOOST_AUTO_TEST_CASE(test_pdns_lock)
 
   std::vector<WriteLock> wlocks;
   for(auto& pp : g_locks)
-    wlocks.emplace_back(&*pp);
+    wlocks.emplace_back(pp);
 
   // on macOS, this TryReadLock throws (EDEADLK) instead of simply failing
   // so we catch the exception and consider that success for this test
   bool gotit = false;
   try {
-    TryReadLock trl(&*g_locks[0]);
+    TryReadLock trl(g_locks.at(0));
     gotit = trl.gotIt();
   }
   catch(const PDNSException &e) {
@@ -54,12 +47,13 @@ BOOST_AUTO_TEST_CASE(test_pdns_lock)
   BOOST_CHECK(!gotit);
 
   wlocks.clear();
-  TryReadLock trl2(&*g_locks[0]);
-  BOOST_CHECK(trl2.gotIt());
-  
-  for(auto& pp : g_locks) {
-    pthread_rwlock_destroy(pp.get());
-  }  
+
+  {
+    TryReadLock trl2(g_locks.at(0));
+    BOOST_CHECK(trl2.gotIt());
+  }
+
+  g_locks.clear();
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index 1522850c3089a332a697415f52cee61c1efdc55c..52ac44ff74553a72f274f4155cb7d8194237fb76 100644 (file)
@@ -8,10 +8,13 @@
 #include <boost/assign/list_of.hpp>
 
 #include <boost/tuple/tuple.hpp>
-#include "misc.hh"
-#include "dns.hh"
+
 #include <arpa/inet.h>
-#include <utility>
+
+#include "dns.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "utility.hh"
 
 using std::string;
 
@@ -202,5 +205,26 @@ BOOST_AUTO_TEST_CASE(test_rfc1982LessThan) {
   BOOST_CHECK(rfc1982check<uint64_t>(UINT64_MAX/2, UINT64_MAX-10));
 }
 
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_CASE(test_reverse_name_to_ip)
+{
+  static const ComboAddress v4("192.0.2.1");
+  static const ComboAddress v6("2001:DB8::42");
+  BOOST_CHECK_EQUAL(reverseNameFromIP(v4).toString(), "1.2.0.192.in-addr.arpa.");
+  BOOST_CHECK_EQUAL(reverseNameFromIP(v6).toString(), "2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.");
+}
+
+BOOST_AUTO_TEST_CASE(test_getCarbonHostName)
+{
+  char buffer[4096];
+
+  BOOST_CHECK_EQUAL(gethostname(buffer, sizeof buffer), 0);
+  std::string my_hostname(buffer);
+  boost::replace_all(my_hostname, ".", "_");
 
+  std::string hostname = getCarbonHostName();
+  // ensure it matches what we get
+  BOOST_CHECK_EQUAL(my_hostname, hostname);
+  BOOST_CHECK_EQUAL(my_hostname.size(), hostname.size());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 39f5911998ff5d868320b4183132fb5ce0a60366..e37c4b062ee363df0912e228677b88a988b60413 100644 (file)
@@ -12,6 +12,8 @@
 #include "auth-querycache.hh"
 #include "arguments.hh"
 #include <utility>
+#include <thread>
+
 extern StatBag S;
 
 BOOST_AUTO_TEST_SUITE(test_packetcache_cc)
@@ -71,30 +73,26 @@ BOOST_AUTO_TEST_CASE(test_AuthQueryCacheSimple) {
 static AuthQueryCache* g_QC;
 static AtomicCounter g_QCmissing;
 
-static void *threadQCMangler(void* a)
+static void threadQCMangler(unsigned int offset)
 try
 {
   vector<DNSZoneRecord> records;
-  unsigned int offset=(unsigned int)(unsigned long)a;
   for(unsigned int counter=0; counter < 100000; ++counter)
     g_QC->insert(DNSName("hello ")+DNSName(std::to_string(counter+offset)), QType(QType::A), vector<DNSZoneRecord>(records), 3600, 1);
-  return 0;
 }
  catch(PDNSException& e) {
    cerr<<"Had error: "<<e.reason<<endl;
    throw;
  }
 
-static void *threadQCReader(void* a)
+static void threadQCReader(unsigned int offset)
 try
 {
-  unsigned int offset=(unsigned int)(unsigned long)a;
   vector<DNSZoneRecord> entry;
   for(unsigned int counter=0; counter < 100000; ++counter)
     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 threadQCReader: "<<e.reason<<endl;
@@ -107,20 +105,28 @@ BOOST_AUTO_TEST_CASE(test_QueryCacheThreaded) {
     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);
+    std::vector<std::thread> manglers;
+    for (int i=0; i < 4; ++i) {
+      manglers.push_back(std::thread(threadQCMangler, i*1000000UL));
+    }
+
+    for (auto& t : manglers) {
+      t.join();
+    }
+    manglers.clear();
 
     BOOST_CHECK_EQUAL(QC.size() + S.read("deferred-cache-inserts"), 400000U);
     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);
+    std::vector<std::thread> readers;
+    for (int i=0; i < 4; ++i) {
+      readers.push_back(std::thread(threadQCReader, i*1000000UL));
+    }
+
+    for (auto& t : readers) {
+      t.join();
+    }
+    readers.clear();
 
     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
@@ -135,10 +141,9 @@ BOOST_AUTO_TEST_CASE(test_QueryCacheThreaded) {
 static AuthPacketCache* g_PC;
 static AtomicCounter g_PCmissing;
 
-static void *threadPCMangler(void* a)
+static void threadPCMangler(unsigned int offset)
 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));
@@ -160,23 +165,20 @@ try
        we directly compute the hash instead of querying the
        cache because 1/ it's faster 2/ no deferred-lookup issues
     */
-    q.setHash(g_PC->canHashPacket(q.getString()));
+    q.setHash(g_PC->canHashPacket(q.getString(), false));
 
     const unsigned int maxTTL = 3600;
     g_PC->insert(q, r, maxTTL);
   }
-
-  return 0;
 }
  catch(PDNSException& e) {
    cerr<<"Had error: "<<e.reason<<endl;
    throw;
  }
 
-static void *threadPCReader(void* a)
+static void threadPCReader(unsigned int offset)
 try
 {
-  unsigned int offset=(unsigned int)(unsigned long)a;
   vector<DNSZoneRecord> entry;
   for(unsigned int counter=0; counter < 100000; ++counter) {
     vector<uint8_t> pak;
@@ -191,8 +193,6 @@ try
       g_PCmissing++;
     }
   }
-
-  return 0;
 }
 catch(PDNSException& e) {
   cerr<<"Had error in threadPCReader: "<<e.reason<<endl;
@@ -207,21 +207,29 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
 
     g_PC=&PC;
     g_PCmissing = 0;
-    pthread_t tid[4];
-    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);
+    std::vector<std::thread> manglers;
+    for (int i=0; i < 4; ++i) {
+      manglers.push_back(std::thread(threadPCMangler, i*1000000UL));
+    }
+
+    for (auto& t : manglers) {
+      t.join();
+    }
+    manglers.clear();
 
     BOOST_CHECK_EQUAL(PC.size() + S.read("deferred-packetcache-inserts"), 400000UL);
     BOOST_CHECK_EQUAL(S.read("deferred-packetcache-lookup"), 0UL);
     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);
+    std::vector<std::thread> readers;
+    for (int i=0; i < 4; ++i) {
+      readers.push_back(std::thread(threadPCReader, i*1000000UL));
+    }
+
+    for (auto& t : readers) {
+      t.join();
+    }
+    readers.clear();
 
 /*
     cerr<<"Misses: "<<S.read("packetcache-miss")<<endl;
@@ -243,14 +251,12 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
 }
 
 bool g_stopCleaning;
-static void *cacheCleaner(void*)
+static void cacheCleaner()
 try
 {
   while(!g_stopCleaning) {
     g_QC->cleanup();
   }
-
-  return 0;
 }
 catch(PDNSException& e) {
   cerr<<"Had error in cacheCleaner: "<<e.reason<<endl;
@@ -270,19 +276,24 @@ BOOST_AUTO_TEST_CASE(test_QueryCacheClean) {
     sleep(1);
 
     g_QC=&QC;
-    pthread_t tid[4];
+    std::vector<std::thread> readers;
+    for (int i=0; i < 4; ++i) {
+      if (i < 3) {
+        readers.push_back(std::thread(threadQCReader, i*1000000UL));
+      }
+      else {
+        readers.push_back(std::thread(cacheCleaner));
+      }
+    }
 
-    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);
+    for (int i = 0; i < 3 ; ++i) {
+      readers.at(i).join();
+    }
 
-    void *res;
-    for(int i=0; i < 3 ; ++i)
-      pthread_join(tid[i], &res);
     g_stopCleaning=true;
-    pthread_join(tid[3], &res);
+    readers.at(3).join();
+
+    readers.clear();
   }
   catch(PDNSException& e) {
     cerr<<"Had error in test_QueryCacheClean: "<<e.reason<<endl;
index 3ce89b79d854261a4340e658eefc9365a25f695d..6a55effcffe8850ce275b68591474b724338b0b0 100644 (file)
@@ -21,6 +21,8 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
   uint16_t qtype = QType::AAAA;
   EDNSSubnetOpts opt;
   DNSPacketWriter::optvect_t ednsOptions;
+  static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE };
+  static const std::unordered_set<uint16_t> noOptionsToSkip{ };
 
   {
     /* same query, different IDs */
@@ -30,7 +32,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw1.getHeader()->qr = false;
     pw1.getHeader()->id = 0x42;
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1);
+    auto hash1 = PacketCache::canHashPacket(spacket1, false);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
@@ -38,10 +40,10 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw2.getHeader()->qr = false;
     pw2.getHeader()->id = 0x84;
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2);
+    auto hash2 = PacketCache::canHashPacket(spacket2, false);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
-    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname));
+    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
   }
 
   {
@@ -51,32 +53,69 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw1.getHeader()->rd = true;
     pw1.getHeader()->qr = false;
     pw1.getHeader()->id = 0x42;
-    opt.source = Netmask("10.0.18.199/32");
+    opt.source = Netmask("10.0.152.74/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     pw1.addOpt(512, 0, 0, ednsOptions);
     pw1.commit();
 
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1);
+    auto hash1 = PacketCache::canHashPacket(spacket1, false);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
     pw2.getHeader()->rd = true;
     pw2.getHeader()->qr = false;
     pw2.getHeader()->id = 0x84;
-    opt.source = Netmask("10.0.131.66/32");
+    opt.source = Netmask("10.2.70.250/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     pw2.addOpt(512, 0, 0, ednsOptions);
     pw2.commit();
 
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2);
+    auto hash2 = PacketCache::canHashPacket(spacket2, false);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
     /* the hash is the same but we should _not_ match */
-    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname));
+    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
+
+#if 0
+    /* to be able to compute a new collision if the hashing function is updated */
+    {
+    std::map<uint32_t, Netmask> colMap;
+    size_t collisions = 0;
+    size_t total = 0;
+
+    for (size_t idxA = 0; idxA < 256; idxA++) {
+      for (size_t idxB = 0; idxB < 256; idxB++) {
+        for (size_t idxC = 0; idxC < 256; idxC++) {
+          vector<uint8_t> secondQuery;
+          DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+          pwFQ.getHeader()->rd = 1;
+          pwFQ.getHeader()->qr = false;
+          pwFQ.getHeader()->id = 0x42;
+          opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+          ednsOptions.clear();
+          ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+          pwFQ.addOpt(512, 0, 0, ednsOptions);
+          pwFQ.commit();
+          auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), false);
+          auto pair = colMap.insert(std::make_pair(secondKey, opt.source));
+          total++;
+          if (!pair.second) {
+            collisions++;
+            cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+            goto done1;
+          }
+        }
+      }
+    }
+  done1:
+    cerr<<"collisions: "<<collisions<<endl;
+    cerr<<"total: "<<total<<endl;
+    }
+#endif
   }
 
   {
@@ -86,21 +125,21 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw1.getHeader()->rd = true;
     pw1.getHeader()->qr = false;
     pw1.getHeader()->id = 0x42;
-    opt.source = Netmask("47.8.0.0/32");
+    opt.source = Netmask("10.0.34.159/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw1.commit();
 
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1);
+    auto hash1 = PacketCache::canHashPacket(spacket1, false);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
     pw2.getHeader()->rd = true;
     pw2.getHeader()->qr = false;
     pw2.getHeader()->id = 0x84;
-    opt.source = Netmask("18.43.1.0/32");
+    opt.source = Netmask("10.0.179.58/32");
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     /* no EDNSOpts::DNSSECOK !! */
@@ -108,11 +147,11 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     pw2.commit();
 
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2);
+    auto hash2 = PacketCache::canHashPacket(spacket2, false);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
     /* the hash is the same but we should _not_ match */
-    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname));
+    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
   }
 
   {
@@ -128,16 +167,12 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     EDNSCookiesOpt cookiesOpt;
     cookiesOpt.client = string("deadbeef");
     cookiesOpt.server = string("deadbeef");
-    cookiesOpt.server[4] = -42;
-    cookiesOpt.server[5] = -6;
-    cookiesOpt.server[6] = 1;
-    cookiesOpt.server[7] = 0;
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
     pw1.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw1.commit();
 
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1);
+    auto hash1 = PacketCache::canHashPacket(spacket1, false);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
@@ -148,21 +183,69 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheAuthCollision) {
     ednsOptions.clear();
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
     cookiesOpt.client = string("deadbeef");
-    cookiesOpt.server = string("deadbeef");
-    cookiesOpt.server[4] = 29;
-    cookiesOpt.server[5] = -79;
-    cookiesOpt.server[6] = 1;
-    cookiesOpt.server[7] = 0;
+    cookiesOpt.server = string("badc0fee");
     ednsOptions.push_back(std::make_pair(EDNSOptionCode::COOKIE, makeEDNSCookiesOptString(cookiesOpt)));
     pw2.addOpt(512, 0, EDNSOpts::DNSSECOK, ednsOptions);
     pw2.commit();
 
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2);
+    auto hash2 = PacketCache::canHashPacket(spacket2, false);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
     /* the hash is the same but we should _not_ match */
-    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname));
+    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, noOptionsToSkip));
+    /* but it does match if we skip cookies, though */
+    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
+
+#if 0
+    {
+      /* to be able to compute a new collision if the packet cache hashing code is updated */
+    std::map<uint32_t, Netmask> colMap;
+    size_t collisions = 0;
+    size_t total = 0;
+
+    for (size_t idxA = 0; idxA < 256; idxA++) {
+      for (size_t idxB = 0; idxB < 256; idxB++) {
+        for (size_t idxC = 0; idxC < 256; idxC++) {
+          vector<uint8_t> secondQuery;
+          DNSPacketWriter pwFQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+          pwFQ.getHeader()->rd = 1;
+          pwFQ.getHeader()->qr = false;
+          pwFQ.getHeader()->id = 0x42;
+          opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+          ednsOptions.clear();
+          ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+          pwFQ.addOpt(512, 0, 32768, ednsOptions);
+          pwFQ.commit();
+          auto secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), false);
+          colMap.insert(std::make_pair(secondKey, opt.source));
+
+          secondQuery.clear();
+          DNSPacketWriter pwSQ(secondQuery, qname, QType::AAAA, QClass::IN, 0);
+          pwSQ.getHeader()->rd = 1;
+          pwSQ.getHeader()->qr = false;
+          pwSQ.getHeader()->id = 0x42;
+          opt.source = Netmask("10." + std::to_string(idxA) + "." + std::to_string(idxB) + "." + std::to_string(idxC) + "/32");
+          ednsOptions.clear();
+          ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
+          pwSQ.addOpt(512, 0, 0, ednsOptions);
+          pwSQ.commit();
+          secondKey = PacketCache::canHashPacket(std::string(reinterpret_cast<const char *>(secondQuery.data()), secondQuery.size()), false);
+
+          total++;
+          if (colMap.count(secondKey)) {
+            collisions++;
+            cerr<<"Collision between "<<colMap[secondKey].toString()<<" and "<<opt.source.toString()<<" for key "<<secondKey<<endl;
+            goto done2;
+          }
+        }
+      }
+    }
+  done2:
+    cerr<<"collisions: "<<collisions<<endl;
+    cerr<<"total: "<<total<<endl;
+  }
+#endif
   }
 }
 
@@ -172,8 +255,6 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecSimple) {
   uint16_t qtype = QType::AAAA;
   EDNSSubnetOpts opt;
   DNSPacketWriter::optvect_t ednsOptions;
-  uint16_t ecsBegin;
-  uint16_t ecsEnd;
 
   {
     vector<uint8_t> packet;
@@ -191,10 +272,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecSimple) {
     *(ptr + 1) = 255;
     /* truncate the end of the OPT header to try to trigger an out of bounds read */
     spacket1.resize(spacket1.size() - 6);
-    PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
-    /* no ECS */
-    BOOST_CHECK_EQUAL(ecsBegin, 0);
-    BOOST_CHECK_EQUAL(ecsEnd, 0);
+    BOOST_CHECK_NO_THROW(PacketCache::canHashPacket(spacket1, true));
   }
 }
 
@@ -205,8 +283,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
   uint16_t qtype = QType::AAAA;
   EDNSSubnetOpts opt;
   DNSPacketWriter::optvect_t ednsOptions;
-  uint16_t ecsBegin;
-  uint16_t ecsEnd;
+  static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
 
   {
     /* same query, different IDs */
@@ -216,10 +293,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     pw1.getHeader()->qr = false;
     pw1.getHeader()->id = 0x42;
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
-    /* no ECS */
-    BOOST_CHECK_EQUAL(ecsBegin, 0);
-    BOOST_CHECK_EQUAL(ecsEnd, 0);
+    auto hash1 = PacketCache::canHashPacket(spacket1, true);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
@@ -227,13 +301,10 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     pw2.getHeader()->qr = false;
     pw2.getHeader()->id = 0x84;
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2, &ecsBegin, &ecsEnd);
-    /* no ECS */
-    BOOST_CHECK_EQUAL(ecsBegin, 0);
-    BOOST_CHECK_EQUAL(ecsEnd, 0);
+    auto hash2 = PacketCache::canHashPacket(spacket2, true);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
-    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, ecsBegin, ecsEnd));
+    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
   }
 
   {
@@ -250,10 +321,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     pw1.commit();
 
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
-    /* ECS value */
-    BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
-    BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+    auto hash1 = PacketCache::canHashPacket(spacket1, true);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
@@ -267,14 +335,11 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     pw2.commit();
 
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2, &ecsBegin, &ecsEnd);
-    /* ECS value */
-    BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
-    BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+    auto hash2 = PacketCache::canHashPacket(spacket2, true);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
     /* the hash is the same and we don't hash the ECS so we should match */
-    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, ecsBegin, ecsEnd));
+    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
   }
 
   {
@@ -299,10 +364,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     pw1.commit();
 
     string spacket1((const char*)&packet[0], packet.size());
-    auto hash1 = PacketCache::canHashPacket(spacket1, &ecsBegin, &ecsEnd);
-    /* ECS value */
-    BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
-    BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+    auto hash1 = PacketCache::canHashPacket(spacket1, true);
 
     packet.clear();
     DNSPacketWriter pw2(packet, qname, qtype);
@@ -323,14 +385,15 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheRecCollision) {
     pw2.commit();
 
     string spacket2((const char*)&packet[0], packet.size());
-    auto hash2 = PacketCache::canHashPacket(spacket2, &ecsBegin, &ecsEnd);
-    /* ECS value */
-    BOOST_CHECK_EQUAL(ecsBegin, sizeof(dnsheader) + qname.wirelength() + ( 2 * sizeof(uint16_t)) /* qtype */ + (2 * sizeof(uint16_t)) /* qclass */ + /* OPT root label */ 1 + sizeof(uint32_t) /* TTL */ + DNS_RDLENGTH_SIZE);
-    BOOST_CHECK_EQUAL(ecsEnd, ecsBegin + EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + 2 /* family */ + 1 /* scope length */ + 1 /* source length */ + 4 /* IPv4 */);
+    auto hash2 = PacketCache::canHashPacket(spacket2, true);
 
     BOOST_CHECK_EQUAL(hash1, hash2);
     /* the hash is the same but we should _not_ match, even though we skip the ECS part, because the cookies are different */
-    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, ecsBegin, ecsEnd));
+    static const std::unordered_set<uint16_t> skipECSOnly{ EDNSOptionCode::ECS };
+    BOOST_CHECK(!PacketCache::queryMatches(spacket1, spacket2, qname, skipECSOnly));
+
+    /* we do match if we skip the cookie as well */
+    BOOST_CHECK(PacketCache::queryMatches(spacket1, spacket2, qname, optionsToSkip));
   }
 }
 
diff --git a/pdns/test-proxy_protocol_cc.cc b/pdns/test-proxy_protocol_cc.cc
new file mode 100644 (file)
index 0000000..e61f978
--- /dev/null
@@ -0,0 +1,227 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "iputils.hh"
+#include "proxy-protocol.hh"
+
+using namespace boost;
+using std::string;
+
+
+BOOST_AUTO_TEST_SUITE(test_proxy_protocol_cc)
+
+#define BINARY(s) (std::string(s, sizeof(s) - 1))
+
+#define PROXYMAGIC "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
+#define PROXYMAGICLEN sizeof(PROXYMAGIC)-1
+
+static string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
+
+BOOST_AUTO_TEST_CASE(test_roundtrip) {
+  std::vector<ProxyProtocolValue> values;
+  string proxyheader;
+
+  bool ptcp = true;
+  ComboAddress src("65.66.67.68:18762");  // 18762 = 0x494a = "IJ"
+  ComboAddress dest("69.70.71.72:19276"); // 19276 = 0x4b4c = "KL"
+  proxyheader = makeProxyHeader(ptcp, src, dest, values);
+
+  BOOST_CHECK_EQUAL(proxyheader, BINARY(
+    PROXYMAGIC
+    "\x21"          // version | command
+    "\x11"          // ipv4=0x10 | TCP=0x1
+    "\x00\x0c"      // 4 bytes IPv4 * 2 + 2 port numbers = 8 + 2 * 2 =12 = 0xc
+    "ABCD"          // 65.66.67.68
+    "EFGH"          // 69.70.71.72
+    "IJ"            // src port
+    "KL"            // dst port
+    ));
+
+  bool proxy;
+  bool ptcp2;
+  ComboAddress src2, dest2;
+
+  BOOST_CHECK_EQUAL(parseProxyHeader(proxyheader, proxy, src2, dest2, ptcp2, values), 28);
+
+  BOOST_CHECK_EQUAL(proxy, true);
+  BOOST_CHECK_EQUAL(ptcp2, true);
+  BOOST_CHECK(src2 == src);
+  BOOST_CHECK(dest2 == dest);
+}
+
+BOOST_AUTO_TEST_CASE(test_local_proxy_header) {
+  auto payload = makeLocalProxyHeader();
+
+  BOOST_CHECK_EQUAL(payload, BINARY(
+    PROXYMAGIC
+    "\x20"          // version | command
+    "\x00"          // protocol family and address are set to 0
+    "\x00\x00"      // no content
+    ));
+
+  bool proxy;
+  bool tcp = false;
+  ComboAddress src, dest;
+  std::vector<ProxyProtocolValue> values;
+
+  BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src, dest, tcp, values), 16);
+
+  BOOST_CHECK_EQUAL(proxy, false);
+  BOOST_CHECK_EQUAL(tcp, false);
+  BOOST_CHECK_EQUAL(values.size(), 0U);
+}
+
+BOOST_AUTO_TEST_CASE(test_tlv_values_content_len_signedness) {
+  std::string largeValue;
+  /* this value will make the content length parsing fail in case of signedness mistake */
+  largeValue.resize(65128, 'A');
+  const std::vector<ProxyProtocolValue> values = { { "foo", 0 }, { largeValue, 255 }};
+
+  const bool tcp = false;
+  const ComboAddress src("[2001:db8::1]:0");
+  const ComboAddress dest("[::1]:65535");
+  const auto payload = makeProxyHeader(tcp, src, dest, values);
+
+  bool proxy;
+  bool tcp2;
+  ComboAddress src2;
+  ComboAddress dest2;
+  std::vector<ProxyProtocolValue> parsedValues;
+
+  BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src2, dest2, tcp2, parsedValues), 16 + 36 + 6 + 65131);
+  BOOST_CHECK_EQUAL(proxy, true);
+  BOOST_CHECK_EQUAL(tcp2, tcp);
+  BOOST_CHECK(src2 == src);
+  BOOST_CHECK(dest2 == dest);
+  BOOST_REQUIRE_EQUAL(parsedValues.size(), values.size());
+  for (size_t idx = 0; idx < values.size(); idx++) {
+    BOOST_CHECK_EQUAL(parsedValues.at(idx).type, values.at(idx).type);
+    BOOST_CHECK_EQUAL(parsedValues.at(idx).content, values.at(idx).content);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_tlv_values_length_signedness) {
+  std::string largeValue;
+  /* this value will make the TLV length parsing fail in case of signedness mistake */
+  largeValue.resize(65000, 'A');
+  const std::vector<ProxyProtocolValue> values = { { "foo", 0 }, { largeValue, 255 }};
+
+  const bool tcp = false;
+  const ComboAddress src("[2001:db8::1]:0");
+  const ComboAddress dest("[::1]:65535");
+  const auto payload = makeProxyHeader(tcp, src, dest, values);
+
+  bool proxy;
+  bool tcp2;
+  ComboAddress src2;
+  ComboAddress dest2;
+  std::vector<ProxyProtocolValue> parsedValues;
+
+  BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src2, dest2, tcp2, parsedValues), 16 + 36 + 6 + 65003);
+  BOOST_CHECK_EQUAL(proxy, true);
+  BOOST_CHECK_EQUAL(tcp2, tcp);
+  BOOST_CHECK(src2 == src);
+  BOOST_CHECK(dest2 == dest);
+  BOOST_REQUIRE_EQUAL(parsedValues.size(), values.size());
+  for (size_t idx = 0; idx < values.size(); idx++) {
+    BOOST_CHECK_EQUAL(parsedValues.at(idx).type, values.at(idx).type);
+    BOOST_CHECK_EQUAL(parsedValues.at(idx).content, values.at(idx).content);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_parsing_invalid_headers) {
+  const std::vector<ProxyProtocolValue> noValues;
+
+  const bool tcp = false;
+  const ComboAddress src("[2001:db8::1]:0");
+  const ComboAddress dest("[::1]:65535");
+  const auto payload = makeProxyHeader(tcp, src, dest, noValues);
+
+  bool proxy;
+  bool tcp2;
+  ComboAddress src2;
+  ComboAddress dest2;
+  std::vector<ProxyProtocolValue> values;
+
+  {
+    /* just checking that everything works */
+    BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src2, dest2, tcp2, values), 52);
+    BOOST_CHECK_EQUAL(proxy, true);
+    BOOST_CHECK_EQUAL(tcp2, tcp);
+    BOOST_CHECK(src2 == src);
+    BOOST_CHECK(dest2 == dest);
+    BOOST_CHECK_EQUAL(values.size(), 0U);
+  }
+
+  {
+    /* too short (not even full header) */
+    std::string truncated = payload;
+    truncated.resize(15);
+    BOOST_CHECK_EQUAL(parseProxyHeader(truncated, proxy, src2, dest2, tcp2, values), -1);
+  }
+
+  {
+    /* too short (missing address part) */
+    std::string truncated = payload;
+    truncated.resize(/* full header */ 16 + /* two IPv6s + port */ 36 - /* truncation */ 1);
+    BOOST_CHECK_EQUAL(parseProxyHeader(truncated, proxy, src2, dest2, tcp2, values), -1);
+  }
+
+  {
+    /* too short (missing TLV) */
+    values = { { "foo", 0 }, { "bar", 255 }} ;
+    const auto payloadWithValues = makeProxyHeader(tcp, src, dest, values);
+
+    std::string truncated = payloadWithValues;
+    truncated.resize(/* full header */ 16 + /* two IPv6s + port */ 36 + /* TLV 1 */ 6 + /* TLV 2 */ 6 - /* truncation */ 2);
+    BOOST_CHECK_EQUAL(parseProxyHeader(truncated, proxy, src2, dest2, tcp2, values), -2);
+  }
+
+  {
+    /* invalid magic */
+    std::string invalid = payload;
+    invalid.at(4) = 42;
+    BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+  }
+
+  {
+    /* invalid version */
+    std::string invalid = payload;
+    invalid.at(12) = 0x10 | 0x01;
+    BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+  }
+
+  {
+    /* invalid command */
+    std::string invalid = payload;
+    invalid.at(12) = 0x20 | 0x02;
+    BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+  }
+
+  {
+    /* invalid family */
+    std::string invalid = payload;
+    invalid.at(13) = (0x04 << 4) | 0x01 /* STREAM */;
+    BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+  }
+
+  {
+    /* invalid address */
+    std::string invalid = payload;
+    invalid.at(13) = (0x02 /* AF_INET */ << 4) | 0x03;
+    BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+  }
+
+  {
+    /* TLV advertised len gets out of bounds */
+    values = { { "foo", 0 }, { "bar", 255 }} ;
+    const auto payloadWithValues = makeProxyHeader(tcp, src, dest, values);
+    std::string invalid = payloadWithValues;
+    /* full header (16) + two IPv6s + port (36) + TLV (6) TLV 2 (6) */
+    invalid.at(59) += 1;
+    BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 8555f6c59117fb68fdd656855405a8ebcfbc281d..fce6fcc823a91d78e50ed440bf588bfca7deb282 100644 (file)
@@ -45,16 +45,16 @@ BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple) {
   pw.commit();
   string rpacket((const char*)&packet[0], packet.size());
 
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
   rpc.doPruneTo(0);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
   rpc.doWipePacketCache(qname);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
 
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
   uint32_t qhash2 = 0;
   bool found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash2);
@@ -140,11 +140,11 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK(r1packet != r2packet);
 
   /* inserting a response for tag1 */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
 
   /* inserting a different response for tag2, should not override the first one */
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 2U);
 
   /* remove all responses from the cache */
@@ -152,10 +152,10 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
 
   /* reinsert both */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
 
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 2U);
 
   /* remove the responses by qname, should remove both */
@@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
 
   /* insert the response for tag1 */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
 
   /* we can retrieve it */
@@ -182,7 +182,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK_EQUAL(temphash, qhash);
 
   /* adding a response for the second tag */
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, vState::Indeterminate, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 2U);
 
   /* We still get the correct response for the first tag */
index da21db062a16d40576322225309f15da3f28f1b7..135dbf665a7119bb791ee0c83fbcacda6bfd00eb 100644 (file)
@@ -161,7 +161,7 @@ static void checkRR(const signerParams& signer)
 
 BOOST_AUTO_TEST_CASE(test_generic_signers)
 {
-  for (const auto signer : signers) {
+  for (const auto& signer : signers) {
     DNSKEYRecordContent drc;
     auto dcke = DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap);
 
index a33978540a78b267254a690bd2573c5e142b32de..d551ca6546172327bee860374d36d51f6aec66a2 100644 (file)
@@ -9,26 +9,23 @@
 
 #include <boost/tuple/tuple.hpp>
 #include <stdint.h>
+#include <thread>
 #include "misc.hh"
 #include "dns.hh"
 #include "statbag.hh"
 
 using std::string;
 
-static void *threadMangler(void* a)
+static void threadMangler(AtomicCounter* ac)
 {
-  AtomicCounter* ac=(AtomicCounter*)a;
   for(unsigned int n=0; n < 1000000; ++n)
     (*ac)++;
-  return 0;
 }
 
-static void *threadMangler2(void* a)
+static void threadMangler2(StatBag* S)
 {
-  StatBag* S = (StatBag*)a;
   for(unsigned int n=0; n < 1000000; ++n)
     S->inc("c");
-  return 0;
 }
 
 
@@ -56,22 +53,28 @@ BOOST_AUTO_TEST_CASE(test_StatBagBasic) {
   BOOST_CHECK_EQUAL(s.read("a"), n+1);
 
   AtomicCounter* acc = s.getPointer("c");
-  pthread_t tid[4];
-  for(int i=0; i < 4; ++i) 
-    pthread_create(&tid[i], 0, threadMangler, (void*)acc);
-  void* res;
-  for(int i=0; i < 4 ; ++i)
-    pthread_join(tid[i], &res);
+  std::vector<std::thread> manglers;
+  for (int i=0; i < 4; ++i) {
+    manglers.push_back(std::thread(threadMangler, acc));
+  }
+
+  for (auto& t : manglers) {
+    t.join();
+  }
+  manglers.clear();
 
   BOOST_CHECK_EQUAL(s.read("c"), 4000000U);
  
   s.set("c", 0);
 
-  for(int i=0; i < 4; ++i) 
-    pthread_create(&tid[i], 0, threadMangler2, (void*)&s);
+  for (int i=0; i < 4; ++i) {
+    manglers.push_back(std::thread(threadMangler2, &s));
+  }
 
-  for(int i=0; i < 4 ; ++i)
-    pthread_join(tid[i], &res);
+  for (auto& t : manglers) {
+    t.join();
+  }
+  manglers.clear();
 
   BOOST_CHECK_EQUAL(s.read("c"), 4000000U);
 
diff --git a/pdns/test-ueberbackend_cc.cc b/pdns/test-ueberbackend_cc.cc
new file mode 100644 (file)
index 0000000..2ca4a56
--- /dev/null
@@ -0,0 +1,1126 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unordered_map>
+
+#include <boost/test/unit_test.hpp>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+
+#include "arguments.hh"
+#include "auth-querycache.hh"
+#include "ueberbackend.hh"
+
+class SimpleBackend : public DNSBackend
+{
+public:
+  struct SimpleDNSRecord
+  {
+    SimpleDNSRecord(const DNSName& name, uint16_t type, const std::string& content, uint32_t ttl): d_content(content), d_name(name), d_ttl(ttl), d_type(type)
+    {
+    }
+
+    std::string d_content;
+    DNSName d_name;
+    uint32_t d_ttl;
+    uint16_t d_type;
+  };
+
+  struct OrderedNameTypeTag;
+
+  typedef multi_index_container<
+    SimpleDNSRecord,
+    indexed_by <
+      ordered_non_unique<tag<OrderedNameTypeTag>,
+                         composite_key<
+                           SimpleDNSRecord,
+                           member<SimpleDNSRecord, DNSName, &SimpleDNSRecord::d_name>,
+                           member<SimpleDNSRecord, uint16_t, &SimpleDNSRecord::d_type>
+                           >,
+                         composite_key_compare<CanonDNSNameCompare, std::less<uint16_t> >
+                         >
+      >
+    > RecordStorage;
+
+  struct SimpleDNSZone
+  {
+    SimpleDNSZone(const DNSName& name, uint64_t id): d_records(std::make_shared<RecordStorage>()), d_name(name), d_id(id)
+    {
+    }
+    std::shared_ptr<RecordStorage> d_records;
+    DNSName d_name;
+    uint64_t d_id;
+  };
+
+  struct HashedNameTag {};
+  struct IDTag {};
+
+  typedef multi_index_container<
+    SimpleDNSZone,
+    indexed_by <
+      ordered_unique<tag<IDTag>, member<SimpleDNSZone, uint64_t, &SimpleDNSZone::d_id> >,
+      hashed_unique<tag<HashedNameTag>, member<SimpleDNSZone, DNSName, &SimpleDNSZone::d_name> >
+      >
+    > ZoneStorage;
+
+  struct SimpleMetaData
+  {
+    SimpleMetaData(const DNSName& name, const std::string& kind, const std::vector<std::string>& values): d_name(name), d_kind(kind), d_values(values)
+    {
+    }
+
+    DNSName d_name;
+    std::string d_kind;
+    std::vector<std::string> d_values;
+  };
+
+  struct OrderedNameKindTag {};
+
+  typedef multi_index_container<
+    SimpleMetaData,
+    indexed_by <
+      ordered_unique<tag<OrderedNameKindTag>,
+                     composite_key<
+                       SimpleMetaData,
+                       member<SimpleMetaData, DNSName, &SimpleMetaData::d_name>,
+                       member<SimpleMetaData, std::string, &SimpleMetaData::d_kind>
+                       >,
+                     composite_key_compare<CanonDNSNameCompare, std::less<std::string> >
+                     >
+      >
+    > MetaDataStorage;
+
+  // Initialize our backend ID from the suffix, skipping the '-' that DNSBackend adds there
+  SimpleBackend(const std::string& suffix): d_suffix(suffix), d_backendId(pdns_stou(suffix.substr(1)))
+  {
+  }
+
+  bool findZone(const DNSName& qdomain, int zoneId, std::shared_ptr<RecordStorage>& records, uint64_t& currentZoneId) const
+  {
+    currentZoneId = -1;
+    records.reset();
+
+    if (zoneId != -1) {
+      const auto& idx = boost::multi_index::get<IDTag>(s_zones.at(d_backendId));
+      auto it = idx.find(zoneId);
+      if (it == idx.end()) {
+        return false;
+      }
+      records = it->d_records;
+      currentZoneId = it->d_id;
+    }
+    else {
+      const auto& idx = boost::multi_index::get<HashedNameTag>(s_zones.at(d_backendId));
+      auto it = idx.find(qdomain);
+      if (it == idx.end()) {
+        return false;
+      }
+      records = it->d_records;
+      currentZoneId = it->d_id;
+    }
+
+    return true;
+  }
+
+  void lookup(const QType& qtype, const DNSName& qdomain, int zoneId = -1, DNSPacket *pkt_p = nullptr) override
+  {
+    d_currentScopeMask = 0;
+    findZone(qdomain, zoneId, d_records, d_currentZone);
+
+    if (d_records) {
+      if (qdomain == DNSName("geo.powerdns.com.") && pkt_p != nullptr && pkt_p->getRealRemote() == Netmask("192.0.2.1")) {
+        d_currentScopeMask = 32;
+      }
+
+      auto& idx = d_records->get<OrderedNameTypeTag>();
+      if (qtype == QType::ANY) {
+        auto range = idx.equal_range(qdomain);
+        d_iter = range.first;
+        d_end = range.second;
+      }
+      else {
+        auto range = idx.equal_range(boost::make_tuple(qdomain, qtype.getCode()));
+        d_iter = range.first;
+        d_end = range.second;
+      }
+    }
+  }
+
+  bool get(DNSResourceRecord& drr) override
+  {
+    if (!d_records) {
+      return false;
+    }
+
+    if (d_iter == d_end) {
+      return false;
+    }
+
+    drr.qname = d_iter->d_name;
+    drr.domain_id = d_currentZone;
+    drr.content = d_iter->d_content;
+    drr.qtype = d_iter->d_type;
+    drr.ttl = d_iter->d_ttl;
+
+    // drr.auth = d_iter->auth; might bring pain at some point, let's not cross that bridge until then
+    drr.auth = true;
+    drr.scopeMask = d_currentScopeMask;
+
+    ++d_iter;
+    return true;
+  }
+
+  bool list(const DNSName& target, int zoneId, bool include_disabled = false) override
+  {
+    findZone(target, zoneId, d_records, d_currentZone);
+
+    if (d_records) {
+      d_iter = d_records->begin();
+      d_end = d_records->end();
+      return true;
+    }
+
+    return false;
+  }
+
+  bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
+  {
+    const auto& idx = boost::multi_index::get<OrderedNameKindTag>(s_metadata.at(d_backendId));
+    auto it = idx.find(boost::make_tuple(name, kind));
+    if (it == idx.end()) {
+      /* funnily enough, we are expected to return true even though we might not know that zone */
+      return true;
+    }
+
+    meta = it->d_values;
+    return true;
+  }
+
+  bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override
+  {
+    auto& idx = boost::multi_index::get<OrderedNameKindTag>(s_metadata.at(d_backendId));
+    auto it = idx.find(boost::make_tuple(name, kind));
+    if (it == idx.end()) {
+      s_metadata.at(d_backendId).insert(SimpleMetaData(name, kind, meta));
+      return true;
+    }
+    idx.replace(it, SimpleMetaData(name, kind, meta));
+    return true;
+  }
+
+  /* this is not thread-safe */
+  static std::unordered_map<uint64_t, ZoneStorage> s_zones;
+  static std::unordered_map<uint64_t, MetaDataStorage> s_metadata;
+
+protected:
+  std::string d_suffix;
+  std::shared_ptr<RecordStorage> d_records{nullptr};
+  RecordStorage::index<OrderedNameTypeTag>::type::const_iterator d_iter;
+  RecordStorage::index<OrderedNameTypeTag>::type::const_iterator d_end;
+  const uint64_t d_backendId;
+  uint64_t d_currentZone{0};
+  uint8_t d_currentScopeMask{0};
+};
+
+class SimpleBackendBestAuth : public SimpleBackend
+{
+public:
+  SimpleBackendBestAuth(const std::string& suffix): SimpleBackend(suffix)
+  {
+  }
+
+  bool getAuth(const DNSName& target, SOAData* sd) override
+  {
+    static const DNSName best("d.0.1.0.0.2.ip6.arpa.");
+
+    ++d_authLookupCount;
+
+    if (target.isPartOf(best)) {
+      /* return the best SOA right away */
+      std::shared_ptr<RecordStorage> records;
+      uint64_t zoneId;
+      if (!findZone(best, -1, records, zoneId)) {
+        return false;
+      }
+
+      auto& idx = records->get<OrderedNameTypeTag>();
+      auto range = idx.equal_range(boost::make_tuple(best, QType::SOA));
+      if (range.first == range.second) {
+        return false;
+      }
+
+      fillSOAData(range.first->d_content, *sd);
+      sd->ttl = range.first->d_ttl;
+      sd->qname = best;
+      sd->domain_id = zoneId;
+      return true;
+    }
+
+    return getSOA(target, *sd);
+  }
+
+  size_t d_authLookupCount{0};
+};
+
+class SimpleBackendNoMeta : public SimpleBackend
+{
+public:
+  SimpleBackendNoMeta(const std::string& suffix): SimpleBackend(suffix)
+  {
+  }
+
+  bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override
+  {
+    return false;
+  }
+
+  bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override
+  {
+    return false;
+  }
+};
+
+std::unordered_map<uint64_t, SimpleBackend::ZoneStorage> SimpleBackend::s_zones;
+std::unordered_map<uint64_t, SimpleBackend::MetaDataStorage> SimpleBackend::s_metadata;
+
+class SimpleBackendFactory : public BackendFactory
+{
+public:
+  SimpleBackendFactory(): BackendFactory("SimpleBackend")
+  {
+  }
+
+  DNSBackend *make(const string& suffix="") override
+  {
+    return new SimpleBackend(suffix);
+  }
+};
+
+class SimpleBackendBestAuthFactory : public BackendFactory
+{
+public:
+  SimpleBackendBestAuthFactory(): BackendFactory("SimpleBackendBestAuth")
+  {
+  }
+
+  DNSBackend *make(const string& suffix="") override
+  {
+    return new SimpleBackendBestAuth(suffix);
+  }
+};
+
+class SimpleBackendNoMetaFactory : public BackendFactory
+{
+public:
+  SimpleBackendNoMetaFactory(): BackendFactory("SimpleBackendNoMeta")
+  {
+  }
+
+  DNSBackend *make(const string& suffix="") override
+  {
+    return new SimpleBackendNoMeta(suffix);
+  }
+};
+
+struct UeberBackendSetupArgFixture {
+  UeberBackendSetupArgFixture() {
+    extern AuthQueryCache QC;
+    ::arg().set("query-cache-ttl")="0";
+    ::arg().set("negquery-cache-ttl")="0";
+    QC.cleanup();
+    BackendMakers().clear();
+    SimpleBackend::s_zones.clear();
+    SimpleBackend::s_metadata.clear();
+  };
+};
+
+static void testWithoutThenWithCache(std::function<void(UeberBackend& ub)> func)
+{
+  extern AuthQueryCache QC;
+
+  {
+    /* disable the cache */
+    ::arg().set("query-cache-ttl")="0";
+    ::arg().set("negquery-cache-ttl")="0";
+    QC.cleanup();
+
+    UeberBackend ub;
+    func(ub);
+  }
+
+  {
+    /* enable the cache */
+    ::arg().set("query-cache-ttl")="20";
+    ::arg().set("negquery-cache-ttl")="60";
+    QC.cleanup();
+
+    UeberBackend ub;
+    /* a first time to fill the cache */
+    func(ub);
+    /* a second time to make sure every call has been tried with the cache filled */
+    func(ub);
+  }
+}
+
+BOOST_FIXTURE_TEST_SUITE(test_ueberbackend_cc, UeberBackendSetupArgFixture)
+
+static std::vector<DNSZoneRecord> getRecords(UeberBackend& ub, const DNSName& name, uint16_t qtype, int zoneId, const DNSPacket* pkt)
+{
+  std::vector<DNSZoneRecord> result;
+
+  ub.lookup(QType(qtype), name, zoneId, const_cast<DNSPacket*>(pkt));
+
+  DNSZoneRecord dzr;
+  while (ub.get(dzr))
+  {
+    result.push_back(std::move(dzr));
+  }
+
+  return result;
+}
+
+static void checkRecordExists(const std::vector<DNSZoneRecord>& records, const DNSName& name, uint16_t type, int zoneId, uint8_t scopeMask, bool auth)
+{
+  BOOST_REQUIRE_GE(records.size(), 1);
+  for (const auto& record : records) {
+    if (record.domain_id == zoneId &&
+        record.dr.d_type == type &&
+        record.dr.d_name == name &&
+        record.auth == auth &&
+        record.scopeMask == scopeMask) {
+      return;
+    }
+  }
+  BOOST_CHECK_MESSAGE(false, "Record " + name.toString() + "/" + QType(type).getName() + " - " + std::to_string(zoneId) + " not found");
+}
+
+BOOST_AUTO_TEST_CASE(test_simple) {
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 65535, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_separate_zones) {
+  // one zone in backend 1, a second zone in backend 2
+  // no overlap
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.org."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::SOA, "ns1.powerdns.org. powerdns.org. 3 600 600 3600000 604800", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.org."), QType::AAAA, "2001:db8::42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::SOA, 2, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::SOA, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 2);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::SOA, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("www.powerdns.org."), QType::AAAA, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("www.powerdns.org."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::PTR, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("powerdns.org."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+
+      records = getRecords(ub, DNSName("not-powerdns.com."), QType::ANY, 65535, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_overlay) {
+  // one backend holds the SOA, NS and one A
+  // a second backend holds another A and AAAA
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.1", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.com."), 1);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      // /!\ only 3 records are returned since we don't allow spreading the same name over several backends
+      BOOST_REQUIRE_EQUAL(records.size(), 3);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      //checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      // /!\ the AAAA will be found on an exact search, but not on an ANY one
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      // /!\ only 3 records are returned since we don't allow spreading the same name over several backends
+      BOOST_REQUIRE_EQUAL(records.size(), 3);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      //checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      // /!\ the AAAA will be found on an exact search, but not on an ANY one
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test www - A with zone id set (only in the second backend)
+      auto records = getRecords(ub, DNSName("www.powerdns.com."), QType::A, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("www.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_overlay_name) {
+  // one backend holds the apex with SOA, NS and one A
+  // a second backend holds others names
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::A, "192.168.0.2", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.com."), 1);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::AAAA, "192.168.0.1", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 5);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 5);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::A, 1, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test AAAA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::AAAA, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 1, 0, true);
+    }
+
+    {
+      // test www - A with zone id set (only in the second backend)
+      auto records = getRecords(ub, DNSName("www.powerdns.com."), QType::A, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("www.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    {
+      // test NODATA with zone id set
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::PTR, 1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test ANY with wrong zone id
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, 2, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 0);
+    }
+
+    {
+      // test a DNS packet is correctly passed and that the corresponding scope is passed back
+      DNSPacket pkt(true);
+      ComboAddress remote("192.0.2.1");
+      pkt.setRemote(&remote);
+      auto records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 32, true);
+      // and that we don't get the same result for a different client
+      remote = ComboAddress("192.0.2.2");
+      pkt.setRemote(&remote);
+      records = getRecords(ub, DNSName("geo.powerdns.com."), QType::ANY, 1, &pkt);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("geo.powerdns.com."), QType::A, 1, 0, true);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_child_zone) {
+  // Backend 1 holds zone A "com" while backend 2 holds zone B "powerdns.com"
+  // Check that DS queries are correctly handled
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("com."), QType::SOA, "a.gtld-servers.net. nstld.verisign-grs.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::DS, "44030 8 3 7DD75AE1565051F9563CF8DF976AC99CDCA51E3463019C81BD2BB083 82F3854E", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("ns1.powerdns.com."), QType::A, "192.0.2.1", 3600));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.com."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("ns1.powerdns.com."), QType::A, "192.0.2.1", 3600));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // test SOA with unknown zone id == -1
+      auto records = getRecords(ub, DNSName("com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("com."), QType::SOA, 1, 0, true);
+
+      records = getRecords(ub, DNSName("powerdns.com."), QType::SOA, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 1);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 2, 0, true);
+    }
+
+    {
+      // test ANY with zone id == -1
+      auto records = getRecords(ub, DNSName("powerdns.com."), QType::ANY, -1, nullptr);
+      BOOST_REQUIRE_EQUAL(records.size(), 3);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::SOA, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::NS, 2, 0, true);
+      checkRecordExists(records, DNSName("powerdns.com."), QType::AAAA, 2, 0, true);
+    }
+
+    {
+      // test getAuth() for DS
+      SOAData sd;
+      BOOST_REQUIRE(ub.getAuth(DNSName("powerdns.com."), QType::DS, &sd));
+      BOOST_CHECK_EQUAL(sd.qname.toString(), "com.");
+      BOOST_CHECK_EQUAL(sd.domain_id, 1);
+    }
+
+    {
+      // test getAuth() for A
+      SOAData sd;
+      BOOST_REQUIRE(ub.getAuth(DNSName("powerdns.com."), QType::A, &sd));
+      BOOST_CHECK_EQUAL(sd.qname.toString(), "powerdns.com.");
+      BOOST_CHECK_EQUAL(sd.domain_id, 2);
+    }
+
+    };
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_best_soa) {
+  // several backends, one returns the best SOA it has right away
+  // while the others do simple lookups
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("d.0.1.0.0.2.ip6.arpa."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("d.0.1.0.0.2.ip6.arpa."), QType::SOA, "ns.apnic.net. read-txt-record-of-zone-first-dns-admin.apnic.net. 3005126844 7200 1800 604800 3600", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."), QType::PTR, "a.reverse.", 3600));
+    SimpleBackend::s_zones[1].insert(zoneA);
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("0.1.0.0.2.ip6.arpa."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("0.1.0.0.2.ip6.arpa."), QType::SOA, "ns.apnic.net. read-txt-record-of-zone-first-dns-admin.apnic.net. 3005126844 7200 1800 604800 3600", 3600));
+    SimpleBackend::s_zones[2].insert(zoneB);
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().report(new SimpleBackendBestAuthFactory());
+    BackendMakers().launch("SimpleBackendBestAuth:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      auto sbba = dynamic_cast<SimpleBackendBestAuth*>(ub.backends.at(0));
+      BOOST_REQUIRE(sbba != nullptr);
+      sbba->d_authLookupCount = 0;
+
+      // test getAuth()
+      SOAData sd;
+      BOOST_REQUIRE(ub.getAuth(DNSName("2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."), QType::PTR, &sd));
+      BOOST_CHECK_EQUAL(sd.qname.toString(), "d.0.1.0.0.2.ip6.arpa.");
+      BOOST_CHECK_EQUAL(sd.domain_id, 1);
+      // check that only one auth lookup occurred to this backend
+      BOOST_CHECK_EQUAL(sbba->d_authLookupCount, 1);
+    }
+
+    };
+
+    testWithoutThenWithCache(testFunction);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_backends_metadata) {
+  // we have metadata stored in the first and second backend.
+  // We can read from the first backend but not from the second, since the first will return "true" even though it has nothing
+  // Updating will insert into the first backend, leaving the first one untouched
+
+  try {
+    SimpleBackend::SimpleDNSZone zoneA(DNSName("powerdns.com."), 1);
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::SOA, "ns1.powerdns.com. powerdns.com. 3 600 600 3600000 604800", 3600));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.com."), QType::AAAA, "2001:db8::1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.com."), QType::A, "192.168.0.1", 60));
+    zoneA.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.com."), QType::A, "192.168.0.42", 60));
+    SimpleBackend::s_zones[1].insert(zoneA);
+    SimpleBackend::s_metadata[1].insert(SimpleBackend::SimpleMetaData(DNSName("powerdns.com."), "test-data-a", { "value1", "value2"}));
+
+    SimpleBackend::SimpleDNSZone zoneB(DNSName("powerdns.org."), 2);
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::SOA, "ns1.powerdns.org. powerdns.org. 3 600 600 3600000 604800", 3600));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("www.powerdns.org."), QType::AAAA, "2001:db8::2", 60));
+    zoneB.d_records->insert(SimpleBackend::SimpleDNSRecord(DNSName("geo.powerdns.org."), QType::AAAA, "2001:db8::42", 60));
+    SimpleBackend::s_zones[2].insert(zoneB);
+    SimpleBackend::s_metadata[2].insert(SimpleBackend::SimpleMetaData(DNSName("powerdns.org."), "test-data-b", { "value1", "value2"}));
+
+    BackendMakers().report(new SimpleBackendFactory());
+    BackendMakers().launch("SimpleBackend:1, SimpleBackend:2");
+    UeberBackend::go();
+
+    auto testFunction = [](UeberBackend& ub) -> void {
+    {
+      // check the initial values
+      std::vector<std::string> values;
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.com."), "test-data-a", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 2);
+      BOOST_CHECK_EQUAL(values.at(0), "value1");
+      BOOST_CHECK_EQUAL(values.at(1), "value2");
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.com."), "test-data-b", values));
+      BOOST_CHECK_EQUAL(values.size(), 0);
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-a", values));
+      BOOST_CHECK_EQUAL(values.size(), 0);
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-b", values));
+      BOOST_CHECK_EQUAL(values.size(), 0);
+    }
+
+    {
+      // update the values
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.com."), "test-data-a", { "value3" }));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-a", { "value4" }));
+      BOOST_CHECK(ub.setDomainMetadata(DNSName("powerdns.org."), "test-data-b", { "value5" }));
+    }
+
+    // check the updated values
+    {
+      std::vector<std::string> values;
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.com."), "test-data-a", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 1);
+      BOOST_CHECK_EQUAL(values.at(0), "value3");
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-a", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 1);
+      BOOST_CHECK_EQUAL(values.at(0), "value4");
+      values.clear();
+      BOOST_CHECK(ub.getDomainMetadata(DNSName("powerdns.org."), "test-data-b", values));
+      BOOST_REQUIRE_EQUAL(values.size(), 1);
+      BOOST_CHECK_EQUAL(values.at(0), "value5");
+    }
+
+    {
+      // check that it has not been updated in the second backend
+      const auto& it = SimpleBackend::s_metadata[2].find(boost::make_tuple(DNSName("powerdns.org."), "test-data-b"));
+      BOOST_REQUIRE(it != SimpleBackend::s_metadata[2].end());
+      BOOST_REQUIRE_EQUAL(it->d_values.size(), 2);
+      BOOST_CHECK_EQUAL(it->d_values.at(0), "value1");
+      BOOST_CHECK_EQUAL(it->d_values.at(1), "value2");
+    }
+    };
+
+    UeberBackend ub;
+    testFunction(ub);
+  }
+  catch(const PDNSException& e) {
+    cerr<<e.reason<<endl;
+    throw;
+  }
+  catch(const std::exception& e) {
+    cerr<<e.what()<<endl;
+    throw;
+  }
+  catch(...) {
+    cerr<<"An unexpected error occurred.."<<endl;
+    throw;
+  }
+}
+
+
+BOOST_AUTO_TEST_SUITE_END();
index 0ea154593f0acec2b56d1baba40fbffd2121d981..7db0337ae21f382349cef12d30934fbd9fa5f434 100644 (file)
@@ -19,7 +19,7 @@ ArgvMap &arg()
 }
 
 
-bool init_unit_test() {
+static bool init_unit_test() {
   reportAllTypes();
   return true;
 }
@@ -27,5 +27,6 @@ bool init_unit_test() {
 // entry point:
 int main(int argc, char* argv[])
 {
+  S.d_allowRedeclare = true;
   return boost::unit_test::unit_test_main( &init_unit_test, argc, argv );
 }
index 7f7e5da313e35667d2f739223caa245e69b385f2..7e0c76e91ba4912c8550866cbede4730a25deea1 100644 (file)
@@ -7,7 +7,6 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   TKEYRecordContent tkey_in;
   std::shared_ptr<TKEYRecordContent> tkey_out(new TKEYRecordContent());
   DNSName name;
-  bool sign = false;
 
   if (!p.getTKEYRecord(&tkey_in, &name)) {
     g_log<<Logger::Error<<"TKEY request but no TKEY RR found"<<endl;
@@ -22,26 +21,9 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   tkey_out->d_inception = time((time_t*)NULL);
   tkey_out->d_expiration = tkey_out->d_inception+15;
 
-  GssContext ctx(name);
-
   if (tkey_in.d_mode == 3) { // establish context
     if (tkey_in.d_algo == DNSName("gss-tsig.")) {
-      std::vector<std::string> meta;
-      DNSName tmpName(name);
-      do {
-        if (B.getDomainMetadata(tmpName, "GSS-ACCEPTOR-PRINCIPAL", meta) && meta.size()>0) {
-          break;
-        }
-      } while(tmpName.chopOff());
-
-      if (meta.size()>0) {
-        ctx.setLocalPrincipal(meta[0]);
-      }
-      // try to get a context
-      if (!ctx.accept(tkey_in.d_key, tkey_out->d_key))
-        tkey_out->d_error = 19;
-      else
-        sign = true;
+      tkey_out->d_error = 19;
     } else {
       tkey_out->d_error = 21; // BADALGO
     }
@@ -53,10 +35,8 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
         r->setRcode(RCode::NotAuth);
       return;
     }
-    if (ctx.valid())
-      ctx.destroy();
-    else
-      tkey_out->d_error = 20; // BADNAME (because we have no support for anything here)
+
+    tkey_out->d_error = 20; // BADNAME (because we have no support for anything here)
   } else {
     if (p.d_havetsig == false && tkey_in.d_mode != 2) { // unauthenticated
       if (p.d.opcode == Opcode::Update)
@@ -80,20 +60,5 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   zrr.dr.d_content = tkey_out;
   zrr.dr.d_place = DNSResourceRecord::ANSWER;
   r->addRecord(std::move(zrr));
-
-  if (sign)
-  {
-    TSIGRecordContent trc;
-    trc.d_algoName = DNSName("gss-tsig");
-    trc.d_time = tkey_out->d_inception;
-    trc.d_fudge = 300;
-    trc.d_mac = "";
-    trc.d_origID = p.d.id;
-    trc.d_eRcode = 0;
-    trc.d_otherData = "";
-    // this should cause it to lookup name context
-    r->setTSIGDetails(trc, name, name.toStringNoDot(), "", false);
-  }
-
   r->commitD();
 }
index c453f0e3b94ab66f0cf5ed1fd666b17a6054adb4..b2e93c30a67545770d90c56e9bc4cda45aac980e 100644 (file)
@@ -145,7 +145,7 @@ try
       for(const auto& sig : csp.second.signatures) {
        cerr<<"got rrsig "<<sig->d_signer<<"/"<<sig->d_tag<<endl;
        vState state = getKeysFor(tro, sig->d_signer, keys);
-       cerr<<"! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
+       cerr<<"! state = "<<state<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
         // dsmap.insert(make_pair(dsrc.d_tag, dsrc));
       }
     }
@@ -155,7 +155,7 @@ try
   else {
     cerr<<"no sigs, hoping for Insecure"<<endl;
     vState state = getKeysFor(tro, qname, keys);
-    cerr<<"! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
+    cerr<<"! state = "<<state<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
   }
   cerr<<"! validated "<<validrrsets.size()<<" RRsets out of "<<cspmap.size()<<endl;
 
index 3a705096cd5369949b86a083677d973daccbf5f6..e63e29ebc5ecebbf617d1ce73f8f01acba19e58f 100644 (file)
 #include "digests.hh"
 #include "base64.hh"
 #include "dnssecinfra.hh"
-#include "resolver.hh"
+#include "axfr-retriever.hh"
 #include "arguments.hh"
 #include "dns_random.hh"
+#include "query-local-address.hh"
 
 StatBag S;
 
@@ -26,8 +27,7 @@ ArgvMap& arg()
 int main(int argc, char** argv)
 try
 {
-  ::arg().set("query-local-address","Source IP address for sending queries")="0.0.0.0";
-  ::arg().set("query-local-address6","Source IPv6 address for sending queries")="::";
+  pdns::parseQueryLocalAddress(":: 0.0.0.0");
 
   reportAllTypes();
 
index 7e75731993ef27ceaf938b96e1e91409ce4764dc..40f49a9119c4216d9262f601f30899d630313825 100644 (file)
@@ -25,6 +25,7 @@
 #include "dns_random.hh"
 #include "misc.hh"
 #include "pdnsexception.hh"
+#include "tsigutils.hh"
 #include <string>
 
 /*
index de0471b73dde899b853c1ac6a9e4bdbda3ff7e78..c379c841afa6b724981b964747e33be0c75ffecb 100644 (file)
@@ -1,7 +1,6 @@
 
 #include "tsigverifier.hh"
 #include "dnssecinfra.hh"
-#include "gss_context.hh"
 
 bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
 {
index 5fd0a3936e08b3b95d9d11c44f4011e76766265a..38717e0f3402a42a0f6566fa672e5ed328daf80a 100644 (file)
 extern StatBag S;
 
 vector<UeberBackend *>UeberBackend::instances;
-pthread_mutex_t UeberBackend::instances_lock=PTHREAD_MUTEX_INITIALIZER;
+std::mutex UeberBackend::instances_lock;
 
 // initially we are blocked
 bool UeberBackend::d_go=false;
-pthread_mutex_t  UeberBackend::d_mut = PTHREAD_MUTEX_INITIALIZER;
-pthread_cond_t UeberBackend::d_cond = PTHREAD_COND_INITIALIZER;
+std::mutex UeberBackend::d_mut;
+std::condition_variable UeberBackend::d_cond;
 
 //! Loads a module and reports it to all UeberBackend threads
 bool UeberBackend::loadmodule(const string &name)
@@ -94,10 +94,11 @@ bool UeberBackend::loadModules(const vector<string>& modules, const string& path
 
 void UeberBackend::go(void)
 {
-  pthread_mutex_lock(&d_mut);
-  d_go=true;
-  pthread_cond_broadcast(&d_cond);
-  pthread_mutex_unlock(&d_mut);
+  {
+    std::unique_lock<std::mutex> l(d_mut);
+    d_go = true;
+  }
+  d_cond.notify_all();
 }
 
 bool UeberBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial)
@@ -108,10 +109,10 @@ bool UeberBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool get
   return false;
 }
 
-bool UeberBackend::createDomain(const DNSName &domain)
+bool UeberBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account)
 {
   for(DNSBackend* mydb :  backends) {
-    if(mydb->createDomain(domain)) {
+    if(mydb->createDomain(domain, kind, masters, account)) {
       return true;
     }
   }
@@ -321,7 +322,7 @@ bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* s
         DLOG(g_log<<Logger::Error<<"has pos cache entry: "<<shorter<<endl);
         fillSOAData(d_answers[0], *sd);
 
-        sd->db = 0;
+        sd->db = nullptr;
         sd->qname = shorter;
         goto found;
       } else if(cstat == 0 && d_negcache_ttl) {
@@ -415,7 +416,7 @@ bool UeberBackend::getSOA(const DNSName &domain, SOAData &sd)
     fillSOAData(d_answers[0],sd);
     sd.domain_id=d_answers[0].domain_id;
     sd.ttl=d_answers[0].dr.d_ttl;
-    sd.db=0;
+    sd.db = nullptr;
     return true;
   }
 
@@ -453,6 +454,14 @@ bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd)
   return false;
 }
 
+bool UeberBackend::superMasterAdd(const string &ip, const string &nameserver, const string &account) 
+{
+  for(vector<DNSBackend *>::const_iterator i=backends.begin();i!=backends.end();++i)
+    if((*i)->superMasterAdd(ip, nameserver, account)) 
+      return true;
+  return false;
+}
+
 bool UeberBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
 {
   for(vector<DNSBackend *>::const_iterator i=backends.begin();i!=backends.end();++i)
@@ -463,9 +472,10 @@ bool UeberBackend::superMasterBackend(const string &ip, const DNSName &domain, c
 
 UeberBackend::UeberBackend(const string &pname)
 {
-  pthread_mutex_lock(&instances_lock);
-  instances.push_back(this); // report to the static list of ourself
-  pthread_mutex_unlock(&instances_lock);
+  {
+    std::lock_guard<std::mutex> l(instances_lock);
+    instances.push_back(this); // report to the static list of ourself
+  }
 
   d_negcached=0;
   d_ancount=0;
@@ -474,25 +484,23 @@ UeberBackend::UeberBackend(const string &pname)
   d_cache_ttl = ::arg().asNum("query-cache-ttl");
   d_negcache_ttl = ::arg().asNum("negquery-cache-ttl");
 
-  d_tid=pthread_self();
-  d_stale=false;
+  d_stale = false;
 
   backends=BackendMakers().all(pname=="key-only");
 }
 
-void del(DNSBackend* d)
+static void del(DNSBackend* d)
 {
   delete d;
 }
 
 void UeberBackend::cleanup()
 {
-  pthread_mutex_lock(&instances_lock);
-
-  remove(instances.begin(),instances.end(),this);
-  instances.resize(instances.size()-1);
-
-  pthread_mutex_unlock(&instances_lock);
+  {
+    std::lock_guard<std::mutex> l(instances_lock);
+    remove(instances.begin(),instances.end(),this);
+    instances.resize(instances.size()-1);
+  }
 
   for_each(backends.begin(),backends.end(),del);
 }
@@ -567,14 +575,11 @@ void UeberBackend::lookup(const QType &qtype,const DNSName &qname, int zoneId, D
   }
 
   DLOG(g_log<<"UeberBackend received question for "<<qtype.getName()<<" of "<<qname<<endl);
-  if(!d_go) {
-    pthread_mutex_lock(&d_mut);
-    while (d_go==false) {
-      g_log<<Logger::Error<<"UeberBackend is blocked, waiting for 'go'"<<endl;
-      pthread_cond_wait(&d_cond, &d_mut);
-      g_log<<Logger::Error<<"Broadcast received, unblocked"<<endl;
-    }
-    pthread_mutex_unlock(&d_mut);
+  if (!d_go) {
+    g_log<<Logger::Error<<"UeberBackend is blocked, waiting for 'go'"<<endl;
+    std::unique_lock<std::mutex> l(d_mut);
+    d_cond.wait(l, []{ return d_go == true; });
+    g_log<<Logger::Error<<"Broadcast received, unblocked"<<endl;
   }
 
   d_domain_id=zoneId;
index 09c3b451b15b585c1e1ed07dca095d11431f5c0a..4a21569ca05aa64ae99684655c4f5fefde531d29 100644 (file)
 #include <map>
 #include <string>
 #include <algorithm>
-#include <pthread.h>
-#include <semaphore.h>
+#include <mutex>
+#include <condition_variable>
 
-#include <unistd.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
 #include <boost/utility.hpp>
+
 #include "dnspacket.hh"
 #include "dnsbackend.hh"
-
 #include "namespaces.hh"
 
 /** This is a very magic backend that allows us to load modules dynamically,
@@ -52,11 +48,13 @@ public:
 
   bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db);
 
+  bool superMasterAdd(const string &ip, const string &nameserver, const string &account); 
+
   /** Tracks all created UeberBackend instances for us. We use this vector to notify
       existing threads of new modules 
   */
   static vector<UeberBackend *>instances;
-  static pthread_mutex_t instances_lock;
+  static std::mutex instances_lock;
 
   static bool loadmodule(const string &name);
   static bool loadModules(const vector<string>& modules, const string& path);
@@ -108,7 +106,7 @@ public:
   void getUnfreshSlaveInfos(vector<DomainInfo>* domains);
   void getUpdatedMasters(vector<DomainInfo>* domains);
   bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true);
-  bool createDomain(const DNSName &domain);
+  bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account);
   
   bool doesDNSSEC();
   bool addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& id);
@@ -134,13 +132,12 @@ public:
   bool searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result);
   bool searchComments(const string &pattern, int maxResults, vector<Comment>& result);
 private:
-  pthread_t d_tid;
   handle d_handle;
   vector<DNSZoneRecord> d_answers;
   vector<DNSZoneRecord>::const_iterator d_cachehandleiter;
 
-  static pthread_mutex_t d_mut;
-  static pthread_cond_t d_cond;
+  static std::mutex d_mut;
+  static std::condition_variable d_cond;
 
   struct Question
   {
index b8ce53a8d277bb1ad4858939a56bec34ba8b627a..88a120826e6f1d0c15738e161206b05a533b4491 100644 (file)
@@ -11,15 +11,47 @@ uint16_t g_maxNSEC3Iterations{0};
 
 #define LOG(x) if(g_dnssecLOG) { g_log <<Logger::Warning << x; }
 
-const char *dStates[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
-const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA", "TA"};
+static bool isAZoneKey(const DNSKEYRecordContent& key)
+{
+  /* rfc4034 Section 2.1.1:
+     "Bit 7 of the Flags field is the Zone Key flag.  If bit 7 has value 1,
+     then the DNSKEY record holds a DNS zone key, and the DNSKEY RR's
+     owner name MUST be the name of a zone.  If bit 7 has value 0, then
+     the DNSKEY record holds some other type of DNS public key and MUST
+     NOT be used to verify RRSIGs that cover RRsets."
+
+     Let's check that this is a ZONE key, even though there is no other
+     types of DNSKEYs at the moment.
+  */
+  return (key.d_flags & 256) != 0;
+}
+
+static bool isRevokedKey(const DNSKEYRecordContent& key)
+{
+  /* rfc5011 Section 3 */
+  return (key.d_flags & 128) != 0;
+}
 
 static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm)
 {
   vector<shared_ptr<DNSKEYRecordContent>> ret;
-  for(const auto& key : keys)
-    if(key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm)
+
+  for (const auto& key : keys) {
+    if (!isAZoneKey(*key)) {
+      LOG("Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" is not a zone key, skipping"<<endl;);
+      continue;
+    }
+
+    if (isRevokedKey(*key)) {
+      LOG("Key for tag "<<std::to_string(tag)<<" and algorithm "<<std::to_string(algorithm)<<" has been revoked, skipping"<<endl;);
+      continue;
+    }
+
+    if (key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm) {
       ret.push_back(key);
+    }
+  }
+
   return ret;
 }
 
@@ -334,19 +366,19 @@ static bool provesNSEC3NoWildCard(DNSName wildcard, uint16_t const qtype, const
 /*
   This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
   in validrrsets.
-  - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODATA
+  - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODENIAL
   if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9.
   - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return
   NXQTYPE is the name is proven to be ENT and NXDOMAIN otherwise.
   - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is
-  useful when we have a positive answer synthetized from a wildcard and we only need to prove that the exact
+  useful when we have a positive answer synthesized from a wildcard and we only need to prove that the exact
   name does not exist.
 */
 dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, bool needWildcardProof, unsigned int wildcardLabelsCount)
 {
   bool nsec3Seen = false;
   if (!needWildcardProof && wildcardLabelsCount == 0) {
-    throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthetized from a wildcard");
+    throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
   }
 
   for(const auto& v : validrrsets) {
@@ -374,14 +406,14 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec->isSet(QType::NS))<<", SOA is "<<std::to_string(nsec->isSet(QType::SOA))<<", signer is "<<signer<<", owner name is "<<owner<<endl);
           /* this is an "ancestor delegation" NSEC RR */
           LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
-          return NODATA;
+          return dState::NODENIAL;
         }
 
         /* check if the type is denied */
         if(qname == owner) {
           if (nsec->isSet(qtype)) {
             LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<endl);
-            return NODATA;
+            return dState::NODENIAL;
           }
 
           LOG("Denies existence of type "<<QType(qtype).getName()<<endl);
@@ -389,7 +421,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           /* RFC 6840 section 4.3 */
           if (nsec->isSet(QType::CNAME)) {
             LOG("However a CNAME exists"<<endl);
-            return NODATA;
+            return dState::NODENIAL;
           }
 
           /*
@@ -400,7 +432,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            */
           if (referralToUnsigned && qtype == QType::DS && !nsec->isSet(QType::NS)) {
             LOG("However, no NS record exists at this level!"<<endl);
-            return NODATA;
+            return dState::NODENIAL;
           }
 
           /* we know that the name exists (but this qtype doesn't) so except
@@ -411,44 +443,52 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           }
 
           if (!needWildcardProof || provesNoWildCard(qname, qtype, validrrsets)) {
-            return NXQTYPE;
+            return dState::NXQTYPE;
           }
 
           LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
-          return NODATA;
+          return dState::NODENIAL;
         }
 
         /* check if the whole NAME is denied existing */
-        if(isCoveredByNSEC(qname, owner, nsec->d_next)) {
+        if (isCoveredByNSEC(qname, owner, nsec->d_next)) {
           LOG(qname<<" is covered ");
-          /* if the name is an ENT and we received a NODATA answer,
-             we are fine with a NSEC proving that the name does not exist. */
-          if (wantsNoDataProof && nsecProvesENT(qname, owner, nsec->d_next)) {
-            LOG("Denies existence of type "<<qname<<"/"<<QType(qtype).getName()<<" by proving that "<<qname<<" is an ENT"<<endl);
-            return NXQTYPE;
+
+          if (nsecProvesENT(qname, owner, nsec->d_next)) {
+            if (wantsNoDataProof) {
+              /* if the name is an ENT and we received a NODATA answer,
+                 we are fine with a NSEC proving that the name does not exist. */
+              LOG("Denies existence of type "<<qname<<"/"<<QType(qtype).getName()<<" by proving that "<<qname<<" is an ENT"<<endl);
+              return dState::NXQTYPE;
+            }
+            else {
+              /* but for a NXDOMAIN proof, this doesn't make sense! */
+              LOG("but it tries to deny the existence of "<<qname<<" by proving that "<<qname<<" is an ENT, this does not make sense!"<<endl);
+              return dState::NODENIAL;
+            }
           }
 
           if (!needWildcardProof) {
             LOG("and we did not need a wildcard proof"<<endl);
-            return NXDOMAIN;
+            return dState::NXDOMAIN;
           }
 
           LOG("but we do need a wildcard proof so ");
           if (wantsNoDataProof) {
             LOG("looking for NODATA proof"<<endl);
             if (provesNoDataWildCard(qname, qtype, validrrsets)) {
-              return NXQTYPE;
+              return dState::NXQTYPE;
             }
           }
           else {
             LOG("looking for NO wildcard proof"<<endl);
             if (provesNoWildCard(qname, qtype, validrrsets)) {
-              return NXDOMAIN;
+              return dState::NXDOMAIN;
             }
           }
 
           LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl);
-          return NODATA;
+          return dState::NODENIAL;
         }
 
         LOG("Did not deny existence of "<<QType(qtype).getName()<<", "<<owner<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
@@ -469,7 +509,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
         string h = getHashFromNSEC3(qname, nsec3);
         if (h.empty()) {
           LOG("Unsupported hash, ignoring"<<endl);
-          return INSECURE;
+          return dState::INSECURE;
         }
 
         nsec3Seen = true;
@@ -488,14 +528,14 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec3->isSet(QType::NS))<<", SOA is "<<std::to_string(nsec3->isSet(QType::SOA))<<", signer is "<<signer<<", owner name is "<<v.first.first<<endl);
           /* this is an "ancestor delegation" NSEC3 RR */
           LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
-          return NODATA;
+          return dState::NODENIAL;
         }
 
         // If the name exists, check if the qtype is denied
         if(beginHash == h) {
           if (nsec3->isSet(qtype)) {
             LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
-            return NODATA;
+            return dState::NODENIAL;
           }
 
           LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
@@ -503,7 +543,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           /* RFC 6840 section 4.3 */
           if (nsec3->isSet(QType::CNAME)) {
             LOG("However a CNAME exists"<<endl);
-            return NODATA;
+            return dState::NODENIAL;
           }
 
           /*
@@ -515,10 +555,10 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
            */
           if (referralToUnsigned && qtype == QType::DS && !nsec3->isSet(QType::NS)) {
             LOG("However, no NS record exists at this level!"<<endl);
-            return NODATA;
+            return dState::NODENIAL;
           }
 
-          return NXQTYPE;
+          return dState::NXQTYPE;
         }
       }
     }
@@ -526,7 +566,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
   /* if we have no NSEC3 records, we are done */
   if (!nsec3Seen) {
-    return NODATA;
+    return dState::NODENIAL;
   }
 
   DNSName closestEncloser(qname);
@@ -556,7 +596,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
             string h = getHashFromNSEC3(closestEncloser, nsec3);
             if (h.empty()) {
-              return INSECURE;
+              return dState::INSECURE;
             }
 
             string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
@@ -617,7 +657,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
             string h = getHashFromNSEC3(nextCloser, nsec3);
             if (h.empty()) {
-              return INSECURE;
+              return dState::INSECURE;
             }
 
             string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
@@ -650,24 +690,24 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
     if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists)) {
       if (!isOptOut) {
         LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl);
-        return NODATA;
+        return dState::NODENIAL;
       }
     }
 
     if (isOptOut) {
-      return OPTOUT;
+      return dState::OPTOUT;
     }
     else {
       if (wildcardExists) {
-        return NXQTYPE;
+        return dState::NXQTYPE;
       }
-      return NXDOMAIN;
+      return dState::NXDOMAIN;
     }
   }
 
   // There were no valid NSEC(3) records
   // XXX maybe this should be INSECURE... it depends on the semantics of this function
-  return NODATA;
+  return dState::NODENIAL;
 }
 
 /*
@@ -693,7 +733,7 @@ static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& en
     qname.prependRawLabel(labelsToAdd.back());
     labelsToAdd.pop_back();
     auto records = dro.get(qname, (uint16_t)QType::NS);
-    for (const auto record : records) {
+    for (const auto& record : records) {
       if(record.d_type != QType::NS || record.d_name != qname)
         continue;
       foundCut = true;
@@ -743,17 +783,17 @@ bool validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t&
       LOG(name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
     }
 
-    auto r = getByTag(keys, signature->d_tag, signature->d_algorithm);
+    auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm);
 
-    if(r.empty()) {
+    if (keysMatchingTag.empty()) {
       LOG("No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
       continue;
     }
 
-    string msg=getMessageForRRSET(name, *signature, toSign, true);
-    for(const auto& l : r) {
-      bool signIsValid = checkSignatureWithKey(now, signature, l, msg);
-      if(signIsValid) {
+    string msg = getMessageForRRSET(name, *signature, toSign, true);
+    for(const auto& key : keysMatchingTag) {
+      bool signIsValid = checkSignatureWithKey(now, signature, key, msg);
+      if (signIsValid) {
         isValid = true;
         LOG("Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
         //       cerr<<"valid"<<endl;
@@ -876,11 +916,6 @@ void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& ds
     }
   }
 
-  vector<uint16_t> toSignTags;
-  for (const auto& key : tkeys) {
-    toSignTags.push_back(key->getTag());
-  }
-
   //    cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
   // these counts could be off if we somehow ended up with
   // duplicate keys. Should switch to a type that prevents that.
@@ -924,7 +959,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
   auto luaLocal = g_luaconfs.getLocal();
   const auto anchors = luaLocal->dsAnchors;
   if (anchors.empty()) // Nothing to do here
-    return Insecure;
+    return vState::Insecure;
 
   // Determine the lowest (i.e. with the most labels) Trust Anchor for zone
   DNSName lowestTA(".");
@@ -953,7 +988,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
        */
       if(lowestTA.countLabels() <= lowestNTA.countLabels()) {
         LOG("marking answer Insecure"<<endl);
-        return NTA; // Not Insecure, this way validateRecords() can shortcut
+        return vState::NTA; // Not Insecure, this way validateRecords() can shortcut
       }
       LOG("but a Trust Anchor for "<<lowestTA<<" is configured, continuing validation."<<endl);
     }
@@ -1020,14 +1055,14 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
     if(validkeys.empty())
     {
       LOG("ended up with zero valid DNSKEYs, going Bogus"<<endl);
-      return Bogus;
+      return vState::Bogus;
     }
     LOG("situation: we have one or more valid DNSKEYs for ["<<*zoneCutIter<<"] (want ["<<zone<<"])"<<endl);
 
     if(zoneCutIter == zoneCuts.cend()-1) {
       LOG("requested keyset found! returning Secure for the keyset"<<endl);
       keyset.insert(validkeys.cbegin(), validkeys.cend());
-      return Secure;
+      return vState::Secure;
     }
 
     // We now have the DNSKEYs, use them to validate the DS records at the next zonecut
@@ -1050,10 +1085,10 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
     if(r.first == r.second) {
       LOG("No DS for "<<*(zoneCutIter+1)<<", now look for a secure denial"<<endl);
       dState res = getDenial(validrrsets, *(zoneCutIter+1), QType::DS, true, true);
-      if (res == INSECURE || res == NXDOMAIN)
-        return Bogus;
-      if (res == NXQTYPE || res == OPTOUT)
-        return Insecure;
+      if (res == dState::INSECURE || res == dState::NXDOMAIN)
+        return vState::Bogus;
+      if (res == dState::NXQTYPE || res == dState::OPTOUT)
+        return vState::Insecure;
     }
 
     /*
@@ -1070,7 +1105,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
     }
   }
   // There were no zone cuts (aka, we should never get here)
-  return Bogus;
+  return vState::Bogus;
 }
 
 bool isSupportedDS(const DSRecordContent& ds)
@@ -1090,7 +1125,7 @@ bool isSupportedDS(const DSRecordContent& ds)
 
 DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
 {
-  for (const auto sig : signatures) {
+  for (const auto& sig : signatures) {
     if (sig) {
       return sig->d_signer;
     }
@@ -1098,3 +1133,22 @@ DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signa
 
   return DNSName();
 }
+
+const std::string& vStateToString(vState state)
+{
+  static const std::vector<std::string> vStates = {"Indeterminate", "Bogus", "Insecure", "Secure", "NTA", "TA"};
+  return vStates.at(static_cast<size_t>(state));
+}
+
+std::ostream& operator<<(std::ostream &os, const vState d)
+{
+  os<<vStateToString(d);
+  return os;
+}
+
+std::ostream& operator<<(std::ostream &os, const dState d)
+{
+  static const std::vector<std::string> dStates = {"no denial", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
+  os<<dStates.at(static_cast<size_t>(d));
+  return os;
+}
index 76e9de3d046ab204d1da8c0bb92cd33c44492b30..60054aa1303e88b70558185c7d5fb63a1bfa7d4a 100644 (file)
@@ -33,12 +33,14 @@ extern time_t g_signatureInceptionSkew;
 extern uint16_t g_maxNSEC3Iterations;
 
 // 4033 5
-enum vState { Indeterminate, Bogus, Insecure, Secure, NTA, TA };
-extern const char *vStates[];
+enum class vState : uint8_t { Indeterminate, Bogus, Insecure, Secure, NTA, TA };
+const std::string& vStateToString(vState state);
 
 // NSEC(3) results
-enum dState { NODATA, NXDOMAIN, NXQTYPE, ENT, INSECURE, OPTOUT};
-extern const char *dStates[];
+enum class dState : uint8_t { NODENIAL, NXDOMAIN, NXQTYPE, ENT, INSECURE, OPTOUT};
+
+std::ostream& operator<<(std::ostream &os, const vState d);
+std::ostream& operator<<(std::ostream &os, const dState d);
 
 class DNSRecordOracle
 {
diff --git a/pdns/views.hh b/pdns/views.hh
new file mode 100644 (file)
index 0000000..d931c09
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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
+
+#ifdef __cpp_lib_string_view
+using pdns_string_view = std::string_view;
+#else
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106400
+// string_view already exists in 1.61.0 but string_view::at() is not usable with modern compilers, see:
+// https://github.com/boostorg/utility/pull/26
+#include <boost/utility/string_view.hpp>
+using pdns_string_view = boost::string_view;
+#elif BOOST_VERSION >= 105300
+#include <boost/utility/string_ref.hpp>
+using pdns_string_view = boost::string_ref;
+#else
+using pdns_string_view = std::string;
+#endif
+#endif
index eafd3059a00736bc4bda75fc2dbcc4ef99a95436..ca10668787c54e0110bc5de5b000134a55150d48 100644 (file)
@@ -107,7 +107,7 @@ static void bareHandlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Reque
 
 void WebServer::registerBareHandler(const string& url, HandlerFunction handler)
 {
-  YaHTTP::THandlerFunction f = boost::bind(&bareHandlerWrapper, handler, _1, _2);
+  YaHTTP::THandlerFunction f = [=](YaHTTP::Request* req, YaHTTP::Response* resp){return bareHandlerWrapper(handler, req, resp);};
   YaHTTP::Router::Any(url, f);
 }
 
@@ -179,7 +179,7 @@ void WebServer::apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req,
 }
 
 void WebServer::registerApiHandler(const string& url, HandlerFunction handler, bool allowPassword) {
-  HandlerFunction f = boost::bind(&WebServer::apiWrapper, this, handler, _1, _2, allowPassword);
+  HandlerFunction f = std::bind(&WebServer::apiWrapper, this, handler, std::placeholders::_1, std::placeholders::_2, allowPassword);
   registerBareHandler(url, f);
 }
 
@@ -196,7 +196,7 @@ void WebServer::webWrapper(WebServer::HandlerFunction handler, HttpRequest* req,
 }
 
 void WebServer::registerWebHandler(const string& url, HandlerFunction handler) {
-  HandlerFunction f = boost::bind(&WebServer::webWrapper, this, handler, _1, _2);
+  HandlerFunction f = std::bind(&WebServer::webWrapper, this, handler, std::placeholders::_1, std::placeholders::_2);
   registerBareHandler(url, f);
 }
 
index 8a8c4336e9a325744d287f63c649e58558814b9a..32bb9ad27b477a1dcf9b4c0feb976270c96e8219 100644 (file)
@@ -62,7 +62,6 @@ static const std::set<uint16_t> onlyOneEntryTypes = { QType::CNAME, QType::DNAME
 static const std::set<uint16_t> exclusiveEntryTypes = { QType::CNAME };
 
 AuthWebServer::AuthWebServer() :
-  d_tid(0),
   d_start(time(nullptr)),
   d_min10(0),
   d_min5(0),
@@ -87,8 +86,10 @@ AuthWebServer::AuthWebServer() :
 void AuthWebServer::go()
 {
   S.doRings();
-  pthread_create(&d_tid, 0, webThreadHelper, this);
-  pthread_create(&d_tid, 0, statThreadHelper, this);
+  std::thread webT(std::bind(&AuthWebServer::webThread, this));
+  webT.detach();
+  std::thread statT(std::bind(&AuthWebServer::statThread, this));
+  statT.detach();
 }
 
 void AuthWebServer::statThread()
@@ -110,20 +111,6 @@ void AuthWebServer::statThread()
   }
 }
 
-void *AuthWebServer::statThreadHelper(void *p)
-{
-  AuthWebServer *self=static_cast<AuthWebServer *>(p);
-  self->statThread();
-  return 0; // never reached
-}
-
-void *AuthWebServer::webThreadHelper(void *p)
-{
-  AuthWebServer *self=static_cast<AuthWebServer *>(p);
-  self->webThread();
-  return 0; // never reached
-}
-
 static string htmlescape(const string &s) {
   string result;
   for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
@@ -147,7 +134,7 @@ static string htmlescape(const string &s) {
   return result;
 }
 
-void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
+static void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
 {
   int tot=0;
   int entries=0;
@@ -628,27 +615,54 @@ static void throwUnableToSecure(const DNSName& zonename) {
       + "capable backends are loaded, or because the backends have DNSSEC disabled. Check your configuration.");
 }
 
-static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json document, bool rectifyTransaction=true) {
-  vector<string> zonemaster;
-  bool shouldRectify = false;
-  for(auto value : document["masters"].array_items()) {
-    string master = value.string_value();
-    if (master.empty())
-      throw ApiException("Master can not be an empty string");
-    try {
-      ComboAddress m(master);
-    } catch (const PDNSException &e) {
-      throw ApiException("Master (" + master + ") is not an IP address: " + e.reason);
+
+static void extractDomainInfoFromDocument(const Json document, boost::optional<DomainInfo::DomainKind>& kind, boost::optional<vector<ComboAddress>>& masters, boost::optional<string>& account) {
+  if (document["kind"].is_string()) {
+    kind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
+  } else {
+    kind = boost::none;
+  }
+
+  if (document["masters"].is_array()) {
+    masters = vector<ComboAddress>();
+    for(auto value : document["masters"].array_items()) {
+      string master = value.string_value();
+      if (master.empty())
+        throw ApiException("Master can not be an empty string");
+      try {
+        masters->emplace_back(master, 53);
+      } catch (const PDNSException &e) {
+        throw ApiException("Master (" + master + ") is not an IP address: " + e.reason);
+      }
     }
-    zonemaster.push_back(master);
+  } else {
+    masters = boost::none;
   }
 
-  if (zonemaster.size()) {
-    di.backend->setMaster(zonename, boost::join(zonemaster, ","));
+  if (document["account"].is_string()) {
+    account = document["account"].string_value();
+  } else {
+    account = boost::none;
   }
-  if (document["kind"].is_string()) {
-    di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
+}
+
+static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json document, bool rectifyTransaction=true) {
+  boost::optional<DomainInfo::DomainKind> kind;
+  boost::optional<vector<ComboAddress>> masters;
+  boost::optional<string> account;
+
+  extractDomainInfoFromDocument(document, kind, masters, account);
+
+  if (kind) {
+    di.backend->setKind(zonename, *kind);
+  }
+  if (masters) {
+    di.backend->setMasters(zonename, *masters);
   }
+  if (account) {
+    di.backend->setAccount(zonename, *account);
+  }
+
   if (document["soa_edit_api"].is_string()) {
     di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
   }
@@ -661,13 +675,13 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
   }
   catch (const JsonException&) {}
 
-  if (document["account"].is_string()) {
-    di.backend->setAccount(zonename, document["account"].string_value());
-  }
 
   DNSSECKeeper dk(&B);
+  bool shouldRectify = false;
   bool dnssecInJSON = false;
   bool dnssecDocVal = false;
+  bool nsec3paramInJSON = false;
+  string nsec3paramDocVal;
 
   try {
     dnssecDocVal = boolFromJson(document, "dnssec");
@@ -675,6 +689,13 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
   }
   catch (const JsonException&) {}
 
+  try {
+    nsec3paramDocVal = stringFromJson(document, "nsec3param");
+    nsec3paramInJSON = true;
+  }
+  catch (const JsonException&) {}
+
+
   bool isDNSSECZone = dk.isSecuredZone(zonename);
 
   if (dnssecInJSON) {
@@ -725,19 +746,30 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
     }
   }
 
-  if(document["nsec3param"].string_value().length() > 0) {
+  if (nsec3paramInJSON) {
     shouldRectify = true;
-    NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
-    string error_msg = "";
     if (!isDNSSECZone) {
       throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"', but zone is not DNSSEC secured.");
     }
-    if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
-      throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
+
+    if (nsec3paramDocVal.length() == 0) {
+      // Switch to NSEC
+      if (!dk.unsetNSEC3PARAM(zonename)) {
+        throw ApiException("Unable to remove NSEC3PARAMs from zone '" + zonename.toString());
+      }
     }
-    if (!dk.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) {
-      throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() +
-          "' passed our basic sanity checks, but cannot be used with the current backend.");
+
+    if (nsec3paramDocVal.length() > 0) {
+      // Set the NSEC3PARAMs
+      NSEC3PARAMRecordContent ns3pr(nsec3paramDocVal);
+      string error_msg = "";
+      if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
+        throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
+      }
+      if (!dk.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) {
+        throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() +
+            "' passed our basic sanity checks, but cannot be used with the current backend.");
+      }
     }
   }
 
@@ -823,8 +855,6 @@ static bool isValidMetadataKind(const string& kind, bool readonly) {
     "NOTIFY-DNSUPDATE",
     "ALSO-NOTIFY",
     "AXFR-MASTER-TSIG",
-    "GSS-ALLOW-AXFR-PRINCIPAL",
-    "GSS-ACCEPTOR-PRINCIPAL",
     "IXFR",
     "LUA-AXFR-SCRIPT",
     "NSEC3NARROW",
@@ -1348,7 +1378,7 @@ static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou
   }
 }
 
-/** Throws ApiException if records which violate RRset contraints are present.
+/** Throws ApiException if records which violate RRset constraints are present.
  *  NOTE: sorts records in-place.
  *
  *  Constraints being checked:
@@ -1663,8 +1693,13 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
       }
     }
 
+    boost::optional<DomainInfo::DomainKind> kind;
+    boost::optional<vector<ComboAddress>> masters;
+    boost::optional<string> account;
+    extractDomainInfoFromDocument(document, kind, masters, account);
+
     // no going back after this
-    if(!B.createDomain(zonename))
+    if(!B.createDomain(zonename, kind.get_value_or(DomainInfo::Native), masters.get_value_or(vector<ComboAddress>()), account.get_value_or("")))
       throw ApiException("Creating domain '"+zonename.toString()+"' failed");
 
     if(!B.getDomainInfo(zonename, di))
@@ -1836,7 +1871,7 @@ static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
   if(di.masters.empty())
     throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
 
-  random_shuffle(di.masters.begin(), di.masters.end());
+  shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
   Communicator.addSuckRequest(zonename, di.masters.front());
   resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front().toLogString());
 }
@@ -1873,11 +1908,8 @@ static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) {
 
   DNSSECKeeper dk(&B);
 
-  if (!dk.isSecuredZone(zonename))
-    throw ApiException("Zone '" + zonename.toString() + "' is not DNSSEC signed, not rectifying.");
-
-  if (di.kind == DomainInfo::Slave)
-    throw ApiException("Zone '" + zonename.toString() + "' is a slave zone, not rectifying.");
+  if (dk.isPresigned(zonename))
+    throw ApiException("Zone '" + zonename.toString() + "' is pre-signed, not rectifying.");
 
   string error_msg = "";
   string info;
@@ -1983,7 +2015,7 @@ static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
     di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
     bool soa_edit_done = false;
 
-    set<pair<DNSName, QType>> seen;
+    set<tuple<DNSName, QType, string>> seen;
 
     for (const auto& rrset : rrsets.array_items()) {
       string changetype = toUpper(stringFromJson(rrset, "changetype"));
@@ -1995,11 +2027,11 @@ static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
         throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
       }
 
-      if(seen.count({qname, qtype}))
+      if(seen.count({qname, qtype, changetype}))
       {
-        throw ApiException("Duplicate RRset "+qname.toString()+" IN "+qtype.getName());
+        throw ApiException("Duplicate RRset "+qname.toString()+" IN "+qtype.getName()+" with changetype: "+changetype);
       }
-      seen.insert({qname, qtype});
+      seen.insert({qname, qtype, changetype});
 
       if (changetype == "DELETE") {
         // delete all matching qname/qtype RRs (and, implicitly comments).
@@ -2197,7 +2229,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
 
   B.getAllDomains(&domains, true);
 
-  for(const DomainInfo di: domains)
+  for(const DomainInfo& di: domains)
   {
     if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && sm.match(di.zone)) {
       doc.push_back(Json::object {
@@ -2253,7 +2285,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
   resp->setBody(doc);
 }
 
-void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
+static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "PUT")
     throw HttpMethodNotAllowedException();
 
@@ -2266,6 +2298,39 @@ void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
   });
 }
 
+static std::ostream& operator<<(std::ostream& os, StatType statType)
+{
+  switch (statType)
+  {
+    case StatType::counter: return os << "counter";
+    case StatType::gauge: return os << "gauge";
+  };
+  return os << static_cast<uint16_t>(statType);
+}
+
+static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) {
+  if (req->method != "GET")
+    throw HttpMethodNotAllowedException();
+
+  std::ostringstream output;
+  for (const auto &metricName : S.getEntries()) {
+    // Prometheus suggest using '_' instead of '-'
+    std::string prometheusMetricName = "pdns_auth_" + boost::replace_all_copy(metricName, "-", "_");
+
+    output << "# HELP " << prometheusMetricName << " " << S.getDescrip(metricName) << "\n";
+    output << "# TYPE " << prometheusMetricName << " " << S.getStatType(metricName) << "\n";
+    output << prometheusMetricName << " " << S.read(metricName) << "\n";
+  }
+
+  output << "# HELP pdns_auth_info " << "Info from PowerDNS, value is always 1" << "\n";
+  output << "# TYPE pdns_auth_info " << "gauge" << "\n";
+  output << "pdns_auth_info{version=\"" << VERSION << "\"} " << "1" << "\n";
+
+  resp->body = output.str();
+  resp->headers["Content-Type"] = "text/plain";
+  resp->status = 200;
+}
+
 void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
 {
   resp->headers["Cache-Control"] = "max-age=86400";
@@ -2328,8 +2393,9 @@ void AuthWebServer::webThread()
       d_ws->registerApiHandler("/api", &apiDiscovery);
     }
     if (::arg().mustDo("webserver")) {
-      d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
-      d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
+      d_ws->registerWebHandler("/style.css", std::bind(&AuthWebServer::cssfunction, this, std::placeholders::_1, std::placeholders::_2));
+      d_ws->registerWebHandler("/", std::bind(&AuthWebServer::indexfunction, this, std::placeholders::_1, std::placeholders::_2));
+      d_ws->registerWebHandler("/metrics", prometheusMetrics);
     }
     d_ws->go();
   }
index b613174f6216a9fbfdab7450e656515eeb80e43b..2cd127345fadae064a349f2585dc6c61669f5c70 100644 (file)
@@ -21,6 +21,7 @@
  */
 #pragma once
 #include <string>
+#include <tuple>
 #include <map>
 #include <time.h>
 #include <pthread.h>
@@ -78,8 +79,6 @@ public:
   static string makePercentage(const double& val);
 
 private:
-  static void *webThreadHelper(void *);
-  static void *statThreadHelper(void *p);
   void indexfunction(HttpRequest* req, HttpResponse* resp);
   void cssfunction(HttpRequest* req, HttpResponse* resp);
   void jsonstat(HttpRequest* req, HttpResponse* resp);
@@ -88,7 +87,6 @@ private:
   void printargs(ostringstream &ret);
   void webThread();
   void statThread();
-  pthread_t d_tid;
 
   time_t d_start;
   double d_min10, d_min5, d_min1;
index 8a0977495bda22314d3df0cb96b784a49539d9bf..83e3e43af3b7f47bb97745b969e0999863ab56d5 100644 (file)
@@ -382,9 +382,9 @@ static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
   DNSName canon = apiNameToDNSName(req->getvars["domain"]);
   bool subtree = (req->getvars.count("subtree") > 0 && req->getvars["subtree"].compare("true") == 0);
 
-  int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon, subtree, 0xffff));
-  count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, canon, subtree, 0xffff));
-  count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon, subtree));
+  int count = broadcastAccFunction<uint64_t>([=]{return pleaseWipeCache(canon, subtree, 0xffff);});
+  count += broadcastAccFunction<uint64_t>([=]{return pleaseWipePacketCache(canon, subtree, 0xffff);});
+  count += broadcastAccFunction<uint64_t>([=]{return pleaseWipeAndCountNegCache(canon, subtree);});
   resp->setBody(Json::object {
     { "count", count },
     { "result", "Flushed cache." }
@@ -404,8 +404,8 @@ static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp) {
     auto zone = luaconf->dfe.getZone(i);
     if (zone == nullptr)
       continue;
-    auto name = zone->getName();
-    auto stats = getRPZZoneStats(*name);
+    const auto& name = zone->getName();
+    auto stats = getRPZZoneStats(name);
     if (stats == nullptr)
       continue;
     Json::object zoneInfo = {
@@ -416,7 +416,7 @@ static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp) {
       {"last_update", (double)stats->d_lastUpdate},
       {"serial", (double)stats->d_serial},
     };
-    ret[*name] = zoneInfo;
+    ret[name] = zoneInfo;
   }
   resp->setBody(ret);
 }
@@ -458,6 +458,10 @@ static void prometheusMetrics(HttpRequest *req, HttpResponse *resp) {
         output << prometheusMetricName << " " << tup.second << "\n";
     }
 
+    output << "# HELP pdns_recursor_info " << "Info from pdns_recursor, value is always 1" << "\n";
+    output << "# TYPE pdns_recursor_info " << "gauge" << "\n";
+    output << "pdns_recursor_info{version=\"" << VERSION << "\"} " << "1" << "\n";
+
     resp->body = output.str();
     resp->headers["Content-Type"] = "text/plain";
     resp->status = 200;
@@ -512,7 +516,7 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
   d_ws->bind();
 
   // legacy dispatch
-  d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2), true);
+  d_ws->registerApiHandler("/jsonstat", std::bind(&RecursorWebServer::jsonstat, this, std::placeholders::_1, std::placeholders::_2), true);
   d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
   d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", &apiServerConfigAllowFrom);
   d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
@@ -664,7 +668,7 @@ void AsyncServerNewConnectionMT(void *p) {
 void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
 {
   d_asyncNewConnectionCallback = callback;
-  fdm->addReadFD(d_server_socket.getHandle(), boost::bind(&AsyncServer::newConnection, this));
+  fdm->addReadFD(d_server_socket.getHandle(), std::bind(&AsyncServer::newConnection, this));
 }
 
 void AsyncServer::newConnection()
@@ -674,6 +678,10 @@ void AsyncServer::newConnection()
 
 // This is an entry point from FDM, so it needs to catch everything.
 void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const {
+  if (!client->acl(d_acl)) {
+    return;
+  }
+
   const string logprefix = d_logprefix + to_string(getUniqueID()) + " ";
 
   HttpRequest req(logprefix);
@@ -743,5 +751,5 @@ void AsyncWebServer::go() {
   auto server = std::dynamic_pointer_cast<AsyncServer>(d_server);
   if (!server)
     return;
-  server->asyncWaitForConnections(d_fdm, boost::bind(&AsyncWebServer::serveConnection, this, _1));
+  server->asyncWaitForConnections(d_fdm, std::bind(&AsyncWebServer::serveConnection, this, std::placeholders::_1));
 }
index 897316b647274f0df6f8f399654f314409cb8651..f09177222d57d8e37643f00fbf5c9dc5b70bb832 100644 (file)
@@ -30,7 +30,10 @@ class HttpResponse;
 
 class AsyncServer : public Server {
 public:
-  AsyncServer(const string &localaddress, int port) : Server(localaddress, port) { };
+  AsyncServer(const string &localaddress, int port) : Server(localaddress, port)
+  {
+    d_server_socket.setNonBlocking();
+  };
 
   friend void AsyncServerNewConnectionMT(void *p);
 
index b7ebcb3c31c9fd16bc018824790fc5829fe76271..6e514a5123714a82084ee4cfd3b6d8591b4f8f66 100644 (file)
@@ -56,7 +56,7 @@ map<string, bool> g_entries;
 map<DNSName,bool> g_recorddata;
 map<DNSName, map<string, bool> > g_recordttl;
 
-std::string encode_non_ascii( const std::string &input ) {
+static std::string encode_non_ascii( const std::string &input ) {
         std::ostringstream out;
 
         for ( auto i : input ) {
index 08fca465d4ada00c430e17772c2e974b61a51844..5b8c2bb6b52f3bca99fd5db8e088e41e18f164a4 100644 (file)
@@ -175,29 +175,14 @@ static void emitRecord(const DNSName& zoneName, const DNSName &DNSqname, const s
     trim_left(content);
   }
 
-  bool auth = true;
-  if(qtype == "NS" && !pdns_iequals(qname, zname)) {
-    auth=false;
-  }
-
-  if(g_mode==MYSQL || g_mode==SQLITE) {
-    cout<<"insert into records (domain_id, name, type,content,ttl,prio,disabled) select id ,"<<
-      sqlstr(toLower(qname))<<", "<<
-      sqlstr(qtype)<<", "<<
-      sqlstr(stripDotContent(content))<<", "<<ttl<<", "<<prio<<", "<<disabled<<
-      " from domains where name="<<toLower(sqlstr(zname))<<";\n";
-
-    if(!recordcomment.empty()) {
-      cout<<"insert into comments (domain_id,name,type,modified_at, comment) select id, "<<toLower(sqlstr(stripDot(qname)))<<", "<<sqlstr(qtype)<<", "<<time(0)<<", "<<sqlstr(recordcomment)<<" from domains where name="<<toLower(sqlstr(zname))<<";\n";
-    }
-  }
-  else if(g_mode==POSTGRES) {
-    cout<<"insert into records (domain_id, name, ordername, auth, type,content,ttl,prio,disabled) select id ,"<<
-      sqlstr(toLower(qname))<<", "<<
-      sqlstr(DNSName(qname).makeRelative(DNSName(zname)).makeLowerCase().labelReverse().toString(" ", false))<<", '"<< (auth  ? 't' : 'f') <<"', "<<
-      sqlstr(qtype)<<", "<<
-      sqlstr(stripDotContent(content))<<", "<<ttl<<", "<<prio<<", '"<<(disabled ? 't': 'f') <<
-      "' from domains where name="<<toLower(sqlstr(zname))<<";\n";
+  cout<<"insert into records (domain_id, name, type,content,ttl,prio,disabled) select id ,"<<
+    sqlstr(toLower(qname))<<", "<<
+    sqlstr(qtype)<<", "<<
+    sqlstr(stripDotContent(content))<<", "<<ttl<<", "<<prio<<", "<<(g_mode==POSTGRES ? (disabled ? "'t'" : "'f'") : std::to_string(disabled))<<
+    " from domains where name="<<toLower(sqlstr(zname))<<";\n";
+
+  if(!recordcomment.empty()) {
+    cout<<"insert into comments (domain_id,name,type,modified_at, comment) select id, "<<toLower(sqlstr(stripDot(qname)))<<", "<<sqlstr(qtype)<<", "<<time(0)<<", "<<sqlstr(recordcomment)<<" from domains where name="<<toLower(sqlstr(zname))<<";\n";
   }
 }
 
index 0dc2b646701900218566b8921b7d3d670e35223e..5a4f8336f87c822c2b00120cf93302204e4dea06 100644 (file)
@@ -222,7 +222,7 @@ bool ZoneParserTNG::getTemplateLine()
   return true;
 }
 
-void chopComment(string& line)
+static void chopComment(string& line)
 {
   if(line.find(';')==string::npos)
     return;
@@ -240,7 +240,7 @@ void chopComment(string& line)
     line.resize(pos);
 }
 
-bool findAndElide(string& line, char c)
+static bool findAndElide(string& line, char c)
 {
   string::size_type pos, len = line.length();
   bool inQuote=false;
index 88715cf219cb33fa4cae8c960df4298e200d38bc..dc08921bedf3e06caf40a412d37810d2e5bc1b44 100644 (file)
@@ -60,7 +60,7 @@ class AuthTSIG(ApiTestCase, AuthTSIGHelperMixin):
         """
         Try to get get a key that does not exist
         """
-        name = "idontexist"
+        name = "idonotexist"
         r = self.session.get(self.url(
             "/api/v1/servers/localhost/tsigkeys/" + name + '.'),
             headers={'accept': 'application/json'})
index ca372f124a5c2026d613988e9d84ae2264628930..e2069d8efa43e8317818acd018f58e57a587ff89 100644 (file)
@@ -539,6 +539,21 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         self.assertEquals(data['kind'], 'NSEC3NARROW')
         self.assertEquals(data['metadata'][0], '1')
 
+    def test_create_zone_with_nsec3param_switch_to_nsec(self):
+        """
+        Create a zone with "nsec3param", then remove the params
+        """
+        name, payload, data = self.create_zone(dnssec=True,
+                                               nsec3param='1 0 1 ab')
+        self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
+                         data=json.dumps({'nsec3param': ''}))
+        r = self.session.get(
+            self.url("/api/v1/servers/localhost/zones/" + name))
+        data = r.json()
+
+        self.assertEquals(r.status_code, 200)
+        self.assertEquals(data['nsec3param'], '')
+
     def test_create_zone_dnssec_serial(self):
         """
         Create a zone set/unset "dnssec" and see if the serial was increased
@@ -1184,6 +1199,35 @@ $ORIGIN %NAME%
         data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
         self.assertIsNone(get_rrset(data, name, 'NS'))
 
+    def test_zone_rr_update_rrset_combine_replace_and_delete(self):
+        name, payload, zone = self.create_zone()
+        rrset1 = {
+            'changetype': 'delete',
+            'name': 'sub.' + name,
+            'type': 'CNAME',
+        }
+        rrset2 = {
+            'changetype': 'replace',
+            'name': 'sub.' + name,
+            'type': 'CNAME',
+            'ttl': 500,
+            'records': [
+                {
+                    "content": "www.example.org.",
+                    "disabled": False
+                }
+            ]
+        }
+        payload = {'rrsets': [rrset1, rrset2]}
+        r = self.session.patch(
+            self.url("/api/v1/servers/localhost/zones/" + name),
+            data=json.dumps(payload),
+            headers={'content-type': 'application/json'})
+        self.assert_success(r)
+        # verify that (only) the new record is there
+        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        self.assertEquals(get_rrset(data, 'sub.' + name, 'CNAME')['records'], rrset2['records'])
+
     def test_zone_disable_reenable(self):
         # This also tests that SOA-EDIT-API works.
         name, payload, zone = self.create_zone(soa_edit_api='EPOCH')
@@ -1916,6 +1960,27 @@ $ORIGIN %NAME%
         dbrecs = get_db_records(name, 'AAAA')
         self.assertIsNone(dbrecs[0]['ordername'])
 
+    def test_explicit_rectify_success(self):
+        name, _, data = self.create_zone = self.create_zone(api_rectify=False, dnssec=True, nsec3param='1 0 1 ab')
+        dbrecs = get_db_records(name, 'SOA')
+        self.assertIsNone(dbrecs[0]['ordername'])
+        r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/rectify"))
+        self.assertEquals(r.status_code, 200)
+        dbrecs = get_db_records(name, 'SOA')
+        self.assertIsNotNone(dbrecs[0]['ordername'])
+
+    def test_explicit_rectify_slave(self):
+        # Some users want to move a zone to kind=Slave and then rectify, without a re-transfer.
+        name, _, data = self.create_zone = self.create_zone(api_rectify=False, dnssec=True, nsec3param='1 0 1 ab')
+        r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id']),
+            data=json.dumps({'kind': 'Slave'}),
+            headers={'content-type': 'application/json'})
+        self.assertEquals(r.status_code, 204)
+        r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/rectify"))
+        self.assertEquals(r.status_code, 200)
+        dbrecs = get_db_records(name, 'SOA')
+        self.assertIsNotNone(dbrecs[0]['ordername'])
+
     def test_cname_at_ent_place(self):
         name, payload, zone = self.create_zone(dnssec=True, api_rectify=True)
         rrset = {
index 6bb1f686752f6498508cb92d08b7f54294f32e01..14a51f6919ee02ab18c8a4d58c39ec47e9be2c62 100644 (file)
@@ -119,6 +119,8 @@ options {
                 bind_dnssec_db=bind_dnssec_db))
             pdnsconf.write(cls._config_template % params)
 
+        os.system("sqlite3 ./configs/auth/powerdns.sqlite < ../modules/gsqlite3backend/schema.sqlite3.sql")
+
         pdnsutilCmd = [os.environ['PDNSUTIL'],
                        '--config-dir=%s' % confdir,
                        'create-bind-db',
@@ -159,16 +161,15 @@ options {
 
     @classmethod
     def generateAllAuthConfig(cls, confdir):
-        if cls._zones:
-            cls.generateAuthConfig(confdir)
-            cls.generateAuthNamedConf(confdir, cls._zones.keys())
+        cls.generateAuthConfig(confdir)
+        cls.generateAuthNamedConf(confdir, cls._zones.keys())
 
-            for zonename, zonecontent in cls._zones.items():
-                cls.generateAuthZone(confdir,
-                                     zonename,
-                                     zonecontent)
-                if cls._zone_keys.get(zonename, None):
-                    cls.secureZone(confdir, zonename, cls._zone_keys.get(zonename))
+        for zonename, zonecontent in cls._zones.items():
+            cls.generateAuthZone(confdir,
+                                 zonename,
+                                 zonecontent)
+            if cls._zone_keys.get(zonename, None):
+                cls.secureZone(confdir, zonename, cls._zone_keys.get(zonename))
 
     @classmethod
     def startAuth(cls, confdir, ipaddress):
@@ -284,7 +285,7 @@ options {
         if timeout:
             sock.settimeout(timeout)
 
-        sock.connect(("127.0.0.1", cls._recursorPort))
+        sock.connect(("127.0.0.1", cls._authPort))
 
         try:
             wire = query.to_wire()
@@ -308,9 +309,8 @@ options {
             message = dns.message.from_wire(data)
         return message
 
-
     @classmethod
-    def sendTCPQuery(cls, query, timeout=2.0):
+    def sendTCPQueryMultiResponse(cls, query, timeout=2.0, count=1):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         if timeout:
             sock.settimeout(timeout)
@@ -321,23 +321,28 @@ options {
             wire = query.to_wire()
             sock.send(struct.pack("!H", len(wire)))
             sock.send(wire)
-            data = sock.recv(2)
-            if data:
-                (datalen,) = struct.unpack("!H", data)
-                data = sock.recv(datalen)
         except socket.timeout as e:
-            print("Timeout: %s" % (str(e)))
-            data = None
+            raise Exception("Timeout: %s" % (str(e)))
         except socket.error as e:
-            print("Network error: %s" % (str(e)))
-            data = None
-        finally:
-            sock.close()
+            raise Exception("Network error: %s" % (str(e)))
 
-        message = None
-        if data:
-            message = dns.message.from_wire(data)
-        return message
+        messages = []
+        for i in range(count):
+            try:
+                data = sock.recv(2)
+                print("got data", repr(data))
+                if data:
+                    (datalen,) = struct.unpack("!H", data)
+                    data = sock.recv(datalen)
+                    messages.append(dns.message.from_wire(data))
+                else:
+                    break
+            except socket.timeout as e:
+                raise Exception("Timeout: %s" % (str(e)))
+            except socket.error as e:
+                raise Exception("Network error: %s" % (str(e)))
+
+        return messages
 
     def setUp(self):
         # This function is called before every tests
index 2cbba7a7c3005668b87a2213f36a6780af173d2a..27efa323412b0f6077c563c21ec05452789b469c 100644 (file)
@@ -2,3 +2,4 @@ dnspython>=1.11
 nose>=1.3.7
 Twisted>0.15.0
 requests>=2.18.4
+git+https://github.com/PowerDNS/xfrserver.git@0.2
diff --git a/regression-tests.auth-py/test_IXFR.py b/regression-tests.auth-py/test_IXFR.py
new file mode 100644 (file)
index 0000000..0970d92
--- /dev/null
@@ -0,0 +1,244 @@
+import dns
+import os
+import subprocess
+import time
+
+from authtests import AuthTest
+from xfrserver.xfrserver import AXFRServer
+
+zones = {
+    1: ["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 1 2 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+"""],
+    2: ["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 2 2 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+newrecord.example.        8484    A       192.0.2.42
+"""],
+    3: ["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 3 2 3 4 5""","""
+@        86400   SOA    foo bar 2 2 3 4 5""","""
+@        86400   SOA    foo bar 3 2 3 4 5""","""
+@        4242    NS     ns3.example.
+"""],
+    5: ["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 5 2 3 4 5""","""
+@        86400   SOA    foo bar 3 2 3 4 5""","""
+@        86400   SOA    foo bar 4 2 3 4 5""","""
+@        86400   SOA    foo bar 4 2 3 4 5""","""
+@        86400   SOA    foo bar 5 2 3 4 5""","""
+@        4242    NS     ns5.example.
+"""],
+    8: ["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 8 2 3 4 5""","""
+@        86400   SOA    foo bar 5 2 3 4 5""","""
+@        86400   SOA    foo bar 6 2 3 4 5""","""
+@        86400   SOA    foo bar 6 2 3 4 5""","""
+@        86400   SOA    foo bar 7 2 3 4 5""","""
+@        86400   SOA    foo bar 7 2 3 4 5""","""
+@        86400   SOA    foo bar 8 2 3 4 5""","""
+"""]
+
+
+}
+
+
+xfrServerPort = 4244
+xfrServer = AXFRServer(xfrServerPort, zones)
+
+class TestIXFR(AuthTest):
+    _config_template = """
+launch=gsqlite3 bind
+gsqlite3-database=configs/auth/powerdns.sqlite
+gsqlite3-dnssec
+slave
+slave-cycle-interval=1
+query-cache-ttl=20
+negquery-cache-ttl=60
+"""
+
+    _zones = {}
+    global xfrServerPort
+    _xfrDone = 0
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestIXFR, cls).setUpClass()
+        os.system("$PDNSUTIL --config-dir=configs/auth create-slave-zone example. 127.0.0.1:%s" % (xfrServerPort,))
+        os.system("$PDNSUTIL --config-dir=configs/auth set-meta example. IXFR 1")
+
+    def waitUntilCorrectSerialIsLoaded(self, serial, timeout=10):
+        global xfrServer
+
+        xfrServer.moveToSerial(serial)
+
+        attempts = 0
+        while attempts < timeout:
+            print('attempts=%s timeout=%s' % (attempts, timeout))
+            servedSerial = xfrServer.getServedSerial()
+            print('servedSerial=%s' % servedSerial)
+            if servedSerial > serial:
+                raise AssertionError("Expected serial %d, got %d" % (serial, servedSerial))
+            if servedSerial == serial:
+                self._xfrDone = self._xfrDone + 1
+                time.sleep(1)
+                return
+
+            attempts = attempts + 1
+            time.sleep(1)
+
+        raise AssertionError("Waited %d seconds for the serial to be updated to %d but the last served serial is still %d" % (timeout, serial, servedSerial))
+
+    def checkFullZone(self, serial, data=None):
+        global zones
+
+        # FIXME: 90% duplication from _getRecordsForSerial
+        zone = []
+        if not data:
+            data = zones[serial]
+        for i in dns.zone.from_text('\n'.join(data), relativize=False).iterate_rdatasets():
+            n, rds = i
+            rrs=dns.rrset.RRset(n, rds.rdclass, rds.rdtype)
+            rrs.update(rds)
+            zone.append(rrs)
+
+        expected =[[zone[0]], sorted(zone[1:], key=lambda rrset: (rrset.name, rrset.rdtype)), [zone[0]]] # AXFRs are SOA-wrapped
+
+        query = dns.message.make_query('example.', 'AXFR')
+        res = self.sendTCPQueryMultiResponse(query, count=len(expected))
+        answers = [r.answer for r in res]
+        answers[1].sort(key=lambda rrset: (rrset.name, rrset.rdtype))
+        self.assertEqual(answers, expected)
+
+    def checkIXFR(self, fromserial, toserial):
+        global zones, xfrServer
+
+        ixfr = []
+        soa1 = xfrServer._getSOAForSerial(fromserial)
+        soa2 = xfrServer._getSOAForSerial(toserial)
+        newrecord = [r for r in xfrServer._getRecordsForSerial(toserial) if r.name==dns.name.from_text('newrecord.example.')]
+        query = dns.message.make_query('example.', 'IXFR')
+        query.authority = [soa1]
+
+        expected = [[soa2], [soa1], [soa2], newrecord, [soa2]]
+        res = self.sendTCPQueryMultiResponse(query, count=len(expected))
+        answers = [r.answer for r in res]
+
+        # answers[1].sort(key=lambda rrset: (rrset.name, rrset.rdtype))
+        self.assertEqual(answers, expected)
+        # check the TTLs
+        answerPos = 0
+        for expectedAnswer in expected:
+            pos = 0
+            for rec in expectedAnswer:
+                self.assertEquals(rec.ttl, answers[answerPos][pos].ttl)
+                pos = pos + 1
+            answerPos = answerPos + 1
+
+    def test_a_XFR(self):
+        print("x1")
+        self.waitUntilCorrectSerialIsLoaded(1)
+        print("x2")
+        self.checkFullZone(1)
+        print("x3")
+
+        self.waitUntilCorrectSerialIsLoaded(2)
+        print("x4")
+        self.checkFullZone(2)
+        print("x5")
+
+        self.waitUntilCorrectSerialIsLoaded(3)
+        print("x7")
+        self.checkFullZone(3, data=["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 3 2 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+@        4242    NS     ns3.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+newrecord.example.        8484    A       192.0.2.42
+"""])
+        print("x8")
+
+        self.waitUntilCorrectSerialIsLoaded(5)
+        print("x7")
+        self.checkFullZone(5, data=["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 5 2 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+@        4242    NS     ns3.example.
+@        4242    NS     ns5.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+newrecord.example.        8484    A       192.0.2.42
+"""])
+        print("x8")
+
+
+    # _b_ because we expect post-XFR testing state
+    def test_b_UDP_SOA_existing(self):
+        query = dns.message.make_query('example.', 'SOA')
+        expected = dns.message.make_response(query)
+        expected.answer.append(xfrServer._getSOAForSerial(5))
+        expected.flags |= dns.flags.AA
+
+        response = self.sendUDPQuery(query)
+
+        self.assertEquals(expected, response)
+        # check the TTLs
+        pos = 0
+        for rec in expected.answer:
+            self.assertEquals(rec.ttl, response.answer[pos].ttl)
+            pos = pos + 1
+
+    def test_b_UDP_SOA_not_loaded(self):
+        query = dns.message.make_query('example2.', 'SOA')
+        expected = dns.message.make_response(query)
+        expected.set_rcode(dns.rcode.REFUSED)
+
+        response = self.sendUDPQuery(query)
+        self.assertEquals(expected, response)
+
+    def test_b_UDP_SOA_not_configured(self):
+        query = dns.message.make_query('example3.', 'SOA')
+        expected = dns.message.make_response(query)
+        expected.set_rcode(dns.rcode.REFUSED)
+
+        response = self.sendUDPQuery(query)
+        self.assertEquals(expected, response)
+
+    def test_d_XFR(self):
+        self.waitUntilCorrectSerialIsLoaded(8)
+        print("x7")
+        self.checkFullZone(7, data=["""
+$ORIGIN example.""","""
+@        86400   SOA    foo bar 8 2 3 4 5
+@        4242    NS     ns1.example.
+@        4242    NS     ns2.example.
+@        4242    NS     ns3.example.
+@        4242    NS     ns5.example.
+ns1.example.    4242    A       192.0.2.1
+ns2.example.    4242    A       192.0.2.2
+newrecord.example.        8484    A       192.0.2.42
+"""])
+        print("x8")
+        ret = subprocess.check_output([os.environ['PDNSUTIL'],
+                           '--config-dir=configs/auth',
+                           'list-zone', 'example'], stderr=subprocess.STDOUT)
+        rets = ret.split('\n')
+
+        self.assertEqual(1, sum('SOA' in l for l in ret.split('\n')))
\ No newline at end of file
index 6ebf4427b361674252bf782b691712fc12eef87d..d0ba5ed039117b2a348ceac8d1284e4d308b2ccc 100644 (file)
@@ -115,6 +115,10 @@ www-balanced     IN           CNAME 1-1-1-3.17-1-2-4.1-2-3-5.magic.example.org.
 any              IN    LUA    A   "'192.0.2.1'"
 any              IN           TXT "hello there"
 
+resolve          IN    LUA    A   ";local r=resolve('localhost', 1) local t={{}} for _,v in ipairs(r) do table.insert(t, v:toString()) end return t"
+
+*.createforward  IN    LUA    A   "createForward()"
+
         """,
     }
     _web_rrsets = []
@@ -238,7 +242,7 @@ any              IN           TXT "hello there"
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, expected)
 
-        # the first IP should not be up so only second shoud be returned
+        # the first IP should not be up so only second should be returned
         expected = [expected[1]]
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
@@ -593,6 +597,57 @@ any              IN           TXT "hello there"
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertEqual(self.sortRRsets(res.answer), self.sortRRsets(response.answer))
 
+    def testResolve(self):
+        """
+        Test resolve() function
+        """
+        name = 'resolve.example.org.'
+
+        query = dns.message.make_query(name, 'A')
+
+        response = dns.message.make_response(query)
+
+        response.answer.append(dns.rrset.from_text(name, 0, dns.rdataclass.IN, dns.rdatatype.A, '127.0.0.1'))
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEqual(res.answer, response.answer)
+
+    def testCreateForward(self):
+        name_suffix = '.createforward.example.org.'
+
+        name_expected = {
+            "1.2.3.4": "1.2.3.4",
+            "1.2.3.4.static": "1.2.3.4",
+            "1.2.3.4.5.6": "1.2.3.4",
+            "invalid.1.2.3.4": "0.0.0.0",
+            "invalid": "0.0.0.0",
+            "1-2-3-4": "1.2.3.4",
+            "1-2-3-4.foo": "1.2.3.4",
+            "1-2-3-4.foo.bar": "0.0.0.0",
+            "1-2-3-4.foo.bar.baz": "0.0.0.0",
+            "1-2-3-4.foo.bar.baz.quux": "0.0.0.0",
+            "ip-1-2-3-4": "1.2.3.4",
+            "ip-is-here-for-you-1-2-3-4": "1.2.3.4",
+            "ip40414243": "64.65.66.67",
+            "ipp40414243": "0.0.0.0",
+            "ip4041424": "0.0.0.0",
+        }
+
+        for k, v in name_expected.items():
+            name = k + name_suffix
+
+            query = dns.message.make_query(name, 'A')
+            response = dns.message.make_response(query)
+            response.answer.append(dns.rrset.from_text(
+                name, 0, dns.rdataclass.IN, dns.rdatatype.A, v))
+
+            res = self.sendUDPQuery(query)
+            print(res)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertEqual(res.answer, response.answer)
+
+
 if __name__ == '__main__':
     unittest.main()
     exit(0)
diff --git a/regression-tests.common/proxyprotocol.py b/regression-tests.common/proxyprotocol.py
new file mode 100644 (file)
index 0000000..0677b0d
--- /dev/null
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+
+import copy
+import socket
+import struct
+
+class ProxyProtocol(object):
+    MAGIC = b'\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A'
+    # Header is magic + versioncommand (1) + family (1) + content length (2)
+    HEADER_SIZE = len(MAGIC) + 1 + 1 + 2
+    PORT_SIZE = 2
+
+    def consumed(self):
+        return self.offset
+
+    def parseHeader(self, data):
+        if len(data) < self.HEADER_SIZE:
+            return False
+
+        if data[:len(self.MAGIC)] != self.MAGIC:
+            return False
+
+        value = struct.unpack('!B', bytes(bytearray([data[12]])))[0]
+        self.version = value >> 4
+        if self.version != 0x02:
+            return False
+
+        self.command = value & ~0x20
+        self.local = False
+        self.offset = self.HEADER_SIZE
+
+        if self.command == 0x00:
+            self.local = True
+        elif self.command == 0x01:
+            value = struct.unpack('!B', bytes(bytearray([data[13]])))[0]
+            self.family = value >> 4
+            if self.family == 0x01:
+                self.addrSize = 4
+            elif self.family == 0x02:
+                self.addrSize = 16
+            else:
+                return False
+
+            self.protocol = value & ~0xF0
+            if self.protocol == 0x01:
+                self.tcp = True
+            elif self.protocol == 0x02:
+                self.tcp = False
+            else:
+                return False
+        else:
+            return False
+
+        self.contentLen = struct.unpack("!H", data[14:16])[0]
+
+        if not self.local:
+            if self.contentLen < (self.addrSize * 2 + self.PORT_SIZE * 2):
+                return False
+
+        return True
+
+    def getAddr(self, data):
+        if len(data) < (self.consumed() + self.addrSize):
+            return False
+
+        value = None
+        if self.family == 0x01:
+            value = socket.inet_ntop(socket.AF_INET, data[self.offset:self.offset + self.addrSize])
+        else:
+            value = socket.inet_ntop(socket.AF_INET6, data[self.offset:self.offset + self.addrSize])
+
+        self.offset = self.offset + self.addrSize
+        return value
+
+    def getPort(self, data):
+        if len(data) < (self.consumed() + self.PORT_SIZE):
+            return False
+
+        value = struct.unpack('!H', data[self.offset:self.offset + self.PORT_SIZE])[0]
+        self.offset = self.offset + self.PORT_SIZE
+        return value
+
+    def parseAddressesAndPorts(self, data):
+        if self.local:
+            return True
+
+        if len(data) < (self.consumed() + self.addrSize * 2 + self.PORT_SIZE * 2):
+            return False
+
+        self.source = self.getAddr(data)
+        self.destination = self.getAddr(data)
+        self.sourcePort = self.getPort(data)
+        self.destinationPort = self.getPort(data)
+        return True
+
+    def parseAdditionalValues(self, data):
+        self.values = []
+        if self.local:
+            return True
+
+        if len(data) < (self.HEADER_SIZE + self.contentLen):
+            return False
+
+        remaining = self.HEADER_SIZE + self.contentLen - self.consumed()
+        if len(data) < remaining:
+            return False
+
+        while remaining >= 3:
+            valueType = struct.unpack("!B", bytes(bytearray([data[self.offset]])))[0]
+            self.offset = self.offset + 1
+            valueLen = struct.unpack("!H", data[self.offset:self.offset+2])[0]
+            self.offset = self.offset + 2
+
+            remaining = remaining - 3
+            if valueLen > 0:
+                if valueLen > remaining:
+                    return False
+                self.values.append([valueType, data[self.offset:self.offset+valueLen]])
+                self.offset = self.offset + valueLen
+                remaining = remaining - valueLen
+
+            else:
+                self.values.append([valueType, ""])
+
+        return True
+
+    @classmethod
+    def getPayload(cls, local, tcp, v6, source, destination, sourcePort, destinationPort, values):
+        payload = copy.deepcopy(cls.MAGIC)
+        version = 0x02
+
+        if local:
+            command = 0x00
+        else:
+            command = 0x01
+
+        value = struct.pack('!B', (version << 4) + command)
+        payload = payload + value
+
+        addrSize = 0
+        family = 0x00
+        protocol = 0x00
+        if not local:
+            if tcp:
+                protocol = 0x01
+            else:
+                protocol = 0x02
+            # sorry but compatibility with python 2 is awful for this,
+            # not going to waste time on it
+            if not v6:
+                family = 0x01
+                addrSize = 4
+            else:
+                family = 0x02
+                addrSize = 16
+
+        value = struct.pack('!B', (family << 4)  + protocol)
+        payload = payload + value
+
+        contentSize = 0
+        if not local:
+            contentSize = contentSize + addrSize * 2 + cls.PORT_SIZE *2
+
+        valuesSize = 0
+        for value in values:
+            valuesSize = valuesSize + 3 + len(value[1])
+
+        contentSize = contentSize + valuesSize
+
+        value = struct.pack('!H', contentSize)
+        payload = payload +  value
+
+        if not local:
+            if family == 0x01:
+                af = socket.AF_INET
+            else:
+                af = socket.AF_INET6
+
+            value = socket.inet_pton(af, source)
+            payload = payload + value
+            value = socket.inet_pton(af, destination)
+            payload = payload + value
+            value = struct.pack('!H', sourcePort)
+            payload = payload + value
+            value = struct.pack('!H', destinationPort)
+            payload = payload + value
+
+        for value in values:
+            valueType = struct.pack('!B', value[0])
+            valueLen = struct.pack('!H', len(value[1]))
+            payload = payload + valueType + valueLen + value[1]
+
+        return payload
index 8765ea58445658ea0ac95288b08f19d6933c98f8..773d5732ffa486e62c757f2e8116d8642fd1fb48 100644 (file)
@@ -58,6 +58,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     _healthCheckCounter = 0
     _answerUnexpected = True
     _checkConfigExpectedOutput = None
+    _verboseMode = False
 
     @classmethod
     def startResponders(cls):
@@ -82,6 +83,9 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
         dnsdistcmd = [os.environ['DNSDISTBIN'], '--supervised', '-C', confFile,
                       '-l', '%s:%d' % (cls._dnsDistListeningAddr, cls._dnsDistPort) ]
+        if cls._verboseMode:
+            dnsdistcmd.append('-v')
+
         for acl in cls._acl:
             dnsdistcmd.extend(['--acl', acl])
         print(' '.join(dnsdistcmd))
@@ -96,7 +100,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
           expectedOutput = cls._checkConfigExpectedOutput
         else:
           expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
-        if output != expectedOutput:
+        if not cls._verboseMode and output != expectedOutput:
             raise AssertionError('dnsdist --check-config failed: %s' % output)
 
         logFile = os.path.join('configs', 'dnsdist_%s.log' % (cls.__name__))
diff --git a/regression-tests.dnsdist/proxyprotocol.py b/regression-tests.dnsdist/proxyprotocol.py
new file mode 120000 (symlink)
index 0000000..2a3d79b
--- /dev/null
@@ -0,0 +1 @@
+../regression-tests.common/proxyprotocol.py
\ No newline at end of file
index 0928c28c168a489262848d00376cf0283c571c3e..b894323cb4cd59d71272ae91beb0278a455aceb9 100644 (file)
@@ -130,10 +130,10 @@ class TestAPIBasics(DNSDistTest):
 
     def testServersIDontExist(self):
         """
-        API: /api/v1/servers/idontexist (should be 404)
+        API: /api/v1/servers/idonotexist (should be 404)
         """
         headers = {'x-api-key': self._webServerAPIKey}
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/idontexist'
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/idonotexist'
         r = requests.get(url, headers=headers, timeout=self._webTimeout)
         self.assertEquals(r.status_code, 404)
 
@@ -236,7 +236,8 @@ class TestAPIBasics(DNSDistTest):
                     'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
                     'cache-misses', 'cpu-iowait', 'cpu-steal', 'cpu-sys-msec', 'cpu-user-msec', 'fd-usage', 'dyn-blocked',
                     'dyn-block-nmg-size', 'rule-servfail', 'security-status',
-                    'udp-in-errors', 'udp-noport-errors', 'udp-recvbuf-errors', 'udp-sndbuf-errors']
+                    'udp-in-errors', 'udp-noport-errors', 'udp-recvbuf-errors', 'udp-sndbuf-errors',
+                    'doh-query-pipe-full', 'doh-response-pipe-full']
 
         for key in expected:
             self.assertIn(key, values)
@@ -516,3 +517,39 @@ class TestAPIAuth(DNSDistTest):
 
         r = requests.get(url, headers=headers, timeout=self._webTimeout)
         self.assertEquals(r.status_code, 401)
+
+class TestAPIACL(DNSDistTest):
+
+    _webTimeout = 2.0
+    _webServerPort = 8083
+    _webServerBasicAuthPassword = 'secret'
+    _webServerAPIKey = 'apisecret'
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+    setACL({"127.0.0.1/32", "::1/128"})
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s", "%s", "%s", {}, "192.0.2.1")
+    """
+
+    def testACLChange(self):
+        """
+        API: Should be denied by ACL then allowed
+        """
+
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + "/"
+        try:
+            r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
+            self.assertTrue(False)
+        except requests.exceptions.ConnectionError as exp:
+            pass
+
+        # reset the ACL
+        self.sendConsoleCommand('setWebserverConfig({acl="127.0.0.1"})')
+
+        r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEquals(r.status_code, 200)
index 4ecb637cb8de9a40b232fef25e0b8939ad22ffc3..d3f91753c2b5dc89ba7dd7cbd3a421b9e3c877b0 100644 (file)
@@ -3,6 +3,7 @@ import base64
 import time
 import dns
 import clientsubnetoption
+import cookiesoption
 from dnsdisttests import DNSDistTest
 
 class TestCaching(DNSDistTest):
@@ -468,6 +469,310 @@ class TestCaching(DNSDistTest):
 
         self.assertEquals(total, 1)
 
+    def testCacheDifferentCookies(self):
+        """
+        Cache: The content of cookies should be ignored by the cache
+        """
+        ttl = 600
+        name = 'cache-different-cookies.cache.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+        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)
+
+        eco = cookiesoption.CookiesOption(b'badc0fee', b'badc0fee')
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+        # second query should be served from the cache
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        receivedResponse.id = response.id
+        self.assertEquals(receivedResponse, response)
+
+    def testCacheCookies(self):
+        """
+        Cache: A query with a cookie should not match one without any cookie
+        """
+        ttl = 600
+        name = 'cache-cookie.cache.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.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)
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+        # second query should NOT be served from 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)
+
+    def testCacheSameCookieDifferentECS(self):
+        """
+        Cache: The content of cookies should be ignored by the cache but not the ECS one
+        """
+        ttl = 600
+        name = 'cache-different-cookies-different-ecs.cache.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+class TestCachingHashingCookies(DNSDistTest):
+
+    _config_template = """
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1, cookieHashing=true})
+    getPool(""):setCache(pc)
+    newServer{address="127.0.0.1:%d"}
+    """
+
+    def testCached(self):
+        """
+        Cache: Served from cache
+
+        dnsdist is configured to cache entries, we are sending several
+        identical requests and checking that the backend only receive
+        the first one.
+        """
+        numberOfQueries = 10
+        name = 'cached.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    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)
+
+        for _ in range(numberOfQueries):
+            (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+            self.assertEquals(receivedResponse, response)
+
+        total = 0
+        for key in self._responsesCounter:
+            total += self._responsesCounter[key]
+            TestCaching._responsesCounter[key] = 0
+
+        self.assertEquals(total, 1)
+
+        # TCP should not be cached
+        # first query to fill the cache
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+        for _ in range(numberOfQueries):
+            (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+            self.assertEquals(receivedResponse, response)
+
+        total = 0
+        for key in self._responsesCounter:
+            total += self._responsesCounter[key]
+            TestCaching._responsesCounter[key] = 0
+
+        self.assertEquals(total, 1)
+
+
+    def testCacheDifferentCookies(self):
+        """
+        Cache: The content of cookies should NOT be ignored by the cache (cookieHashing is set)
+        """
+        ttl = 600
+        name = 'cache-different-cookies.cache-cookie-hashing.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+        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)
+
+        eco = cookiesoption.CookiesOption(b'badc0fee', b'badc0fee')
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco])
+        differentResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        differentResponse.answer.append(rrset)
+        # second query should NOT be served from the cache
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, differentResponse)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, differentResponse)
+        self.assertNotEquals(receivedResponse, response)
+
+    def testCacheCookies(self):
+        """
+        Cache: A query with a cookie should not match one without any cookie (cookieHashing=true)
+        """
+        ttl = 600
+        name = 'cache-cookie.cache-cookie-hashing.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[eco])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.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)
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+        # second query should NOT be served from 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)
+
+    def testCacheSameCookieDifferentECS(self):
+        """
+        Cache: The content of cookies should NOT be ignored by the cache (cookieHashing=true), even with ECS there
+        """
+        ttl = 600
+        name = 'cache-different-cookies-different-ecs.cache-cookie-hashing.tests.powerdns.com.'
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
+        query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, payload=4096, options=[eco,ecso])
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    ttl,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '::1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(receivedResponse, response)
+
 class TestTempFailureCacheTTLAction(DNSDistTest):
 
     _config_template = """
@@ -1668,7 +1973,7 @@ class TestCachingNegativeTTL(DNSDistTest):
 
         time.sleep(self._negCacheTTL + 1)
 
-        # we should not have cached for longer than the negativel TTL
+        # we should not have cached for longer than the negative TTL
         # so it should be a miss
         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
         self.assertTrue(receivedQuery)
@@ -1863,7 +2168,7 @@ class TestCachingCollisionNoECSParsing(DNSDistTest):
         Cache: Collision with no ECS parsing
         """
         name = 'collision-no-ecs-parsing.cache.tests.powerdns.com.'
-        ecso = clientsubnetoption.ClientSubnetOption('10.0.188.3', 32)
+        ecso = clientsubnetoption.ClientSubnetOption('10.0.226.63', 32)
         query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso], payload=512)
         query.flags = dns.flags.RD
         response = dns.message.make_response(query)
@@ -1885,7 +2190,7 @@ class TestCachingCollisionNoECSParsing(DNSDistTest):
         # second query will hash to the same key, triggering a collision which
         # will not be detected because the qname, qtype, qclass and flags will
         # match and EDNS Client Subnet parsing has not been enabled
-        ecso2 = clientsubnetoption.ClientSubnetOption('10.0.192.138', 32)
+        ecso2 = clientsubnetoption.ClientSubnetOption('10.1.60.19', 32)
         query2 = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso2], payload=512)
         query2.flags = dns.flags.RD
         (_, receivedResponse) = self.sendUDPQuery(query2, response=None, useQueue=False)
@@ -1905,7 +2210,7 @@ class TestCachingCollisionWithECSParsing(DNSDistTest):
         Cache: Collision with ECS parsing
         """
         name = 'collision-with-ecs-parsing.cache.tests.powerdns.com.'
-        ecso = clientsubnetoption.ClientSubnetOption('10.0.115.61', 32)
+        ecso = clientsubnetoption.ClientSubnetOption('10.0.150.206', 32)
         query = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso], payload=512)
         query.flags = dns.flags.RD
         response = dns.message.make_response(query)
@@ -1927,7 +2232,7 @@ class TestCachingCollisionWithECSParsing(DNSDistTest):
         # second query will hash to the same key, triggering a collision which
         # _will_ be detected this time because the qname, qtype, qclass and flags will
         # match but EDNS Client Subnet parsing is now enabled and will detect the issue
-        ecso2 = clientsubnetoption.ClientSubnetOption('10.0.143.21', 32)
+        ecso2 = clientsubnetoption.ClientSubnetOption('10.0.212.51', 32)
         query2 = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso2], payload=512)
         query2.flags = dns.flags.RD
         response2 = dns.message.make_response(query2)
index f7d2f4f3c8fa3e7cdce4f2dda88b4816c9aa9a55..69e37a9f8678b62a0a98113add8fd4590a12e545 100644 (file)
@@ -950,3 +950,103 @@ class TestDOHFFI(DNSDistDOHTest):
         self.assertEquals(self._rcode, 200)
         self.assertTrue('content-type: text/plain' in self._response_headers.decode())
 
+class TestDOHForwardedFor(DNSDistDOHTest):
+
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _dohServerPort = 8443
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+
+    setACL('192.0.2.1/32')
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true})
+    """
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+
+    def testDOHAllowedForwarded(self):
+        """
+        DOH with X-Forwarded-For allowed
+        """
+        name = 'allowed.forwarded.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        expectedQuery.id = 0
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, customHeaders=['x-forwarded-for: 127.0.0.1:42, 127.0.0.1, 192.0.2.1:4200'])
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        self.assertEquals(expectedQuery, receivedQuery)
+        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+    def testDOHDeniedForwarded(self):
+        """
+        DOH with X-Forwarded-For not allowed
+        """
+        name = 'not-allowed.forwarded.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        expectedQuery.id = 0
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=False, rawResponse=True, customHeaders=['x-forwarded-for: 127.0.0.1:42, 127.0.0.1'])
+
+        self.assertEquals(self._rcode, 403)
+        self.assertEquals(receivedResponse, b'dns query not allowed because of ACL')
+
+class TestDOHForwardedForNoTrusted(DNSDistDOHTest):
+
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _dohServerPort = 8443
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+
+    setACL('192.0.2.1/32')
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    """
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+
+    def testDOHForwardedUntrusted(self):
+        """
+        DOH with X-Forwarded-For not trusted
+        """
+        name = 'not-trusted.forwarded.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        expectedQuery.id = 0
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=False, rawResponse=True, customHeaders=['x-forwarded-for: 192.0.2.1:4200'])
+
+        self.assertEquals(self._rcode, 403)
+        self.assertEquals(receivedResponse, b'dns query not allowed because of ACL')
diff --git a/regression-tests.dnsdist/test_ProxyProtocol.py b/regression-tests.dnsdist/test_ProxyProtocol.py
new file mode 100644 (file)
index 0000000..ab6c1a2
--- /dev/null
@@ -0,0 +1,408 @@
+#!/usr/bin/env python
+
+import dns
+import socket
+import struct
+import sys
+import threading
+
+from dnsdisttests import DNSDistTest
+from proxyprotocol import ProxyProtocol
+
+# Python2/3 compatibility hacks
+try:
+  from queue import Queue
+except ImportError:
+  from Queue import Queue
+
+def ProxyProtocolUDPResponder(port, fromQueue, toQueue):
+    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+    try:
+        sock.bind(("127.0.0.1", port))
+    except socket.error as e:
+        print("Error binding in the Proxy Protocol UDP responder: %s" % str(e))
+        sys.exit(1)
+
+    while True:
+        data, addr = sock.recvfrom(4096)
+
+        proxy = ProxyProtocol()
+        if len(data) < proxy.HEADER_SIZE:
+            continue
+
+        if not proxy.parseHeader(data):
+            continue
+
+        if proxy.local:
+            # likely a healthcheck
+            data = data[proxy.HEADER_SIZE:]
+            request = dns.message.from_wire(data)
+            response = dns.message.make_response(request)
+            wire = response.to_wire()
+            sock.settimeout(2.0)
+            sock.sendto(wire, addr)
+            sock.settimeout(None)
+
+            continue
+
+        payload = data[:(proxy.HEADER_SIZE + proxy.contentLen)]
+        dnsData = data[(proxy.HEADER_SIZE + proxy.contentLen):]
+        toQueue.put([payload, dnsData], True, 2.0)
+        # computing the correct ID for the response
+        request = dns.message.from_wire(dnsData)
+        response = fromQueue.get(True, 2.0)
+        response.id = request.id
+
+        sock.settimeout(2.0)
+        sock.sendto(response.to_wire(), addr)
+        sock.settimeout(None)
+
+    sock.close()
+
+def ProxyProtocolTCPResponder(port, fromQueue, toQueue):
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+    try:
+        sock.bind(("127.0.0.1", port))
+    except socket.error as e:
+        print("Error binding in the TCP responder: %s" % str(e))
+        sys.exit(1)
+
+    sock.listen(100)
+    while True:
+        (conn, _) = sock.accept()
+        conn.settimeout(5.0)
+        # try to read the entire Proxy Protocol header
+        proxy = ProxyProtocol()
+        header = conn.recv(proxy.HEADER_SIZE)
+        if not header:
+            conn.close()
+            continue
+
+        if not proxy.parseHeader(header):
+            conn.close()
+            continue
+
+        proxyContent = conn.recv(proxy.contentLen)
+        if not proxyContent:
+            conn.close()
+            continue
+
+        payload = header + proxyContent
+        while True:
+          try:
+            data = conn.recv(2)
+          except socket.timeout:
+            data = None
+
+          if not data:
+            conn.close()
+            break
+
+          (datalen,) = struct.unpack("!H", data)
+          data = conn.recv(datalen)
+
+          toQueue.put([payload, data], True, 2.0)
+
+          response = fromQueue.get(True, 2.0)
+          if not response:
+            conn.close()
+            break
+
+          # computing the correct ID for the response
+          request = dns.message.from_wire(data)
+          response.id = request.id
+
+          wire = response.to_wire()
+          conn.send(struct.pack("!H", len(wire)))
+          conn.send(wire)
+
+        conn.close()
+
+    sock.close()
+
+toProxyQueue = Queue()
+fromProxyQueue = Queue()
+proxyResponderPort = 5470
+
+udpResponder = threading.Thread(name='UDP Proxy Protocol Responder', target=ProxyProtocolUDPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+udpResponder.setDaemon(True)
+udpResponder.start()
+tcpResponder = threading.Thread(name='TCP Proxy Protocol Responder', target=ProxyProtocolTCPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+tcpResponder.setDaemon(True)
+tcpResponder.start()
+
+class ProxyProtocolTest(DNSDistTest):
+    _proxyResponderPort = proxyResponderPort
+    _config_params = ['_proxyResponderPort']
+
+    def checkMessageProxyProtocol(self, receivedProxyPayload, source, destination, isTCP, values=[]):
+      proxy = ProxyProtocol()
+      self.assertTrue(proxy.parseHeader(receivedProxyPayload))
+      self.assertEquals(proxy.version, 0x02)
+      self.assertEquals(proxy.command, 0x01)
+      self.assertEquals(proxy.family, 0x01)
+      if not isTCP:
+        self.assertEquals(proxy.protocol, 0x02)
+      else:
+        self.assertEquals(proxy.protocol, 0x01)
+      self.assertGreater(proxy.contentLen, 0)
+
+      self.assertTrue(proxy.parseAddressesAndPorts(receivedProxyPayload))
+      self.assertEquals(proxy.source, source)
+      self.assertEquals(proxy.destination, destination)
+      #self.assertEquals(proxy.sourcePort, sourcePort)
+      self.assertEquals(proxy.destinationPort, self._dnsDistPort)
+
+      self.assertTrue(proxy.parseAdditionalValues(receivedProxyPayload))
+      proxy.values.sort()
+      values.sort()
+      self.assertEquals(proxy.values, values)
+
+class TestProxyProtocol(ProxyProtocolTest):
+    """
+    dnsdist is configured to prepend a Proxy Protocol header to the query
+    """
+
+    _config_template = """
+    newServer{address="127.0.0.1:%d", useProxyProtocol=true}
+
+    function addValues(dq)
+      local values = { [0]="foo", [42]="bar" }
+      dq:setProxyProtocolValues(values)
+      return DNSAction.None
+    end
+
+    addAction("values-lua.proxy.tests.powerdns.com.", LuaAction(addValues))
+    addAction("values-action.proxy.tests.powerdns.com.", SetProxyProtocolValuesAction({ ["1"]="dnsdist", ["255"]="proxy-protocol"}))
+    """
+    _config_params = ['_proxyResponderPort']
+
+    def testProxyUDP(self):
+        """
+        Proxy Protocol: no value (UDP)
+        """
+        name = 'simple-udp.proxy.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        toProxyQueue.put(response, True, 2.0)
+
+        data = query.to_wire()
+        self._sock.send(data)
+        receivedResponse = None
+        try:
+            self._sock.settimeout(2.0)
+            data = self._sock.recv(4096)
+        except socket.timeout:
+            print('timeout')
+            data = None
+        if data:
+            receivedResponse = dns.message.from_wire(data)
+
+        (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+        self.assertTrue(receivedProxyPayload)
+        self.assertTrue(receivedDNSData)
+        self.assertTrue(receivedResponse)
+
+        receivedQuery = dns.message.from_wire(receivedDNSData)
+        receivedQuery.id = query.id
+        receivedResponse.id = response.id
+        self.assertEquals(receivedQuery, query)
+        self.assertEquals(receivedResponse, response)
+        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False)
+
+    def testProxyTCP(self):
+      """
+        Proxy Protocol: no value (TCP)
+      """
+      name = 'simple-tcp.proxy.tests.powerdns.com.'
+      query = dns.message.make_query(name, 'A', 'IN')
+      response = dns.message.make_response(query)
+
+      toProxyQueue.put(response, True, 2.0)
+
+      conn = self.openTCPConnection(2.0)
+      data = query.to_wire()
+      self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+      receivedResponse = None
+      try:
+        receivedResponse = self.recvTCPResponseOverConnection(conn)
+      except socket.timeout:
+            print('timeout')
+
+      (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+      self.assertTrue(receivedProxyPayload)
+      self.assertTrue(receivedDNSData)
+      self.assertTrue(receivedResponse)
+
+      receivedQuery = dns.message.from_wire(receivedDNSData)
+      receivedQuery.id = query.id
+      receivedResponse.id = response.id
+      self.assertEquals(receivedQuery, query)
+      self.assertEquals(receivedResponse, response)
+      self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True)
+
+    def testProxyUDPWithValuesFromLua(self):
+        """
+        Proxy Protocol: values from Lua (UDP)
+        """
+        name = 'values-lua.proxy.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        toProxyQueue.put(response, True, 2.0)
+
+        data = query.to_wire()
+        self._sock.send(data)
+        receivedResponse = None
+        try:
+            self._sock.settimeout(2.0)
+            data = self._sock.recv(4096)
+        except socket.timeout:
+            print('timeout')
+            data = None
+        if data:
+            receivedResponse = dns.message.from_wire(data)
+
+        (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+        self.assertTrue(receivedProxyPayload)
+        self.assertTrue(receivedDNSData)
+        self.assertTrue(receivedResponse)
+
+        receivedQuery = dns.message.from_wire(receivedDNSData)
+        receivedQuery.id = query.id
+        receivedResponse.id = response.id
+        self.assertEquals(receivedQuery, query)
+        self.assertEquals(receivedResponse, response)
+        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False, [ [0, b'foo'] , [ 42, b'bar'] ])
+
+    def testProxyTCPWithValuesFromLua(self):
+      """
+        Proxy Protocol: values from Lua (TCP)
+      """
+      name = 'values-lua.proxy.tests.powerdns.com.'
+      query = dns.message.make_query(name, 'A', 'IN')
+      response = dns.message.make_response(query)
+
+      toProxyQueue.put(response, True, 2.0)
+
+      conn = self.openTCPConnection(2.0)
+      data = query.to_wire()
+      self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+      receivedResponse = None
+      try:
+        receivedResponse = self.recvTCPResponseOverConnection(conn)
+      except socket.timeout:
+            print('timeout')
+
+      (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+      self.assertTrue(receivedProxyPayload)
+      self.assertTrue(receivedDNSData)
+      self.assertTrue(receivedResponse)
+
+      receivedQuery = dns.message.from_wire(receivedDNSData)
+      receivedQuery.id = query.id
+      receivedResponse.id = response.id
+      self.assertEquals(receivedQuery, query)
+      self.assertEquals(receivedResponse, response)
+      self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'] , [ 42, b'bar'] ])
+
+    def testProxyUDPWithValuesFromAction(self):
+        """
+        Proxy Protocol: values from Action (UDP)
+        """
+        name = 'values-action.proxy.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        toProxyQueue.put(response, True, 2.0)
+
+        data = query.to_wire()
+        self._sock.send(data)
+        receivedResponse = None
+        try:
+            self._sock.settimeout(2.0)
+            data = self._sock.recv(4096)
+        except socket.timeout:
+            print('timeout')
+            data = None
+        if data:
+            receivedResponse = dns.message.from_wire(data)
+
+        (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+        self.assertTrue(receivedProxyPayload)
+        self.assertTrue(receivedDNSData)
+        self.assertTrue(receivedResponse)
+
+        receivedQuery = dns.message.from_wire(receivedDNSData)
+        receivedQuery.id = query.id
+        receivedResponse.id = response.id
+        self.assertEquals(receivedQuery, query)
+        self.assertEquals(receivedResponse, response)
+        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False, [ [1, b'dnsdist'] , [ 255, b'proxy-protocol'] ])
+
+    def testProxyTCPWithValuesFromAction(self):
+      """
+        Proxy Protocol: values from Action (TCP)
+      """
+      name = 'values-action.proxy.tests.powerdns.com.'
+      query = dns.message.make_query(name, 'A', 'IN')
+      response = dns.message.make_response(query)
+
+      toProxyQueue.put(response, True, 2.0)
+
+      conn = self.openTCPConnection(2.0)
+      data = query.to_wire()
+      self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+      receivedResponse = None
+      try:
+        receivedResponse = self.recvTCPResponseOverConnection(conn)
+      except socket.timeout:
+            print('timeout')
+
+      (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+      self.assertTrue(receivedProxyPayload)
+      self.assertTrue(receivedDNSData)
+      self.assertTrue(receivedResponse)
+
+      receivedQuery = dns.message.from_wire(receivedDNSData)
+      receivedQuery.id = query.id
+      receivedResponse.id = response.id
+      self.assertEquals(receivedQuery, query)
+      self.assertEquals(receivedResponse, response)
+      self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [1, b'dnsdist'] , [ 255, b'proxy-protocol'] ])
+
+    def testProxyTCPSeveralQueriesOnSameConnection(self):
+      """
+        Proxy Protocol: Several queries on the same TCP connection
+      """
+      name = 'several-queries-same-conn.proxy.tests.powerdns.com.'
+      query = dns.message.make_query(name, 'A', 'IN')
+      response = dns.message.make_response(query)
+
+      conn = self.openTCPConnection(2.0)
+      data = query.to_wire()
+
+      for idx in range(10):
+        toProxyQueue.put(response, True, 2.0)
+        self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+        receivedResponse = None
+        try:
+          receivedResponse = self.recvTCPResponseOverConnection(conn)
+        except socket.timeout:
+          print('timeout')
+
+        (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+        self.assertTrue(receivedProxyPayload)
+        self.assertTrue(receivedDNSData)
+        self.assertTrue(receivedResponse)
+
+        receivedQuery = dns.message.from_wire(receivedDNSData)
+        receivedQuery.id = query.id
+        receivedResponse.id = response.id
+        self.assertEquals(receivedQuery, query)
+        self.assertEquals(receivedResponse, response)
+        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [])
index 70deba8f3c43e2d377165749baccf730a6a65802..8aabad85fa3c32d79b462a6f95700b976ac3023a 100644 (file)
@@ -253,6 +253,82 @@ class TestRoutingRoundRobinLBAllDown(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertEquals(receivedResponse, None)
 
+class TestRoutingLuaFFIPerThreadRoundRobinLB(DNSDistTest):
+
+    _testServer2Port = 5351
+    _config_params = ['_testServerPort', '_testServer2Port']
+    _config_template = """
+    setServerPolicyLuaFFIPerThread("luaffiroundrobin", [[
+      local ffi = require("ffi")
+      local C = ffi.C
+
+      local counter = 0
+      return function(servers_list, dq)
+        counter = counter + 1
+        return (counter %% tonumber(C.dnsdist_ffi_servers_list_get_count(servers_list)))
+      end
+    ]])
+
+    s1 = newServer{address="127.0.0.1:%s"}
+    s1:setUp()
+    s2 = newServer{address="127.0.0.1:%s"}
+    s2:setUp()
+    """
+
+    @classmethod
+    def startResponders(cls):
+        print("Launching responders..")
+        cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.start()
+        cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._UDPResponder2.setDaemon(True)
+        cls._UDPResponder2.start()
+
+        cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.start()
+
+        cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._TCPResponder2.setDaemon(True)
+        cls._TCPResponder2.start()
+
+    def testRR(self):
+        """
+        Routing: Round Robin
+
+        Send 10 A queries to "rr.routing.tests.powerdns.com.",
+        check that dnsdist routes half of it to each backend.
+        """
+        numberOfQueries = 10
+        name = 'rr.routing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        # the round robin counter is shared for UDP and TCP,
+        # so we need to do UDP then TCP to have a clean count
+        for _ in range(numberOfQueries):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(response, receivedResponse)
+
+        for _ in range(numberOfQueries):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(response, receivedResponse)
+
+        for key in self._responsesCounter:
+            value = self._responsesCounter[key]
+            self.assertEquals(value, numberOfQueries / 2)
+
 class TestRoutingOrder(DNSDistTest):
 
     _testServer2Port = 5351
index ec20c929f5966591f857c55e9e0d9a2e52730f37..17bc9a5fb1094ee4b829ecb3c895b6bac00ca2d5 100644 (file)
@@ -28,6 +28,7 @@ class TestTCPLimits(DNSDistTest):
     setMaxTCPConnectionDuration(%s)
     """
     _config_params = ['_testServerPort', '_tcpIdleTimeout', '_maxTCPQueriesPerConn', '_maxTCPConnsPerClient', '_maxTCPConnDuration']
+    _verboseMode = True
 
     def testTCPQueriesPerConn(self):
         """
@@ -91,6 +92,11 @@ class TestTCPLimits(DNSDistTest):
         for conn in conns:
             conn.close()
 
+        # wait a bit to be sure that dnsdist closed the connections
+        # and decremented the counters on its side, otherwise subsequent
+        # connections will be dropped
+        time.sleep(1)
+
         self.assertEqual(count, self._maxTCPConnsPerClient)
         self.assertEqual(failed, 1)
 
index 917a0bc9293df287defa39d215a65ff149536bd0..bb6d17232750f3e4a9e4f2cadabafe45b27d0b15 100644 (file)
@@ -10,7 +10,7 @@ dnsupdate-queries=0
 dnsupdate-refused=0
 incoming-notifications=0
 key-cache-size=0
-meta-cache-size=3
+meta-cache-size=1
 open-tcp-connections=0
 overload-drops=0
 packetcache-size=4
index 0ec4367daeaabddc181d338f5a7440bf44ae86db..70ebd4a567081556bd1bf62c7fe8236f5563d858 100644 (file)
@@ -14,4 +14,5 @@ b1f775045fa2cf0a3b91aa834af06e49  ../regression-tests/zones/stest.com
 a98864b315f16bcf49ce577426063c42  ../regression-tests/zones/cdnskey-cds-test.com
 9aeed2c26d0c3ba3baf22dfa9568c451  ../regression-tests/zones/2.0.192.in-addr.arpa
 99c73e8b5db5781fec1ac3fa6a2662a9  ../regression-tests/zones/cryptokeys.org
+1f9e19be0cff67330f3a0a5347654f91  ../regression-tests/zones/hiddencryptokeys.org
 52a95993ada0b4ed986a2fe6463a27e0  ../modules/tinydnsbackend/data.cdb
index 04fc82308914709df473c0df294e59f586d81f5d..b6b26fafd0617d68eb9b8b0932ba2759a29da469 100644 (file)
@@ -95,7 +95,7 @@ class BasicDNSSEC(RecursorTest):
     def testSecureCNAMEWildCardNXDOMAIN(self):
         # the answer to this query reaches the UDP truncation threshold, so let's use TCP
         res = self.sendQuery('something.cnamewildcardnxdomain.secure.example.', 'A', useTCP=True)
-        expectedCNAME = dns.rrset.from_text('something.cnamewildcardnxdomain.secure.example.', 0, dns.rdataclass.IN, 'CNAME', 'doesntexist.secure.example.')
+        expectedCNAME = dns.rrset.from_text('something.cnamewildcardnxdomain.secure.example.', 0, dns.rdataclass.IN, 'CNAME', 'doesnotexist.secure.example.')
 
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
         self.assertMatchingRRSIGInAnswer(res, expectedCNAME)
diff --git a/regression-tests.recursor-dnssec/proxyprotocol.py b/regression-tests.recursor-dnssec/proxyprotocol.py
new file mode 120000 (symlink)
index 0000000..2a3d79b
--- /dev/null
@@ -0,0 +1 @@
+../regression-tests.common/proxyprotocol.py
\ No newline at end of file
index a6f688fc3d37487213ca077ec38f0f9bfb941ea1..5051e2f5dabe61c67d91048612ce4e55246060a8 100644 (file)
@@ -15,6 +15,21 @@ import dns.message
 
 from eqdnsmessage import AssertEqualDNSMessageMixin
 
+
+def have_ipv6():
+    """
+    Try to make an IPv6 socket and bind it, if it fails, no ipv6...
+    """
+    try:
+        sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+        sock.bind(('::1', 56581))
+        sock.close()
+        return True
+    except:
+        return False
+    return False
+
+
 class RecursorTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     """
     Setup all recursors and auths required for the tests
@@ -40,6 +55,7 @@ max-cache-ttl=15
 threads=1
 loglevel=9
 disable-syslog=yes
+log-common-errors=yes
 """
     _config_template = """
 """
@@ -49,6 +65,7 @@ disable-syslog=yes
     _roothints = """
 .                        3600 IN NS  ns.root.
 ns.root.                 3600 IN A   %s.8
+ns.root.                 3600 IN AAAA ::1
 """ % _PREFIX
     _root_DS = "63149 13 1 a59da3f5c1b97fcd5fa2b3b2b0ac91d38a60d33a"
 
@@ -124,6 +141,13 @@ delay1.example.                     3600 IN DS 42043 13 2 7319fa605cf117f36e3de0
 delay2.example.                     3600 IN NS   ns1.delay2.example.
 ns1.delay2.example.                 3600 IN A    {prefix}.17
 delay2.example.                     3600 IN DS 42043 13 2 60a047b87740c8564c21d5fd34626c10a77a6c41e3b34564230119c2f13937b8
+
+cname-nxd.example.                  3600 IN CNAME cname-nxd-target.example.
+cname-nxd-target.example.           3600 IN A 192.0.2.100
+cname-nodata.example.               3600 IN CNAME cname-nodata-target.example.
+cname-nodata-target.example.        3600 IN A 192.0.2.101
+cname-custom-a.example.             3600 IN CNAME cname-custom-a-target.example.
+cname-custom-a-target.example.      3600 IN A 192.0.2.102
         """,
         'secure.example': """
 secure.example.          3600 IN SOA  {soa}
@@ -148,7 +172,7 @@ insecure.sub2.secure.example. 3600 IN NS ns1.insecure.example.
 
 *.cnamewildcard.secure.example. 3600 IN CNAME host1.secure.example.
 
-*.cnamewildcardnxdomain.secure.example. 3600 IN CNAME doesntexist.secure.example.
+*.cnamewildcardnxdomain.secure.example. 3600 IN CNAME doesnotexist.secure.example.
 
 cname-to-formerr.secure.example. 3600 IN CNAME host1.insecure-formerr.example.
 
@@ -356,6 +380,15 @@ PrivateKey: Ep9uo6+wwjb4MaOmqq7LHav2FLrjotVOeZg8JT1Qk04=
         '18': {'threads': 1,
                'zones': ['example']}
     }
+    # Other IPs used:
+    #  2: test_Interop.py
+    #  3-7: free?
+    # 19: free?
+    # 20: free?
+    # 21: test_ECS.py
+    # 22: test_EDNSBuffer.py
+    # 23: test_Lua.py
+    # 24: test_RoutingTag.py
 
     _auth_cmd = ['authbind',
                  os.environ['PDNS']]
@@ -488,7 +521,10 @@ distributor-threads={threads}""".format(confdir=confdir,
         authcmd = list(cls._auth_cmd)
         authcmd.append('--config-dir=%s' % confdir)
         authcmd.append('--local-address=%s' % ipaddress)
-        authcmd.append('--local-ipv6=')
+        if (confdir[-4:] == "ROOT") and have_ipv6():
+            authcmd.append('--local-ipv6=::1')
+        else:
+            authcmd.append('--local-ipv6=')
         print(' '.join(authcmd))
 
         logFile = os.path.join(confdir, 'pdns.log')
diff --git a/regression-tests.recursor-dnssec/test_API.py b/regression-tests.recursor-dnssec/test_API.py
new file mode 100644 (file)
index 0000000..ec275dd
--- /dev/null
@@ -0,0 +1,72 @@
+import os
+import requests
+
+from recursortests import RecursorTest
+
+class APIRecursorTest(RecursorTest):
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+
+class APIAllowedRecursorTest(APIRecursorTest):
+    _confdir = 'API'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+
+    _config_template = """
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+webserver-allow-from=127.0.0.1
+api-key=%s
+""" % (_wsPort, _wsPassword, _apiKey)
+
+    def testAPI(self):
+        headers = {'x-api-key': self._apiKey}
+        url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics'
+        r = requests.get(url, headers=headers, timeout=self._wsTimeout)
+        self.assertTrue(r)
+        self.assertEquals(r.status_code, 200)
+        self.assertTrue(r.json())
+
+class APIDeniedRecursorTest(APIRecursorTest):
+    _confdir = 'API'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+
+    _config_template = """
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+webserver-allow-from=192.0.2.1
+api-key=%s
+""" % (_wsPort, _wsPassword, _apiKey)
+
+    def testAPI(self):
+        headers = {'x-api-key': self._apiKey}
+        url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics'
+        try:
+            r = requests.get(url, headers=headers, timeout=self._wsTimeout)
+            self.assertTrue(False)
+        except requests.exceptions.ConnectionError as exp:
+            pass
diff --git a/regression-tests.recursor-dnssec/test_DNS64.py b/regression-tests.recursor-dnssec/test_DNS64.py
new file mode 100644 (file)
index 0000000..7038fe4
--- /dev/null
@@ -0,0 +1,155 @@
+import dns
+import os
+
+from recursortests import RecursorTest
+
+class DNS64RecursorTest(RecursorTest):
+
+    _confdir = 'DNS64'
+    _config_template = """
+    auth-zones=example.dns64=configs/%s/example.dns64.zone
+    auth-zones+=in-addr.arpa=configs/%s/in-addr.arpa.zone
+    auth-zones+=ip6.arpa=configs/%s/ip6.arpa.zone
+
+    dns64-prefix=64:ff9b::/96
+    """ % (_confdir, _confdir, _confdir)
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.dns64.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.dns64
+@ 3600 IN SOA {soa}
+www 3600 IN A 192.0.2.42
+www 3600 IN TXT "does exist"
+aaaa 3600 IN AAAA 2001:db8::1
+""".format(soa=cls._SOA))
+
+        authzonepath = os.path.join(confdir, 'in-addr.arpa.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN in-addr.arpa
+@ 3600 IN SOA {soa}
+42.2.0.192 IN PTR www.example.dns64.
+""".format(soa=cls._SOA))
+
+        authzonepath = os.path.join(confdir, 'ip6.arpa.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN ip6.arpa
+@ 3600 IN SOA {soa}
+1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2 IN PTR aaaa.example.dns64.
+""".format(soa=cls._SOA))
+
+        super(DNS64RecursorTest, cls).generateRecursorConfig(confdir)
+
+    # this type (A) exists for this name
+    def testExistingA(self):
+        qname = 'www.example.dns64.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    # there is no A record, we should get a NODATA
+    def testNonExistingA(self):
+        qname = 'aaaa.example.dns64.'
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertEquals(len(res.answer), 0)
+
+    # this type (AAAA) does not exist for this name but there is an A record, we should get a DNS64-wrapped AAAA
+    def testNonExistingAAAA(self):
+        qname = 'www.example.dns64.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'AAAA', '64:ff9b::c000:22a')
+
+        query = dns.message.make_query(qname, 'AAAA', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    # this type (AAAA) does not exist for this name and there is no A record either, we should get a NXDomain
+    def testNonExistingAAAA(self):
+        qname = 'nxd.example.dns64.'
+
+        query = dns.message.make_query(qname, 'AAAA', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
+
+    # there is an AAAA record, we should get it
+    def testExistingAAAA(self):
+        qname = 'aaaa.example.dns64.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'AAAA', '2001:db8::1')
+
+        query = dns.message.make_query(qname, 'AAAA', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    # there is a TXT record, we should get it
+    def testExistingTXT(self):
+        qname = 'www.example.dns64.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'TXT', '"does exist"')
+
+        query = dns.message.make_query(qname, 'TXT', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    # the PTR records for the DNS64 prefix should be generated
+    def testNonExistingPTR(self):
+        qname = 'a.2.2.0.0.0.0.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa.'
+        expectedCNAME = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'CNAME', '42.2.0.192.in-addr.arpa.')
+        expected = dns.rrset.from_text('42.2.0.192.in-addr.arpa.', 0, dns.rdataclass.IN, 'PTR', 'www.example.dns64.')
+
+        query = dns.message.make_query(qname, 'PTR', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            print(res)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expectedCNAME)
+            self.assertRRsetInAnswer(res, expected)
+
+    # but not for other prefixes
+    def testExistingPTR(self):
+        qname = '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'PTR', 'aaaa.example.dns64.')
+
+        query = dns.message.make_query(qname, 'PTR', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
index 26038091e8e18cb70e872ae4458a585266a75412..d1b80505fa6f409d621ac3ef60c33fc7e1c7db51 100644 (file)
@@ -5,7 +5,8 @@ import struct
 import threading
 import time
 import clientsubnetoption
-from recursortests import RecursorTest
+import unittest
+from recursortests import RecursorTest, have_ipv6
 from twisted.internet.protocol import DatagramProtocol
 from twisted.internet import reactor
 
@@ -14,6 +15,7 @@ nameECS = 'ecs-echo.example.'
 nameECSInvalidScope = 'invalid-scope.ecs-echo.example.'
 ttlECS = 60
 ecsReactorRunning = False
+ecsReactorv6Running = False
 
 class ECSTest(RecursorTest):
     _config_template_default = """
@@ -62,6 +64,7 @@ disable-syslog=yes
     @classmethod
     def startResponders(cls):
         global ecsReactorRunning
+        global ecsReactorv6Running
         print("Launching responders..")
 
         address = cls._PREFIX + '.21'
@@ -71,6 +74,10 @@ disable-syslog=yes
             reactor.listenUDP(port, UDPECSResponder(), interface=address)
             ecsReactorRunning = True
 
+        if not ecsReactorv6Running and have_ipv6():
+            reactor.listenUDP(53000, UDPECSResponder(), interface='::1')
+            ecsReactorv6Running = True
+
         if not reactor.running:
             cls._UDPResponder = threading.Thread(name='UDP Responder', target=reactor.run, args=(False,))
             cls._UDPResponder.setDaemon(True)
@@ -341,6 +348,7 @@ ecs-ipv6-cache-bits=128
         query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
         self.sendECSQuery(query, expected, ttlECS)
 
+@unittest.skipIf(not have_ipv6(), "No IPv6")
 class testIncomingECSByNameV6(ECSTest):
     _confdir = 'ECSIncomingByNameV6'
 
@@ -349,9 +357,9 @@ use-incoming-edns-subnet=yes
 ecs-ipv6-bits=128
 ecs-ipv4-cache-bits=32
 ecs-ipv6-cache-bits=128
-forward-zones=ecs-echo.example=%s.21
-query-local-address6=::1
-    """ % (os.environ['PREFIX'])
+query-local-address=::1
+forward-zones=ecs-echo.example=[::1]:53000
+    """
 
     def testSendECS(self):
         expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '2001:db8::1/128')
@@ -367,8 +375,7 @@ query-local-address6=::1
         self.sendECSQuery(query, expected, ttlECS)
 
     def testRequireNoECS(self):
-        # we should get ::1/128 because neither ecs-scope-zero-addr nor query-local-address are set,
-        # but query-local-address6 is set to ::1
+        # we should get ::1/128 because ecs-scope-zero-addr is unset and query-local-address is set to ::1
         expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', "::1/128")
 
         ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
index a040ffbadd2b66b83f059f4f7bcfc93e6597363e..054f720f373d108bf7cd86584feb64cc29fc2436 100644 (file)
@@ -378,7 +378,7 @@ quiet=no
             self.assertEqual(res.answer[0].ttl, 1)
 
     def testIPFilterHeader(self):
-        query = dns.message.make_query('ipfiler.luahooks.example.', 'A', 'IN')
+        query = dns.message.make_query('ipfilter.luahooks.example.', 'A', 'IN')
         query.flags |= dns.flags.AD
 
         for method in ("sendUDPQuery", "sendTCPQuery"):
diff --git a/regression-tests.recursor-dnssec/test_PacketCache.py b/regression-tests.recursor-dnssec/test_PacketCache.py
new file mode 100644 (file)
index 0000000..b658e4c
--- /dev/null
@@ -0,0 +1,144 @@
+import clientsubnetoption
+import cookiesoption
+import dns
+import os
+import requests
+
+from recursortests import RecursorTest
+
+class PacketCacheRecursorTest(RecursorTest):
+
+    _confdir = 'PacketCache'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _config_template = """
+    packetcache-ttl=60
+    auth-zones=example=configs/%s/example.zone
+    webserver=yes
+    webserver-port=%d
+    webserver-address=127.0.0.1
+    webserver-password=%s
+    api-key=%s
+    """ % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+        super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir)
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+
+    def checkPacketCacheMetrics(self, expectedHits, expectedMisses):
+        headers = {'x-api-key': self._apiKey}
+        url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics'
+        r = requests.get(url, headers=headers, timeout=self._wsTimeout)
+        self.assertTrue(r)
+        self.assertEquals(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        foundHits = False
+        foundMisses = True
+        for entry in content:
+            if entry['name'] == 'packetcache-hits':
+                foundHits = True
+                self.assertEquals(int(entry['value']), expectedHits)
+            elif entry['name'] == 'packetcache-misses':
+                foundMisses = True
+                self.assertEquals(int(entry['value']), expectedMisses)
+
+        self.assertTrue(foundHits)
+        self.assertTrue(foundMisses)
+
+    def testPacketCache(self):
+        # first query, no cookie
+        qname = 'a.example.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+        self.checkPacketCacheMetrics(0, 1)
+
+        # we should get a hit over UDP this time
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(1, 1)
+
+        eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
+        eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de')
+        ecso1 = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        ecso2 = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
+
+        # we add a cookie, should not match anymore
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(1, 2)
+
+        # same cookie, should match
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(2, 2)
+
+        # different cookie, should still match
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(3, 2)
+
+        # first cookie but with an ECS option, should not match
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso1])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(3, 3)
+
+        # different cookie but same ECS option, should match
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2, ecso1])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(4, 3)
+
+        # first cookie but different ECS option, should still match (we ignore EDNS Client Subnet
+        # in the recursor's packet cache, but ECS-specific responses are not cached
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso2])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.checkPacketCacheMetrics(5, 3)
index cde1e19c5b9cb92f4abefcf9578f2280df68fdb1..b4ecc610c5130637ae70915e1717a1d8e6051347 100644 (file)
@@ -194,14 +194,21 @@ class TestRecursorProtobuf(RecursorTest):
             self.assertEquals(record.ttl, rttl)
         self.assertTrue(record.HasField('rdata'))
 
-    def checkProtobufPolicy(self, msg, policyType, reason):
+    def checkProtobufPolicy(self, msg, policyType, reason, trigger, hit):
         self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
         self.assertTrue(msg.response.HasField('appliedPolicyType'))
         self.assertTrue(msg.response.HasField('appliedPolicy'))
+        self.assertTrue(msg.response.HasField('appliedPolicyTrigger'))
+        self.assertTrue(msg.response.HasField('appliedPolicyHit'))
         self.assertEquals(msg.response.appliedPolicy, reason)
         self.assertEquals(msg.response.appliedPolicyType, policyType)
+        self.assertEquals(msg.response.appliedPolicyTrigger, trigger)
+        self.assertEquals(msg.response.appliedPolicyHit, hit)
 
     def checkProtobufTags(self, msg, tags):
+        print(tags)
+        print('---')
+        print(msg.response.tags)
         self.assertEquals(len(msg.response.tags), len(tags))
         for tag in msg.response.tags:
             self.assertTrue(tag in tags)
@@ -550,7 +557,7 @@ auth-zones=example=configs/%s/example.zone""" % _confdir
         # check the protobuf messages corresponding to the UDP query and answer
         msg = self.getFirstProtobufMessage()
         self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
-        self.checkProtobufTags(msg, [self._tag_from_gettag])
+        self.checkProtobufTags(msg, [ self._tag_from_gettag ])
         # then the response
         msg = self.getFirstProtobufMessage()
         self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
@@ -559,7 +566,7 @@ auth-zones=example=configs/%s/example.zone""" % _confdir
         # we have max-cache-ttl set to 15
         self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
         self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
-        tags = [self._tag_from_gettag] + self._tags
+        tags = [ self._tag_from_gettag ] + self._tags
         self.checkProtobufTags(msg, tags)
         self.checkNoRemainingMessage()
 
@@ -856,7 +863,75 @@ sub.test 3600 IN A 192.0.2.42
         # then the response
         msg = self.getFirstProtobufMessage()
         self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
-        self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.')
+        self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.', '*.test.example.', 'sub.test.example')
+        self.assertEquals(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # we have max-cache-ttl set to 15
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+        self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+        self.checkNoRemainingMessage()
+
+class ProtobufRPZTagsTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly export the RPZ tags in our protobuf messages
+    """
+
+    _confdir = 'ProtobufRPZTags'
+    _config_template = """
+auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
+    _tags = ['tag1', 'tag2']
+    _tags_from_gettag = ['tag1-from-gettag', 'tag2-from-gettag']
+    _tags_from_rpz = ['tag1-from-rpz', 'tag2-from-rpz' ]
+    _lua_config_file = """
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, tags={'tag1', 'tag2'} } )
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", tags={ '%s', '%s'} })
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _confdir, _tags_from_rpz[0], _tags_from_rpz[1])
+    _lua_dns_script_file = """
+    function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
+      return 0, { '%s', '%s' }
+    end
+    function preresolve(dq)
+      dq:addPolicyTag('%s')
+      dq:addPolicyTag('%s')
+      return false
+    end
+    """ % (_tags_from_gettag[0], _tags_from_gettag[1], _tags[0], _tags[1])
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.rpz.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+sub.test 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
+""".format(soa=cls._SOA))
+
+        super(ProtobufRPZTagsTest, cls).generateRecursorConfig(confdir)
+
+    def testA(self):
+        name = 'sub.test.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+        self.assertRRsetInAnswer(res, expected)
+
+        # check the protobuf messages corresponding to the UDP query and answer
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+        self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.', '*.test.example.', 'sub.test.example')
+        self.checkProtobufTags(msg, self._tags + self._tags_from_gettag + self._tags_from_rpz)
         self.assertEquals(len(msg.response.rrs), 1)
         rr = msg.response.rrs[0]
         # we have max-cache-ttl set to 15
diff --git a/regression-tests.recursor-dnssec/test_ProxyProtocol.py b/regression-tests.recursor-dnssec/test_ProxyProtocol.py
new file mode 100644 (file)
index 0000000..fcc489b
--- /dev/null
@@ -0,0 +1,661 @@
+import dns
+import os
+import socket
+import struct
+import sys
+import time
+
+try:
+    range = xrange
+except NameError:
+    pass
+
+from recursortests import RecursorTest
+from proxyprotocol import ProxyProtocol
+
+class ProxyProtocolRecursorTest(RecursorTest):
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+
+    @classmethod
+    def sendUDPQueryWithProxyProtocol(cls, query, v6, source, destination, sourcePort, destinationPort, values=[], timeout=2.0):
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(False, False, v6, source, destination, sourcePort, destinationPort, values)
+        payload = ppPayload + queryPayload
+
+        if timeout:
+            cls._sock.settimeout(timeout)
+
+        try:
+            cls._sock.send(payload)
+            data = cls._sock.recv(4096)
+        except socket.timeout:
+            data = None
+        finally:
+            if timeout:
+                cls._sock.settimeout(None)
+
+        message = None
+        if data:
+            message = dns.message.from_wire(data)
+        return message
+
+    @classmethod
+    def sendTCPQueryWithProxyProtocol(cls, query, v6, source, destination, sourcePort, destinationPort, values=[], timeout=2.0):
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(False, False, v6, source, destination, sourcePort, destinationPort, values)
+
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        if timeout:
+            sock.settimeout(timeout)
+
+        sock.connect(("127.0.0.1", cls._recursorPort))
+
+        try:
+            sock.send(ppPayload)
+            sock.send(struct.pack("!H", len(queryPayload)))
+            sock.send(queryPayload)
+            data = sock.recv(2)
+            if data:
+                (datalen,) = struct.unpack("!H", data)
+                data = sock.recv(datalen)
+        except socket.timeout as e:
+            print("Timeout: %s" % (str(e)))
+            data = None
+        except socket.error as e:
+            print("Network error: %s" % (str(e)))
+            data = None
+        finally:
+            sock.close()
+
+        message = None
+        if data:
+            message = dns.message.from_wire(data)
+        return message
+
+class ProxyProtocolAllowedRecursorTest(ProxyProtocolRecursorTest):
+    _confdir = 'ProxyProtocol'
+    _lua_dns_script_file = """
+
+    function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyProtocolValues)
+      local remoteaddr = remote:toStringWithPort()
+      local localaddr = localip:toStringWithPort()
+      local foundFoo = false
+      local foundBar = false
+
+      if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
+        pdnslog('gettag: invalid source '..remoteaddr)
+        return 1
+      end
+      if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
+        pdnslog('gettag: invalid dest '..localaddr)
+        return 2
+      end
+
+      for k,v in pairs(proxyProtocolValues) do
+        local type = v:getType()
+        local content = v:getContent()
+        if type == 0 and content == 'foo' then
+          foundFoo = true
+        end
+        if type == 255 and content == 'bar' then
+          foundBar = true
+        end
+      end
+
+      if not foundFoo or not foundBar then
+        pdnslog('gettag: TLV not found')
+        return 3
+      end
+
+      return 42
+    end
+
+    function preresolve(dq)
+      local foundFoo = false
+      local foundBar = false
+      local values = dq:getProxyProtocolValues()
+      for k,v in pairs(values) do
+        local type = v:getType()
+        local content = v:getContent()
+        if type == 0 and content == 'foo' then
+          foundFoo = true
+        end
+        if type == 255 and content == 'bar' then
+          foundBar = true
+        end
+      end
+
+      if not foundFoo or not foundBar then
+        pdnslog('TLV not found')
+        dq:addAnswer(pdns.A, '192.0.2.255', 60)
+        return true
+      end
+
+      local remoteaddr = dq.remoteaddr:toStringWithPort()
+      local localaddr = dq.localaddr:toStringWithPort()
+
+      if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
+        pdnslog('invalid source '..remoteaddr)
+        dq:addAnswer(pdns.A, '192.0.2.128', 60)
+        return true
+      end
+      if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
+        pdnslog('invalid dest '..localaddr)
+        dq:addAnswer(pdns.A, '192.0.2.129', 60)
+        return true
+      end
+
+      if dq.tag ~= 42 then
+        pdnslog('invalid tag '..dq.tag)
+        dq:addAnswer(pdns.A, '192.0.2.130', 60)
+        return true
+      end
+
+      dq:addAnswer(pdns.A, '192.0.2.1', 60)
+      return true
+    end
+    """
+
+    _config_template = """
+    proxy-protocol-from=127.0.0.1
+    proxy-protocol-maximum-size=512
+    allow-from=127.0.0.0/24, ::1/128, ::42/128
+""" % ()
+
+    def testLocalProxyProtocol(self):
+        qname = 'local.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(True, False, False, None, None, None, None, [])
+        payload = ppPayload + queryPayload
+
+        # UDP
+        self._sock.settimeout(2.0)
+
+        try:
+            self._sock.send(payload)
+            data = self._sock.recv(4096)
+        except socket.timeout:
+            data = None
+        finally:
+            self._sock.settimeout(None)
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+
+        # TCP
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.settimeout(2.0)
+        sock.connect(("127.0.0.1", self._recursorPort))
+
+        try:
+            sock.send(ppPayload)
+            sock.send(struct.pack("!H", len(queryPayload)))
+            sock.send(queryPayload)
+            data = sock.recv(2)
+            if data:
+                (datalen,) = struct.unpack("!H", data)
+                data = sock.recv(datalen)
+        except socket.timeout as e:
+            print("Timeout: %s" % (str(e)))
+            data = None
+        except socket.error as e:
+            print("Network error: %s" % (str(e)))
+            data = None
+        finally:
+            sock.close()
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+
+    def testInvalidMagicProxyProtocol(self):
+        qname = 'invalid-magic.proxy-protocol.recursor-tests.powerdns.com.'
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(True, False, False, None, None, None, None, [])
+        ppPayload = b'\x00' + ppPayload[1:]
+        payload = ppPayload + queryPayload
+
+        # UDP
+        self._sock.settimeout(2.0)
+
+        try:
+            self._sock.send(payload)
+            data = self._sock.recv(4096)
+        except socket.timeout:
+            data = None
+        finally:
+            self._sock.settimeout(None)
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertEqual(res, None)
+
+        # TCP
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.settimeout(2.0)
+        sock.connect(("127.0.0.1", self._recursorPort))
+
+        try:
+            sock.send(ppPayload)
+            sock.send(struct.pack("!H", len(queryPayload)))
+            sock.send(queryPayload)
+            data = sock.recv(2)
+            if data:
+                (datalen,) = struct.unpack("!H", data)
+                data = sock.recv(datalen)
+        except socket.timeout as e:
+            print("Timeout: %s" % (str(e)))
+            data = None
+        except socket.error as e:
+            print("Network error: %s" % (str(e)))
+            data = None
+        finally:
+            sock.close()
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertEqual(res, None)
+
+    def testTCPOneByteAtATimeProxyProtocol(self):
+        qname = 'tcp-one-byte-at-a-time.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(False, True, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+
+        # TCP
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.settimeout(2.0)
+        sock.connect(("127.0.0.1", self._recursorPort))
+
+        try:
+            for i in range(len(ppPayload)):
+                sock.send(ppPayload[i:i+1])
+                time.sleep(0.01)
+            value = struct.pack("!H", len(queryPayload))
+            for i in range(len(value)):
+                sock.send(value[i:i+1])
+                time.sleep(0.01)
+            for i in range(len(queryPayload)):
+                sock.send(queryPayload[i:i+1])
+                time.sleep(0.01)
+
+            data = sock.recv(2)
+            if data:
+                (datalen,) = struct.unpack("!H", data)
+                data = sock.recv(datalen)
+        except socket.timeout as e:
+            print("Timeout: %s" % (str(e)))
+            data = None
+        except socket.error as e:
+            print("Network error: %s" % (str(e)))
+            data = None
+        finally:
+            sock.close()
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+
+    def testTooLargeProxyProtocol(self):
+        # the total payload (proxy protocol + DNS) is larger than proxy-protocol-maximum-size
+        # so it should be dropped
+        qname = 'too-large.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(False, True, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [1, b'A'*512], [ 255, b'bar'] ])
+        payload = ppPayload + queryPayload
+
+        # UDP
+        self._sock.settimeout(2.0)
+
+        try:
+            self._sock.send(payload)
+            data = self._sock.recv(4096)
+        except socket.timeout:
+            data = None
+        finally:
+            self._sock.settimeout(None)
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertEqual(res, None)
+
+        # TCP
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.settimeout(2.0)
+        sock.connect(("127.0.0.1", self._recursorPort))
+
+        try:
+            sock.send(ppPayload)
+            sock.send(struct.pack("!H", len(queryPayload)))
+            sock.send(queryPayload)
+
+            data = sock.recv(2)
+            if data:
+                (datalen,) = struct.unpack("!H", data)
+                data = sock.recv(datalen)
+        except socket.timeout as e:
+            print("Timeout: %s" % (str(e)))
+            data = None
+        except socket.error as e:
+            print("Network error: %s" % (str(e)))
+            data = None
+        finally:
+            sock.close()
+
+        res = None
+        if data:
+            res = dns.message.from_wire(data)
+        self.assertEqual(res, None)
+
+    def testNoHeaderProxyProtocol(self):
+        qname = 'no-header.proxy-protocol.recursor-tests.powerdns.com.'
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertEqual(res, None)
+
+    def testIPv4ProxyProtocol(self):
+        qname = 'ipv4.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testIPv4NoValuesProxyProtocol(self):
+        qname = 'ipv4-no-values.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testIPv4ProxyProtocolNotAuthorized(self):
+        qname = 'ipv4-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, False, '192.0.2.255', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+            self.assertEqual(res, None)
+
+    def testIPv6ProxyProtocol(self):
+        qname = 'ipv6.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, True, '::42', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testIPv6NoValuesProxyProtocol(self):
+        qname = 'ipv6-no-values.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, True, '::42', '2001:db8::ff', 0, 65535)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testIPv6ProxyProtocolNotAuthorized(self):
+        qname = 'ipv6-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, True, '2001:db8::1', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+            self.assertEqual(res, None)
+
+    def testIPv6ProxyProtocolSeveralQueriesOverTCP(self):
+        qname = 'several-queries-tcp.proxy-protocol.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        queryPayload = query.to_wire()
+        ppPayload = ProxyProtocol.getPayload(False, True, True, '::42', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+        payload = ppPayload + queryPayload
+
+        # TCP
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock.settimeout(2.0)
+        sock.connect(("127.0.0.1", self._recursorPort))
+
+        sock.send(ppPayload)
+
+        count = 0
+        for idx in range(5):
+            try:
+                sock.send(struct.pack("!H", len(queryPayload)))
+                sock.send(queryPayload)
+
+                data = sock.recv(2)
+                if data:
+                    (datalen,) = struct.unpack("!H", data)
+                    data = sock.recv(datalen)
+            except socket.timeout as e:
+                print("Timeout: %s" % (str(e)))
+                data = None
+                break
+            except socket.error as e:
+                print("Network error: %s" % (str(e)))
+                data = None
+                break
+
+            res = None
+            if data:
+                res = dns.message.from_wire(data)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+            count = count + 1
+
+        self.assertEqual(count, 5)
+        sock.close()
+
+class ProxyProtocolAllowedFFIRecursorTest(ProxyProtocolAllowedRecursorTest):
+    # same tests than ProxyProtocolAllowedRecursorTest but with the Lua FFI interface instead of the regular one
+    _confdir = 'ProxyProtocolFFI'
+    _lua_dns_script_file = """
+    local ffi = require("ffi")
+
+    ffi.cdef[[
+      typedef struct pdns_ffi_param pdns_ffi_param_t;
+
+      typedef struct pdns_proxyprotocol_value {
+        uint8_t     type;
+        uint16_t    len;
+        const void* data;
+      } pdns_proxyprotocol_value_t;
+
+      size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out);
+      const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref);
+      const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref);
+      uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref);
+      uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref);
+
+      void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
+    ]]
+
+    function gettag_ffi(obj)
+      local remoteaddr = ffi.string(ffi.C.pdns_ffi_param_get_remote(obj))
+      local localaddr = ffi.string(ffi.C.pdns_ffi_param_get_local(obj))
+      local foundFoo = false
+      local foundBar = false
+
+      if remoteaddr ~= '127.0.0.42' and remoteaddr ~= '::42' then
+        pdnslog('gettag-ffi: invalid source '..remoteaddr)
+        ffi.C.pdns_ffi_param_set_tag(obj, 1)
+        return
+      end
+      if localaddr ~= '255.255.255.255' and localaddr ~= '2001:db8::ff' then
+        pdnslog('gettag-ffi: invalid dest '..localaddr)
+        ffi.C.pdns_ffi_param_set_tag(obj, 2)
+        return
+      end
+
+      if ffi.C.pdns_ffi_param_get_remote_port(obj) ~= 0 then
+        pdnslog('gettag-ffi: invalid source port '..ffi.C.pdns_ffi_param_get_remote_port(obj))
+        ffi.C.pdns_ffi_param_set_tag(obj, 1)
+        return
+      end
+
+      if ffi.C.pdns_ffi_param_get_local_port(obj) ~= 65535 then
+        pdnslog('gettag-ffi: invalid source port '..ffi.C.pdns_ffi_param_get_local_port(obj))
+        ffi.C.pdns_ffi_param_set_tag(obj, 2)
+        return
+      end
+
+      local ret_ptr = ffi.new("const pdns_proxyprotocol_value_t *[1]")
+      local ret_ptr_param = ffi.cast("const pdns_proxyprotocol_value_t **", ret_ptr)
+      local values_count = ffi.C.pdns_ffi_param_get_proxy_protocol_values(obj, ret_ptr_param)
+
+      if values_count > 0 then
+        for i = 0,tonumber(values_count)-1 do
+          local type = ret_ptr[0][i].type
+          local content = ffi.string(ret_ptr[0][i].data, ret_ptr[0][i].len)
+          if type == 0 and content == 'foo' then
+            foundFoo = true
+          end
+          if type == 255 and content == 'bar' then
+            foundBar = true
+          end
+        end
+      end
+
+      if not foundFoo or not foundBar then
+        pdnslog('gettag-ffi: TLV not found')
+        ffi.C.pdns_ffi_param_set_tag(obj, 3)
+        return
+      end
+
+      ffi.C.pdns_ffi_param_set_tag(obj, 42)
+    end
+
+    function preresolve(dq)
+      local foundFoo = false
+      local foundBar = false
+      local values = dq:getProxyProtocolValues()
+      for k,v in pairs(values) do
+        local type = v:getType()
+        local content = v:getContent()
+        if type == 0 and content == 'foo' then
+          foundFoo = true
+        end
+        if type == 255 and content == 'bar' then
+          foundBar = true
+        end
+      end
+
+      if not foundFoo or not foundBar then
+        pdnslog('TLV not found')
+        dq:addAnswer(pdns.A, '192.0.2.255', 60)
+        return true
+      end
+
+      local remoteaddr = dq.remoteaddr:toStringWithPort()
+      local localaddr = dq.localaddr:toStringWithPort()
+
+      if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
+        pdnslog('invalid source '..remoteaddr)
+        dq:addAnswer(pdns.A, '192.0.2.128', 60)
+        return true
+      end
+      if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
+        pdnslog('invalid dest '..localaddr)
+        dq:addAnswer(pdns.A, '192.0.2.129', 60)
+        return true
+      end
+
+      if dq.tag ~= 42 then
+        pdnslog('invalid tag '..dq.tag)
+        dq:addAnswer(pdns.A, '192.0.2.130', 60)
+        return true
+      end
+
+      dq:addAnswer(pdns.A, '192.0.2.1', 60)
+      return true
+    end
+    """
+
+class ProxyProtocolNotAllowedRecursorTest(ProxyProtocolRecursorTest):
+    _confdir = 'ProxyProtocolNotAllowed'
+    _lua_dns_script_file = """
+
+    function preresolve(dq)
+      dq:addAnswer(pdns.A, '192.0.2.1', 60)
+      return true
+    end
+    """
+
+    _config_template = """
+    proxy-protocol-from=192.0.2.1/32
+    allow-from=127.0.0.0/24, ::1/128
+""" % ()
+
+    def testNoHeaderProxyProtocol(self):
+        qname = 'no-header.proxy-protocol-not-allowed.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testIPv4ProxyProtocol(self):
+        qname = 'ipv4.proxy-protocol-not-allowed.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+            self.assertEqual(res, None)
index 62ab149b9c488631bb2bc1d90bb6c64bbf3ff7c1..595215c6e11165956dac18bf0a5ffeda0290601b 100644 (file)
@@ -28,7 +28,7 @@ class RPZServer(object):
             return False
 
         if newSerial != self._currentSerial + 1:
-            raise AssertionError("Asking the RPZ server to server serial %d, already serving %d" % (newSerial, self._currentSerial))
+            raise AssertionError("Asking the RPZ server to serve serial %d, already serving %d" % (newSerial, self._currentSerial))
         self._targetSerial = newSerial
         return True
 
@@ -52,7 +52,8 @@ class RPZServer(object):
         elif message.question[0].rdtype == dns.rdatatype.IXFR:
             oldSerial = message.authority[0][0].serial
 
-            if oldSerial != self._currentSerial:
+            # special case for the 9th update, which might get skipped
+            if oldSerial != self._currentSerial and self._currentSerial != 9:
                 print('Received an IXFR query with an unexpected serial %d, expected %d' % (oldSerial, self._currentSerial))
                 return (None, self._currentSerial)
 
@@ -124,6 +125,38 @@ class RPZServer(object):
                     dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
                     dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
                     ]
+            elif newSerial == 9:
+                # IXFR inserting a duplicate, we should not crash and skip it
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('dup.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-passthru.'),
+                    dns.rrset.from_text('dup.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-passthru.'),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
+                    ]
+            elif newSerial == 10:
+                # full AXFR to make sure we are removing the duplicate, adding a record, to check that the update was correctly applied
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('f.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
+                    ]
+            elif newSerial == 11:
+                # IXFR with two deltas, the first one adding a 'g' and the second one removing 'f'
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % (newSerial + 1)),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('g.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('f.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1'),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % (newSerial + 1)),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % (newSerial + 1))
+                    ]
+                # this one has two updates in one
+                newSerial = newSerial + 1
+                self._targetSerial = self._targetSerial + 1
 
         response.answer = records
         return (newSerial, response)
@@ -457,6 +490,36 @@ e 3600 IN A 192.0.2.42
         self.checkNXD('tc.example.')
         self.checkNXD('drop.example.')
 
+        # 9th zone is a duplicate, it might get skipped
+        global rpzServer
+        rpzServer.moveToSerial(9)
+        time.sleep(3)
+        self.waitUntilCorrectSerialIsLoaded(10)
+        self.checkRPZStats(10, 1, 4, self._xfrDone)
+        self.checkNotBlocked('a.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkNotBlocked('e.example.')
+        self.checkBlocked('f.example.')
+        self.checkNXD('tc.example.')
+        self.checkNXD('drop.example.')
+
+        # the next update will update the zone twice
+        rpzServer.moveToSerial(11)
+        time.sleep(3)
+        self.waitUntilCorrectSerialIsLoaded(12)
+        self.checkRPZStats(12, 1, 4, self._xfrDone)
+        self.checkNotBlocked('a.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkNotBlocked('e.example.')
+        self.checkNXD('f.example.')
+        self.checkBlocked('g.example.')
+        self.checkNXD('tc.example.')
+        self.checkNXD('drop.example.')
+
 class RPZFileRecursorTest(RPZRecursorTest):
     """
     This test makes sure that we correctly load RPZ zones from a file
@@ -606,7 +669,7 @@ tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
         super(RPZFileDefaultPolNotOverrideLocalRecursorTest, cls).generateRecursorConfig(confdir)
 
     def testRPZ(self):
-        # local data entries will not be overridden by the default polic
+        # local data entries will not be overridden by the default policy
         self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
         self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
         # will be blocked because the default policy does not override local data entries
@@ -800,3 +863,156 @@ forward-zones=delegated.example=127.0.0.1:%d
         # We only test once because after that the answer is cached, so the NS is not contacted
         # and the whitelist is not applied (yes, NSIP and NSDNAME are brittle).
         self.checkCustom('nsip.delegated.example.', 'A', dns.rrset.from_text('nsip.delegated.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1'))
+
+
+class RPZResponseIPCNameChainCustomTest(RPZRecursorTest):
+    """
+    This test makes sure that the recursor applies response IP rules to records in a CNAME chain,
+    and resolves the target of a custom CNAME.
+    """
+
+    _confdir = 'RPZResponseIPCNameChainCustom'
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
+    """ % (_confdir)
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+forward-zones=delegated.example=127.0.0.1:%d
+""" % (_confdir, rpzAuthServerPort)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+name IN CNAME cname
+cname IN A 192.0.2.255
+custom-target IN A 192.0.2.254
+""".format(soa=cls._SOA))
+
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+cname.example IN CNAME custom-target.example.
+custom-target.example IN A 192.0.2.253
+""".format(soa=cls._SOA))
+
+        super(RPZResponseIPCNameChainCustomTest, cls).generateRecursorConfig(confdir)
+
+    def testRPZChain(self):
+        # we request the A record for 'name.example.', which is a CNAME to 'cname.example'
+        # this one does exist but we have a RPZ rule that should be triggered,
+        # replacing the 'real' CNAME by a CNAME to 'custom-target.example.'
+        # There is a RPZ rule for that name but it should not be triggered, since
+        # the RPZ specs state "Recall that only one policy rule, from among all those matched at all
+        # stages of resolving a CNAME or DNAME chain, can affect the final
+        # response; this is true even if the selected rule has a PASSTHRU
+        # action" in 5.1 "CNAME or DNAME Chain Position" Precedence Rule
+
+        # two times to check the cache
+        for _ in range(2):
+            query = dns.message.make_query('name.example.', 'A', want_dnssec=True)
+            query.flags |= dns.flags.CD
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                res = sender(query)
+                self.assertRcodeEqual(res, dns.rcode.NOERROR)
+                self.assertRRsetInAnswer(res, dns.rrset.from_text('name.example.', 0, dns.rdataclass.IN, 'CNAME', 'cname.example.'))
+                self.assertRRsetInAnswer(res, dns.rrset.from_text('cname.example.', 0, dns.rdataclass.IN, 'CNAME', 'custom-target.example.'))
+                self.assertRRsetInAnswer(res, dns.rrset.from_text('custom-target.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.254'))
+
+
+class RPZCNameChainCustomTest(RPZRecursorTest):
+    """
+    This test makes sure that the recursor applies QName rules to names in a CNAME chain.
+    No forward or internal auth zones here, as we want to test the real resolution
+    (with QName Minimization).
+    """
+
+    _PREFIX = os.environ['PREFIX']
+    _confdir = 'RPZCNameChainCustom'
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
+    """ % (_confdir)
+    _config_template = ""
+
+    @classmethod
+    def setUpClass(cls):
+
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateAllAuthConfig(confdir)
+        cls.startAuth(os.path.join(confdir, "auth-8"), cls._PREFIX + '.8')
+        cls.startAuth(os.path.join(confdir, "auth-10"), cls._PREFIX + '.10')
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownAuth()
+        cls.tearDownRecursor()
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+32.100.2.0.192.rpz-ip IN CNAME .
+32.101.2.0.192.rpz-ip IN CNAME *.
+32.102.2.0.192.rpz-ip IN A 192.0.2.103
+""".format(soa=cls._SOA))
+
+        super(RPZCNameChainCustomTest, cls).generateRecursorConfig(confdir)
+
+    def testRPZChainNXD(self):
+        # we should match the A at the end of the CNAME chain and
+        # trigger a NXD
+
+        # two times to check the cache
+        for _ in range(2):
+            query = dns.message.make_query('cname-nxd.example.', 'A', want_dnssec=True)
+            query.flags |= dns.flags.CD
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                res = sender(query)
+                self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
+                self.assertEquals(len(res.answer), 0)
+
+    def testRPZChainNODATA(self):
+        # we should match the A at the end of the CNAME chain and
+        # trigger a NODATA
+
+        # two times to check the cache
+        for _ in range(2):
+            query = dns.message.make_query('cname-nodata.example.', 'A', want_dnssec=True)
+            query.flags |= dns.flags.CD
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                res = sender(query)
+                self.assertRcodeEqual(res, dns.rcode.NOERROR)
+                self.assertEquals(len(res.answer), 0)
+
+    def testRPZChainCustom(self):
+        # we should match the A at the end of the CNAME chain and
+        # get a custom A, replacing the existing one
+
+        # two times to check the cache
+        for _ in range(2):
+            query = dns.message.make_query('cname-custom-a.example.', 'A', want_dnssec=True)
+            query.flags |= dns.flags.CD
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                res = sender(query)
+                self.assertRcodeEqual(res, dns.rcode.NOERROR)
+                # the original CNAME record is signed
+                self.assertEquals(len(res.answer), 3)
+                self.assertRRsetInAnswer(res, dns.rrset.from_text('cname-custom-a.example.', 0, dns.rdataclass.IN, 'CNAME', 'cname-custom-a-target.example.'))
+                self.assertRRsetInAnswer(res, dns.rrset.from_text('cname-custom-a-target.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.103'))
index 913aa3ca6fdf8ad71ad9deaa720463042dddd453..eee64069acf800fdab0e819a547f703d17c7823e 100644 (file)
@@ -4,7 +4,7 @@ import subprocess
 from recursortests import RecursorTest
 
 
-class testReadTrustAnchorsFronFile(RecursorTest):
+class testReadTrustAnchorsFromFile(RecursorTest):
     _confdir = 'ReadTAsFromFile'
 
     _config_template = """dnssec=validate"""
diff --git a/regression-tests.recursor-dnssec/test_RoutingTag.py b/regression-tests.recursor-dnssec/test_RoutingTag.py
new file mode 100644 (file)
index 0000000..cd02160
--- /dev/null
@@ -0,0 +1,307 @@
+import dns
+import os
+import socket
+import struct
+import threading
+import time
+import clientsubnetoption
+import subprocess
+from recursortests import RecursorTest
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+emptyECSText = 'No ECS received'
+nameECS = 'ecs-echo.example.'
+nameECSInvalidScope = 'invalid-scope.ecs-echo.example.'
+ttlECS = 60
+routingReactorRunning = False
+
+class RoutingTagTest(RecursorTest):
+    _config_template_default = """
+daemon=no
+trace=yes
+dont-query=
+ecs-add-for=0.0.0.0/0
+local-address=127.0.0.1
+packetcache-ttl=0
+packetcache-servfail-ttl=0
+max-cache-ttl=600
+threads=1
+loglevel=9
+disable-syslog=yes
+"""
+
+    def sendECSQuery(self, query, expected, expectedFirstTTL=None):
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        # this will break if you are not looking for the first RR, sorry!
+        if expectedFirstTTL is not None:
+            self.assertEqual(res.answer[0].ttl, expectedFirstTTL)
+        else:
+            expectedFirstTTL = res.answer[0].ttl
+
+        # wait one second, check that the TTL has been
+        # decreased indicating a cache hit
+        time.sleep(1)
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        self.assertLess(res.answer[0].ttl, expectedFirstTTL)
+
+    def checkECSQueryHit(self, query, expected):
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expected)
+        # this will break if you are not looking for the first RR, sorry!
+        self.assertLess(res.answer[0].ttl, ttlECS)
+
+    def setRoutingTag(self, tag):
+        # This value is picked up by the gettag()
+        file = open('tagfile', 'w')
+        if tag:
+            file.write(tag)
+        file.close();
+
+    @classmethod
+    def startResponders(cls):
+        global routingReactorRunning
+        print("Launching responders..")
+
+        address = cls._PREFIX + '.24'
+        port = 53
+
+        if not routingReactorRunning:
+            reactor.listenUDP(port, UDPRoutingResponder(), interface=address)
+            routingReactorRunning = True
+
+        if not reactor.running:
+            cls._UDPResponder = threading.Thread(name='UDP Routing Responder', target=reactor.run, args=(False,))
+            cls._UDPResponder.setDaemon(True)
+            cls._UDPResponder.start()
+
+    @classmethod
+    def setUpClass(cls):
+        cls.setUpSockets()
+
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+        print("Launching tests..")
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+        os.unlink('tagfile')
+
+class testRoutingTag(RoutingTagTest):
+    _confdir = 'RoutingTag'
+
+    _config_template = """
+log-common-errors=yes
+use-incoming-edns-subnet=yes
+edns-subnet-whitelist=ecs-echo.example.
+forward-zones=ecs-echo.example=%s.24
+    """ % (os.environ['PREFIX'])
+    _lua_dns_script_file = """
+
+function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyProtocolValues)
+  local rtag
+  for line in io.lines('tagfile') do
+    rtag = line
+    break
+  end
+  return 0, nil, nil, nil, nil, nil, rtag
+end
+"""
+
+    def testSendECS(self):
+        # First send an ECS query with routingTag
+        self.setRoutingTag('foo')
+        expected1 = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.2.0/24')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected1)
+
+        # Now check a cache hit with the same routingTag (but no ECS)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.checkECSQueryHit(query, expected1)
+
+        expected2 = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
+        # And see if a different tag does *not* hit the first one
+        self.setRoutingTag('bar')
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.sendECSQuery(query, expected2)
+
+        # And see if a *no* tag does *not* hit the first one
+        expected3 = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.3.0/24')
+        self.setRoutingTag(None)
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.3.1', 32)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected3)
+
+        # And see if an unknown tag from the same subnet does hit the last
+        self.setRoutingTag('baz')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.3.2', 32)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.checkECSQueryHit(query, expected3)
+
+        # And a no tag and no subnet query does hit the general case
+        self.setRoutingTag(None)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.sendECSQuery(query, expected2)
+
+        # And a unknown tag and no subnet query does hit the general case
+        self.setRoutingTag('bag')
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.sendECSQuery(query, expected2)
+
+        #return # remove this line to peek at cache
+        rec_controlCmd = [os.environ['RECCONTROL'],
+                          '--config-dir=%s' % 'configs/' + self._confdir,
+                          'dump-cache x']
+        try:
+            expected = 'dumped 7 records\n'
+            ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
+            self.assertEqual(ret, expected)
+
+        except subprocess.CalledProcessError as e:
+            print(e.output)
+            raise
+
+class testRoutingTagFFI(RoutingTagTest):
+    _confdir = 'RoutingTagFFI'
+
+    _config_template = """
+log-common-errors=yes
+use-incoming-edns-subnet=yes
+edns-subnet-whitelist=ecs-echo.example.
+forward-zones=ecs-echo.example=%s.24
+    """ % (os.environ['PREFIX'])
+    _lua_dns_script_file = """
+
+local ffi = require("ffi")
+ffi.cdef[[
+  typedef struct pdns_ffi_param pdns_ffi_param_t;
+
+  const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
+  void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag);
+]]
+
+function gettag_ffi(obj)
+  for line in io.lines('tagfile') do
+    local rtag = ffi.string(line)
+    ffi.C.pdns_ffi_param_set_routingtag(obj, rtag)
+    break
+  end
+  return 0
+end
+"""
+    def testSendECS(self):
+        # First send an ECS query with routingTag
+        self.setRoutingTag('foo')
+        expected1 = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.2.0/24')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected1)
+
+        # Now check a cache hit with the same routingTag (but no ECS)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.checkECSQueryHit(query, expected1)
+
+        expected2 = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
+        # And see if a different tag does *not* hit the first one
+        self.setRoutingTag('bar')
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.sendECSQuery(query, expected2)
+
+        # And see if a *no* tag does *not* hit the first one
+        expected3 = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.3.0/24')
+        self.setRoutingTag(None)
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.3.1', 32)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.sendECSQuery(query, expected3)
+
+        # And see if an unknown tag from the same subnet does hit the last
+        self.setRoutingTag('baz')
+        ecso = clientsubnetoption.ClientSubnetOption('192.0.3.2', 32)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
+        self.checkECSQueryHit(query, expected3)
+
+        # And a no tag and no subnet query does hit the general case
+        self.setRoutingTag(None)
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.sendECSQuery(query, expected2)
+
+        # And a unknown tag and no subnet query does hit the general case
+        self.setRoutingTag('bag')
+        query = dns.message.make_query(nameECS, 'TXT', 'IN')
+        self.sendECSQuery(query, expected2)
+
+        return #remove this line to peek at cache
+        rec_controlCmd = [os.environ['RECCONTROL'],
+                          '--config-dir=%s' % 'configs/' + self._confdir,
+                          'dump-cache y']
+        try:
+            expected = 'dumped 6 records\n'
+            ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
+            self.assertEqual(ret, expected)
+
+        except subprocess.CalledProcessError as e:
+            print(e.output)
+            raise
+
+class UDPRoutingResponder(DatagramProtocol):
+    @staticmethod
+    def ipToStr(option):
+        if option.family == clientsubnetoption.FAMILY_IPV4:
+            ip = socket.inet_ntop(socket.AF_INET, struct.pack('!L', option.ip))
+        elif option.family == clientsubnetoption.FAMILY_IPV6:
+            ip = socket.inet_ntop(socket.AF_INET6,
+                                  struct.pack('!QQ',
+                                              option.ip >> 64,
+                                              option.ip & (2 ** 64 - 1)))
+        return ip
+
+    def datagramReceived(self, datagram, address):
+        request = dns.message.from_wire(datagram)
+
+        response = dns.message.make_response(request)
+        response.flags |= dns.flags.AA
+        ecso = None
+
+        if (request.question[0].name == dns.name.from_text(nameECS) or request.question[0].name == dns.name.from_text(nameECSInvalidScope)) and request.question[0].rdtype == dns.rdatatype.TXT:
+
+            text = emptyECSText
+            for option in request.options:
+                if option.otype == clientsubnetoption.ASSIGNED_OPTION_CODE and isinstance(option, clientsubnetoption.ClientSubnetOption):
+                    text = self.ipToStr(option) + '/' + str(option.mask)
+
+                    # Send a scope more specific than the received source for nameECSInvalidScope
+                    if request.question[0].name == dns.name.from_text(nameECSInvalidScope):
+                        ecso = clientsubnetoption.ClientSubnetOption("192.0.42.42", 32, 32)
+                    else:
+                        ecso = clientsubnetoption.ClientSubnetOption(self.ipToStr(option), option.mask, option.mask)
+
+            answer = dns.rrset.from_text(request.question[0].name, ttlECS, dns.rdataclass.IN, 'TXT', text)
+            response.answer.append(answer)
+
+        elif request.question[0].name == dns.name.from_text(nameECS) and request.question[0].rdtype == dns.rdatatype.NS:
+            answer = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'NS', 'ns1.ecs-echo.example.')
+            response.answer.append(answer)
+            additional = dns.rrset.from_text('ns1.ecs-echo.example.', 15, dns.rdataclass.IN, 'A', os.environ['PREFIX'] + '.24')
+            response.additional.append(additional)
+
+        if ecso:
+            response.options = [ecso]
+
+        self.transport.write(response.to_wire(), address)
index 53fd203f0d7f23d50c3761f53f92e3af94443e5e..c8a866e80fe7f7c2a84192dd981266df60d03056 100644 (file)
@@ -20,8 +20,8 @@ Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 ==> www.trillian.example.net has no RPZ policy attached, so lookup should succeed
 Reply to question for qname='www.trillian.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      www.trillian.example.net.       IN      CNAME   15      www2.arthur.example.net.
-0      www2.arthur.example.net.        IN      A       15      192.0.2.6
+0      www.trillian.example.net.       IN      CNAME   15      www3.arthur.example.net.
+0      www3.arthur.example.net.        IN      A       15      192.0.2.6
 ==> www.hijackme.example.net is served on ns.hijackme.example.net, which should be NXDOMAIN
 Reply to question for qname='www.hijackme.example.net.', qtype=A
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
@@ -64,7 +64,9 @@ Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='not-rpz.example.net.', qtype=A
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 0      not-rpz.example.net.    IN      CNAME   5       rpz-not.com.
+1      .       IN      SOA     15      ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 ==> echo-me.wildcard-target.example.net is an RPZ wildcard target
 Reply to question for qname='echo-me.wildcard-target.example.net.', qtype=A
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 0      echo-me.wildcard-target.example.net.    IN      CNAME   7200    echo-me.wildcard-target.example.net.walled-garden.example.net.
+1      example.net.    IN      SOA     15      ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
index 97b505c086ec3c5d95b1a5543403b57901fc0dcd..0e7404099e2d5021383101731fa1d93b7e74a1b5 100755 (executable)
@@ -137,6 +137,7 @@ ns.arthur.example.net.   3600 IN A   $PREFIX.12
 ns2.arthur.example.net.  3600 IN A   $PREFIX.13
 www.arthur.example.net.  3600 IN A   192.0.2.2
 www2.arthur.example.net. 3600 IN A   192.0.2.6
+www3.arthur.example.net. 3600 IN A   192.0.2.6
 mail.arthur.example.net. 3600 IN A   192.0.2.3
 big.arthur.example.net.  3600 IN TXT "the quick brown fox jumps over the lazy dog"
 big.arthur.example.net.  3600 IN TXT "The quick brown fox jumps over the lazy dog"
@@ -243,7 +244,7 @@ cat > $PREFIX.16/trillian.example.net.zone <<EOF
 trillian.example.net.         3600 IN SOA $SOA
 trillian.example.net.         3600 IN NS  ns.trillian.example.net.
 ns.trillian.example.net.      3600 IN A     $PREFIX.16
-www.trillian.example.net.     3600 IN CNAME www2.arthur.example.net.
+www.trillian.example.net.     3600 IN CNAME www3.arthur.example.net.
 EOF
 
 cat > $PREFIX.16/prequery.lua <<EOF
@@ -258,7 +259,7 @@ function prequery ( dnspacket )
     then
         dnspacket:setRcode(pdns.NXDOMAIN)
         ret = {}
-        ret[1] = newDR(newDN(qname), "CNAME", 3600, "www2.arthur.example.net", 1)
+        ret[1] = newDR(newDN(qname), "CNAME", 3600, "www3.arthur.example.net", 1)
         ret[2] = newDR(newDN(""), "SOA", 3600, "$SOA", 2)
         dnspacket:addRecords(ret)
         return true
@@ -579,6 +580,7 @@ local-port=5301
 socket-dir=/tmp/recursor-service3
 lua-config-file=$(pwd)/recursor-service3/config.lua
 lua-dns-script=$(pwd)/recursor-service3/script.lua
+security-poll-suffix=
 
 EOF
 
@@ -602,7 +604,8 @@ cat > recursor-service3/rpz.zone <<EOF
 
 arthur.example.net     CNAME .                   ; NXDOMAIN on apex
 *.arthur.example.net   CNAME *.                  ; NODATA for everything below the apex
-srv.arthur.example.net CNAME rpz-passthru.       ; Allow this name though
+www3.arthur.example.net CNAME rpz-passthru.      ; Allow this name through (so that the CNAME from www.trillian.example.net is not blocked)
+srv.arthur.example.net CNAME rpz-passthru.       ; Allow this name through
 www.example.net        CNAME www2.example.net.   ; Local-Data Action
 www3.example.net       CNAME www4.example.net.   ; Local-Data Action (to be changed in preresolve)
 www5.example.net       A     192.0.2.15          ; Override www5.example.net.
@@ -646,7 +649,7 @@ cat > recursor-service3/rpz4.zone <<EOF
 @ SOA $SOA
 @ NS ns.example.net.
 
-defpol-with-ttl.example.net       50       IN      A     192.0.2.35          ; will be overriden by the default policy and the default TTL
+defpol-with-ttl.example.net       50       IN      A     192.0.2.35          ; will be overridden by the default policy and the default TTL
 
 EOF
 
@@ -656,7 +659,7 @@ cat > recursor-service3/rpz5.zone <<EOF
 @ SOA $SOA
 @ NS ns.example.net.
 
-defpol-with-ttl-capped.example.net       100       IN      A     192.0.2.35          ; will be overriden by the default policy and the default TTL (but capped by maxTTL)
+defpol-with-ttl-capped.example.net       100       IN      A     192.0.2.35          ; will be overridden by the default policy and the default TTL (but capped by maxTTL)
 
 EOF
 
@@ -666,7 +669,7 @@ cat > recursor-service3/rpz6.zone <<EOF
 @ SOA $SOA
 @ NS ns.example.net.
 
-defpol-without-ttl.example.net       A     192.0.2.35          ; will be overriden by the default policy, but with the zone's TTL
+defpol-without-ttl.example.net       A     192.0.2.35          ; will be overridden by the default policy, but with the zone's TTL
 
 EOF
 
@@ -676,7 +679,7 @@ cat > recursor-service3/rpz7.zone <<EOF
 @ SOA $SOA
 @ NS ns.example.net.
 
-defpol-without-ttl-capped.example.net       A     192.0.2.35          ; will be overriden by the default policy, but with the zone's TTL capped by maxTTL
+defpol-without-ttl-capped.example.net       A     192.0.2.35          ; will be overridden by the default policy, but with the zone's TTL capped by maxTTL
 
 EOF
 
@@ -696,9 +699,14 @@ function preresolve(dq)
   if dq.qname:equal("android.marvin.example.net") then
     dq.wantsRPZ = false -- disable RPZ
   end
-  if dq.appliedPolicy.policyKind == pdns.policykinds.Custom then
-    if dq.qname:equal("www3.example.net") then
-      dq.appliedPolicy.policyCustom = "www2.example.net"
+  return false
+end
+
+function policyEventFilter(event)
+  if event.appliedPolicy.policyKind == pdns.policykinds.Custom then
+    if event.qname:equal("www3.example.net") then
+      event.appliedPolicy.policyCustom = "www2.example.net"
+      return false
     end
   end
   return false
index 9a48ef615732f766746fde0ebe3a8041133993e4..490a8a27af3cab51db574146048c498960e1f626 100644 (file)
@@ -1,4 +1,4 @@
-0      www.trillian.example.net.       IN      CNAME   3600    www2.arthur.example.net.
-0      www2.arthur.example.net.        IN      A       3600    192.0.2.6
+0      www.trillian.example.net.       IN      CNAME   3600    www3.arthur.example.net.
+0      www3.arthur.example.net.        IN      A       3600    192.0.2.6
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.trillian.example.net.', qtype=A
index 896cba9125b3d4f8a026c7ec2eb2c4d062e9e80e..6f66b16fcb20c92eb81b7f5eed31105568ad65bf 100644 (file)
@@ -165,6 +165,6 @@ GMYSQLHOST=127.0.0.1 ./start-test-stop 5300 gmysql
 
 Postgres:
 ```sh
-docker run -p 5432:5432 --rm -d postgres
+docker run -p 5432:5432 --rm -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres
 GPGSQLUSER=postgres PGHOST=127.0.0.1  ./start-test-stop 5300 gpgsql
 ```
index c2b577015600971ce246b2347d9e4812bf1ec4d3..54b2f96906b491902934ad62154018e861255bc2 100644 (file)
@@ -1,42 +1,42 @@
 case $context in
-       bind)
-               cat > pdns-bind.conf << __EOF__
+    bind)
+        cat > pdns-bind.conf << __EOF__
 module-dir=./modules
 launch=bind
 bind-config=./named.conf
 bind-ignore-broken-records=yes
 __EOF__
 
-               $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
-                       --config-name=bind --socket-dir=./ --no-shuffle \
-                       --cache-ttl=$cachettl --dname-processing \
-                       --disable-axfr-rectify=yes &
-               skipreasons="nodnssec nodyndns nometa noalias"
-               bindwait bind
-               ;;
+        $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
+            --config-name=bind --socket-dir=./ --no-shuffle \
+            --cache-ttl=$cachettl --dname-processing \
+            --disable-axfr-rectify=yes &
+        skipreasons="nodnssec nodyndns nometa noalias"
+        bindwait bind
+        ;;
 
-       bind-dnssec | bind-dnssec-nsec3 | bind-hybrid-nsec3 | bind-dnssec-nsec3-optout | bind-dnssec-nsec3-narrow)
-               rm -f dnssec.sqlite3
-               cat > pdns-bind.conf << __EOF__
+    bind-dnssec | bind-dnssec-nsec3 | bind-hybrid-nsec3 | bind-dnssec-nsec3-optout | bind-dnssec-nsec3-narrow)
+        rm -f dnssec.sqlite3
+        cat > pdns-bind.conf << __EOF__
 module-dir=./modules
 launch=bind
 bind-config=./named.conf
 bind-ignore-broken-records=yes
 __EOF__
-               if [ $context = bind-hybrid-nsec3 ]
-               then
-                       [ -z "$GMYSQLDB" ] && GMYSQLDB=pdnstest
-                       [ -z "$GMYSQLUSER" ] && GMYSQLUSER=root
-                       [ -z "$GMYSQLHOST" ] && GMYSQLHOST=localhost
-                       [ -z "$GMYSQLPASSWD" ] && GMYSQLPASSWD=''
+        if [ $context = bind-hybrid-nsec3 ]
+        then
+            [ -z "$GMYSQLDB" ] && GMYSQLDB=pdnstest
+            [ -z "$GMYSQLUSER" ] && GMYSQLUSER=root
+            [ -z "$GMYSQLHOST" ] && GMYSQLHOST=localhost
+            [ -z "$GMYSQLPASSWD" ] && GMYSQLPASSWD=''
 
-                       mysqladmin --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" --force drop "$GMYSQLDB" \
-                               || echo ignoring mysqladmin drop failure
-                       mysqladmin --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" create "$GMYSQLDB"
-                       mysql --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" \
-                               "$GMYSQLDB" < ../modules/gmysqlbackend/schema.mysql.sql
+            mysqladmin --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" --force drop "$GMYSQLDB" \
+                || echo ignoring mysqladmin drop failure
+            mysqladmin --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" create "$GMYSQLDB"
+            mysql --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" \
+                "$GMYSQLDB" < ../modules/gmysqlbackend/schema.mysql.sql
 
-               cat >> pdns-bind.conf << __EOF__
+        cat >> pdns-bind.conf << __EOF__
 bind-hybrid
 launch+=gmysql
 gmysql-dbname=$GMYSQLDB
@@ -45,66 +45,71 @@ gmysql-host=$GMYSQLHOST
 gmysql-password=$GMYSQLPASSWD
 gmysql-dnssec
 __EOF__
-               else
-                       echo "bind-dnssec-db=./dnssec.sqlite3" >> pdns-bind.conf
-                       $PDNSUTIL --config-dir=. --config-name=bind create-bind-db dnssec.sqlite3
-               fi
+        else
+            echo "bind-dnssec-db=./dnssec.sqlite3" >> pdns-bind.conf
+            $PDNSUTIL --config-dir=. --config-name=bind create-bind-db dnssec.sqlite3
+        fi
 
-               for zone in $(grep 'zone ' named.conf  | cut -f2 -d\")
-               do
-                       if [ $context = bind-hybrid-nsec3 ]
-                       then
-                               mysql --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" \
-                                       "$GMYSQLDB" -e "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port')"
-                       fi
-                       if [ $zone != insecure.dnssec-parent.com ]
-                       then
-                               securezone $zone bind
-                               if [ $context = bind-dnssec-nsec3 ] || [ $context = bind-dnssec-nsec3-optout ] || [ $context = bind-hybrid-nsec3 ]
-                               then
-                                       $PDNSUTIL --config-dir=. --config-name=bind set-nsec3 $zone "1 $optout 1 abcd" 2>&1
-                               elif [ $context = bind-dnssec-nsec3-narrow ]
-                               then
-                                       $PDNSUTIL --config-dir=. --config-name=bind set-nsec3 $zone '1 1 1 abcd' narrow 2>&1
-                               fi
+        for zone in $(grep 'zone ' named.conf  | cut -f2 -d\")
+        do
+            if [ $context = bind-hybrid-nsec3 ]
+            then
+                mysql --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" \
+                    "$GMYSQLDB" -e "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port')"
+            fi
+            if [ $zone != insecure.dnssec-parent.com ]
+            then
+                securezone $zone bind
+                if [ $zone = hiddencryptokeys.org ]
+                then
+                    keyid=$($PDNSUTIL --config-dir=. --config-name=bind list-keys $zone | grep hiddencryptokeys.org | awk '{ print $5 }')
+                    $PDNSUTIL --config-dir=. --config-name=bind unpublish-zone-key $zone $keyid
+                fi
+                if [ $context = bind-dnssec-nsec3 ] || [ $context = bind-dnssec-nsec3-optout ] || [ $context = bind-hybrid-nsec3 ]
+                then
+                    $PDNSUTIL --config-dir=. --config-name=bind set-nsec3 $zone "1 $optout 1 abcd" 2>&1
+                elif [ $context = bind-dnssec-nsec3-narrow ]
+                then
+                    $PDNSUTIL --config-dir=. --config-name=bind set-nsec3 $zone '1 1 1 abcd' narrow 2>&1
+                fi
                 if [ $zone = cryptokeys.org ]
                 then
                     $PDNSUTIL --config-dir=. --config-name=bind add-zone-key $zone zsk 384 active unpublished ecdsa384
                     $PDNSUTIL --config-dir=. --config-name=bind add-zone-key $zone zsk 2048 inactive published rsasha512
                     $PDNSUTIL --config-dir=. --config-name=bind add-zone-key $zone zsk 2048 inactive unpublished rsasha256
                 fi
-                       fi
-                       if [ "$zone" = "tsig.com" ]; then
-                               $PDNSUTIL --config-dir=. --config-name=bind import-tsig-key test $ALGORITHM $KEY
-                               $PDNSUTIL --config-dir=. --config-name=bind activate-tsig-key tsig.com test master
-                       fi
-               done
+            fi
+            if [ "$zone" = "tsig.com" ]; then
+                $PDNSUTIL --config-dir=. --config-name=bind import-tsig-key test $ALGORITHM $KEY
+                $PDNSUTIL --config-dir=. --config-name=bind activate-tsig-key tsig.com test master
+            fi
+        done
 
-               if [ $context = bind-dnssec-nsec3 ] || [ $context = bind-hybrid-nsec3 ]
-               then
-                       extracontexts="bind dnssec nsec3"
-                       skipreasons="nsec3 nodyndns noalias"
-               elif [ $context = bind-dnssec-nsec3-optout ]
-               then
-                       extracontexts="bind dnssec nsec3 nsec3-optout"
-                       skipreasons="optout nodyndns noalias"
-               elif [ $context = bind-dnssec-nsec3-narrow ]
-               then
-                       extracontexts="bind dnssec narrow"
-                       skipreasons="narrow nodyndns noalias"
-               else
-                       extracontexts="bind dnssec"
-                       skipreasons="nodyndns noalias nsec"
-               fi
+        if [ $context = bind-dnssec-nsec3 ] || [ $context = bind-hybrid-nsec3 ]
+        then
+            extracontexts="bind dnssec nsec3"
+            skipreasons="nsec3 nodyndns noalias"
+        elif [ $context = bind-dnssec-nsec3-optout ]
+        then
+            extracontexts="bind dnssec nsec3 nsec3-optout"
+            skipreasons="optout nodyndns noalias"
+        elif [ $context = bind-dnssec-nsec3-narrow ]
+        then
+            extracontexts="bind dnssec narrow"
+            skipreasons="narrow nodyndns noalias"
+        else
+            extracontexts="bind dnssec"
+            skipreasons="nodyndns noalias nsec"
+        fi
 
-               $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
-                       --config-name=bind --socket-dir=./ --no-shuffle \
-                       --cache-ttl=$cachettl --dname-processing \
-                       --disable-axfr-rectify=yes $lua_prequery &
+        $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
+            --config-name=bind --socket-dir=./ --no-shuffle \
+            --cache-ttl=$cachettl --dname-processing \
+            --disable-axfr-rectify=yes $lua_prequery &
 
-               bindwait bind
-               ;;
+        bindwait bind
+        ;;
 
-       *)
-               nocontext=yes
+    *)
+        nocontext=yes
 esac
index e85c6d8dcbfb95a6367830099b3d98738e5c222b..a0f084926a7e88c98306e165cdea06d3903ae916 100644 (file)
@@ -1,7 +1,7 @@
 source ./backends/gsql-common
 
 case $context in
-       gmysql-nodnssec | gmysql | gmysql-nsec3 | gmysql-nsec3-optout | gmysql-nsec3-narrow)
+       gmysql-nodnssec | gmysql | gmysql-nsec3 | gmysql-nsec3-optout | gmysql-nsec3-narrow | gmysql_sp)
                [ -z "$GMYSQLDB" ] && GMYSQLDB=pdnstest
                [ -z "$GMYSQLUSER" ] && GMYSQLUSER=root
                [ -z "$GMYSQLHOST" ] && GMYSQLHOST=localhost
@@ -33,3 +33,17 @@ __EOF__
        *)
                nocontext=yes
 esac
+
+if [[ "$context" = "gmysql_sp" ]]; then
+    cat >> pdns-gmysql.conf << __EOF__
+gmysql-basic-query=CALL basic_query(?, ?)
+__EOF__
+    mysql --user="$GMYSQLUSER" --password="$GMYSQLPASSWD" --host="$GMYSQLHOST" \
+        "$GMYSQLDB" << __EOF__
+DELIMITER //
+CREATE PROCEDURE basic_query(incoming_type varchar(10), incoming_name varchar(255))
+BEGIN
+SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and type=incoming_type and name=incoming_name;
+END//
+__EOF__
+fi
index 3ce84f19da619c3385372b1e66ce22cb137ba127..ec9757c21eb96960fcd15de2db6c123cdeaae514 100644 (file)
@@ -52,7 +52,7 @@ __EOF__
        while [ $loopcount -lt 30 ]
        do
                sleep 5
-               todo=$(mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQl2HOST" \
+               todo=$(mysql --user="$GMYSQL2USER" --password="$GMYSQL2PASSWD" --host="$GMYSQL2HOST" \
                        "$GMYSQL2DB" -ss -e 'SELECT COUNT(id) FROM domains WHERE last_check IS NULL')
                if [ $todo = 0 ]
                then
index 86840ff1d576a91fb5fa43f74e50e869e1dce783..9a9b527c6a060b9c9d2995f42819bc25275b5103 100644 (file)
@@ -1,70 +1,75 @@
 gsql_master()
 {
-       backend=$1
-       skipreasons=$2
+    backend=$1
+    skipreasons=$2
 
-       real_backend=$backend
-       if `echo $backend | grep -q '_'`; then
-               real_backend=$(echo $backend | awk -F '_' '{print $1}')
-       fi
+    real_backend=$backend
+    if `echo $backend | grep -q '_'`; then
+        real_backend=$(echo $backend | awk -F '_' '{print $1}')
+    fi
 
-       if [ $context != ${backend}-nodnssec ]
-       then
-               echo "${real_backend}-dnssec" >> pdns-${backend}.conf
-       fi
+    if [ $context != ${backend}-nodnssec ]
+    then
+        echo "${real_backend}-dnssec" >> pdns-${backend}.conf
+    fi
 
-       for zone in $(grep 'zone ' named.conf  | cut -f2 -d\")
-       do
-               if [ $context != ${backend}-nodnssec ] && [ $zone != insecure.dnssec-parent.com ]
-               then
-                       if [ $context = ${backend}-nsec3 ] || [ $context = ${backend}-nsec3-optout ]
-                       then
-                               $PDNSUTIL --config-dir=. --config-name=$backend set-nsec3 $zone "1 $optout 1 abcd" 2>&1
-                       elif [ $context = ${backend}-nsec3-narrow ]
-                       then
-                               $PDNSUTIL --config-dir=. --config-name=$backend set-nsec3 $zone '1 1 1 abcd' narrow 2>&1
-                       fi
-                       securezone $zone ${backend}
+    for zone in $(grep 'zone ' named.conf  | cut -f2 -d\")
+    do
+        if [ $context != ${backend}-nodnssec ] && [ $zone != insecure.dnssec-parent.com ]
+        then
+            if [ $context = ${backend}-nsec3 ] || [ $context = ${backend}-nsec3-optout ]
+            then
+                $PDNSUTIL --config-dir=. --config-name=$backend set-nsec3 $zone "1 $optout 1 abcd" 2>&1
+            elif [ $context = ${backend}-nsec3-narrow ]
+            then
+                $PDNSUTIL --config-dir=. --config-name=$backend set-nsec3 $zone '1 1 1 abcd' narrow 2>&1
+            fi
+            securezone $zone ${backend}
+            if [ $zone = hiddencryptokeys.org ]
+            then
+                keyid=$($PDNSUTIL --config-dir=. --config-name=$backend list-keys $zone | grep hiddencryptokeys.org | awk '{ print $5 }')
+                $PDNSUTIL --config-dir=. --config-name=$backend unpublish-zone-key $zone $keyid
+            fi
             if [ $zone = cryptokeys.org ]
             then
                 $PDNSUTIL --config-dir=. --config-name=$backend add-zone-key $zone zsk 384 active unpublished ecdsa384
                 $PDNSUTIL --config-dir=. --config-name=$backend add-zone-key $zone zsk 2048 inactive published rsasha512
                 $PDNSUTIL --config-dir=. --config-name=$backend add-zone-key $zone zsk 2048 inactive unpublished rsasha256
             fi
-               else
-                       $PDNSUTIL --config-dir=. --config-name=$backend rectify-zone $zone 2>&1
-               fi
-               if [ "$zone" = "tsig.com" ]; then
-                       $PDNSUTIL --config-dir=. --config-name=$backend import-tsig-key test $ALGORITHM $KEY
-                       $PDNSUTIL --config-dir=. --config-name=$backend activate-tsig-key tsig.com test master
-               fi
-       done
+        else
+            $PDNSUTIL --config-dir=. --config-name=$backend rectify-zone $zone 2>&1
+        fi
+        if [ "$zone" = "tsig.com" ]; then
+            $PDNSUTIL --config-dir=. --config-name=$backend import-tsig-key test $ALGORITHM $KEY
+            $PDNSUTIL --config-dir=. --config-name=$backend activate-tsig-key tsig.com test master
+        fi
+    done
 
 
-       $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
-               --config-name=$backend --socket-dir=./ --no-shuffle \
-               --dnsupdate=yes --resolver=$RESOLVERIP --outgoing-axfr-expand-alias=yes \
-               --expand-alias=yes \
-               --cache-ttl=$cachettl --dname-processing \
-               --disable-axfr-rectify=yes $lua_prequery &
+    $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --config-dir=. \
+        --config-name=$backend --socket-dir=./ --no-shuffle \
+        --dnsupdate=yes --resolver=$RESOLVERIP --outgoing-axfr-expand-alias=yes \
+        --expand-alias=yes \
+        --cache-ttl=$cachettl --dname-processing \
+        --disable-axfr-rectify=yes $lua_prequery &
 
-       if [ $context = ${backend}-nsec3 ]
-       then
-               extracontexts="dnssec nsec3"
-               skipreasons="$skipreasons nsec3"
-       elif [ $context = ${backend}-nsec3-optout ]
-       then
-               extracontexts="dnssec nsec3 nsec3-optout"
-               skipreasons="$skipreasons optout"
-       elif [ $context = ${backend}-nsec3-narrow ]
-       then
-               extracontexts="dnssec narrow"
-               skipreasons="$skipreasons narrow"
-       elif [ $context = ${backend}-nodnssec ]
-       then
-               skipreasons="$skipreasons nodnssec"
-       else
-               extracontexts="dnssec"
-               skipreasons="$skipreasons nsec"
-       fi
+    if [ $context = ${backend}-nsec3 ]
+    then
+        extracontexts="dnssec nsec3"
+        skipreasons="$skipreasons nsec3"
+    elif [ $context = ${backend}-nsec3-optout ]
+    then
+        extracontexts="dnssec nsec3 nsec3-optout"
+        skipreasons="$skipreasons optout"
+    elif [ $context = ${backend}-nsec3-narrow ]
+    then
+        extracontexts="dnssec narrow"
+        skipreasons="$skipreasons narrow"
+    elif [ $context = ${backend}-nodnssec ]
+    then
+        skipreasons="$skipreasons nodnssec"
+    else
+        extracontexts="dnssec"
+        skipreasons="$skipreasons nsec"
+    fi
 }
index b557b6a88c9c63feeb20f60e02f162d93b4e548e..cc0d18ac844d8015cbec18a47e8002e12cba596a 100644 (file)
@@ -29,6 +29,11 @@ __EOF__
                         $PDNSUTIL --config-dir=. --config-name=lmdb set-nsec3 $zone '1 1 1 abcd' narrow 2>&1
                     fi
                     securezone $zone lmdb
+                    if [ $zone = hiddencryptokeys.org ]
+                    then
+                        keyid=$($PDNSUTIL --config-dir=. --config-name=lmdb list-keys $zone | grep hiddencryptokeys.org | awk '{ print $5 }')
+                        $PDNSUTIL --config-dir=. --config-name=lmdb unpublish-zone-key $zone $keyid
+                    fi
                     if [ $zone = cryptokeys.org ]
                     then
                         $PDNSUTIL --config-dir=. --config-name=lmdb add-zone-key $zone zsk 384 active unpublished ecdsa384
index 52c383f94b0ab73620b35505a7e9e63ecb6838c3..c1105a0891c1ae3db7d895de65a84f2186fa86c9 100644 (file)
@@ -93,3 +93,8 @@ zone "cryptokeys.org"{
     file "cryptokeys.org";
 };
 
+zone "hiddencryptokeys.org"{
+    type master;
+    file "hiddencryptokeys.org";
+};
+
index 4d38c9fc673636576bffad1170808f431f82143d..280d578a0258fb011a19d056a1b97ff6b457fe68 100755 (executable)
@@ -4,9 +4,9 @@ port=$1
 limit=$2
 [ -z "$limit" ] && limit=100000
 threads=$3
-[ -z "$threads" ] && threads=2
+[ -z "$threads" ] && threads=8
 mthreads=$4
-[ -z "$mthreads" ] && mthreads=100
+[ -z "$mthreads" ] && mthreads=2048
 shards=$5
 [ -z "$shards" ] && shards=1024
 
@@ -19,7 +19,7 @@ shards=$5
 
 if [ $IPv6 = 1 ]
 then
-       QLA6="::"
+       QLA6=" ::"
 else
        QLA6=""
 fi
@@ -31,7 +31,7 @@ rm -f recursor.pid pdns_recursor.pid
 <measurement><name>system CPU seconds</name><value>%S</value></measurement>
 <measurement><name>wallclock seconds</name><value>%e</value></measurement>
 <measurement><name>%% CPU used</name><value>%P</value></measurement>
-'         ${RECURSOR} --daemon=no --local-port=$port --socket-dir=./ --trace=$TRACE --config-dir=. --max-mthreads=$mthreads --query-local-address6="${QLA6}" --threads=$threads --cache-shards=$shards --disable-packetcache > recursor.log 2>&1 &
+'         ${RECURSOR} --daemon=no --local-port=$port --socket-dir=./ --trace=$TRACE --config-dir=. --max-mthreads=$mthreads --query-local-address="0.0.0.0${QLA6}" --threads=$threads --record-cache-shards=$shards --disable-packetcache > recursor.log 2>&1 &
 sleep 3
 
 # warm up the cache
index 5cdda716803695243e04e30a1f513e053db8b07c..47ec529df0716cfd188012716dca0d983fdc4c36 100644 (file)
@@ -12,13 +12,13 @@ limit=$2
 
 if [ $IPv6 = 1 ]
 then
-       QLA6="::"
+       QLA6=" ::"
 else
        QLA6=""
 fi
 
 rm -f recursor.pid pdns_recursor.pid
-${RECURSOR} --daemon=no --local-port=$port --socket-dir=./ --trace=$TRACE --config-dir=. --local-address=0.0.0.0 --allow-from=0.0.0.0/0 --query-local-address6="${QLA6}" > recursor.log 2>&1 &
+${RECURSOR} --daemon=no --local-port=$port --socket-dir=./ --trace=$TRACE --config-dir=. --local-address=0.0.0.0 --allow-from=0.0.0.0/0 --query-local-address="0.0.0.0${QLA6}" > recursor.log 2>&1 &
 sleep 3
 ./dnsbulktest -qe 37.252.127.190 $port $limit < ${CSV} > bulktest.results
 kill $(cat pdns_recursor.pid)
index 96044e25f09a1f749f4ace22d54a6d98916d21d7..714aec80808a1e12030ceb6fa365b1a925badcea 100755 (executable)
@@ -44,7 +44,7 @@ Usage: ./start-test-stop <port> [<context>] [wait|nowait] [<cachettl>] [<specifi
 context is one of:
 bind bind-dnssec bind-dnssec-nsec3 bind-dnssec-nsec3-optout bind-dnssec-nsec3-narrow
 geoip geoip-nsec3-narrow
-gmysql-nodnssec gmysql gmysql-nsec3 gmysql-nsec3-optout gmysql-nsec3-narrow
+gmysql-nodnssec gmysql gmysql-nsec3 gmysql-nsec3-optout gmysql-nsec3-narrow gmysql_sp
 godbc_mssql-nodnssec godbc_mssql godbc_mssql-nsec3 godbc_mssql-nsec3-optout godbc_mssql-nsec3-narrow
 godbc_sqlite3-nodnssec godbc_sqlite3 godbc_sqlite3-nsec3 godbc_sqlite3-nsec3-optout godbc_sqlite3-narrow
 gpgsql-nodnssec gpgsql gpgsql-nsec3 gpgsql-nsec3-optout gpgsql-nsec3-narrow
@@ -129,22 +129,24 @@ securezone ()
        else
                # check if PKCS#11 should be used
                if [ "$pkcs11" -eq 1 ]; then
-                  if [ "$slot" == "" ]; then
-                    slot=0
-                  else
-                    slot=$((slot+1))
-                  fi
-                  sudo softhsm --init-token --slot $slot --label label$slot --pin 123$slot --so-pin 123$slot
-                  kid=`$PDNSUTIL --config-dir=. $configname hsm assign $zone ecdsa256 zsk softhsm label$slot 123$slot label$slot 2>&1 | grep softhsm | awk '{ print $NF }'`
-                  kid=`$PDNSUTIL --config-dir=. $configname show-zone $zone | grep 'ID =.*ZSK' | awk '{ print $3 }'`
-                  $PDNSUTIL --config-dir=. $configname hsm create-key $zone $kid
-                else
-                  $PDNSUTIL --config-dir=. $configname secure-zone $zone 2>&1
-                  if [ "${zone: 0:20}" = "cdnskey-cds-test.com" ]; then
-                    $PDNSUTIL --config-dir=. $configname set-publish-cds $zone 2>&1
-                    $PDNSUTIL --config-dir=. $configname set-publish-cdnskey $zone 2>&1
-                  fi
-                fi
+                       if [ "$slot" == "" ]; then
+                               slot=0
+                       else
+                               slot=$((slot+1))
+                       fi
+                       label=pdnstest-${EPOCHSECONDS}-${slot}
+                       softhsm2-util --delete-token --label $label 2> /dev/null || true
+                       softhsm2-util --init-token --label $label --free --pin 1234 --so-pin 1234
+                       kid=`$PDNSUTIL --config-dir=. $configname hsm assign $zone ecdsa256 ksk softhsm2 $label 1234 $label 2>&1 | grep softhsm | awk '{ print $NF }'`
+                       $PDNSUTIL --config-dir=. $configname hsm create-key $zone $kid
+                       $PDNSUTIL --config-dir=. $configname rectify-zone $zone 2>&1
+               else
+                       $PDNSUTIL --config-dir=. $configname secure-zone $zone 2>&1
+               fi
+               if [ "${zone: 0:20}" = "cdnskey-cds-test.com" ]; then
+                       $PDNSUTIL --config-dir=. $configname set-publish-cds $zone 2>&1
+                       $PDNSUTIL --config-dir=. $configname set-publish-cdnskey $zone 2>&1
+               fi
        fi
 }
 
index 1529298c63c4d02f3425b2b94b826678925e5dc6..72757ab77018c68c3bd62a0f46142fdceb413cf9 100755 (executable)
@@ -1,2 +1,3 @@
 #!/bin/sh
 cleandig cryptokeys.org DNSKEY dnssec
+cleandig hiddencryptokeys.org DNSKEY dnssec
index 409f965e24b50e5604801b102a94dd5dbccf71a4..a3e101c9f2a03cd4b7250fe904cf1da178fa08b5 100644 (file)
@@ -5,3 +5,10 @@
 2      .       IN      OPT     32768   
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='cryptokeys.org.', qtype=DNSKEY
+1      hiddencryptokeys.org.   IN      NSEC    3600    hiddencryptokeys.org. A NS SOA RRSIG NSEC
+1      hiddencryptokeys.org.   IN      RRSIG   3600    NSEC 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   IN      SOA     3600    cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
+2      .       IN      OPT     32768   
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='hiddencryptokeys.org.', qtype=DNSKEY
diff --git a/regression-tests/tests/cryptokeys/expected_result.narrow b/regression-tests/tests/cryptokeys/expected_result.narrow
new file mode 100644 (file)
index 0000000..691ab9f
--- /dev/null
@@ -0,0 +1,14 @@
+0      cryptokeys.org. IN      DNSKEY  3600    256 3 10 ...
+0      cryptokeys.org. IN      DNSKEY  3600    257 3 13 ...
+0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+2      .       IN      OPT     32768   
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='cryptokeys.org.', qtype=DNSKEY
+1      hiddencryptokeys.org.   IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   IN      SOA     3600    cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      NSEC3   3600    1 [flags] 1 abcd VD844E5OI5854H79FNAA0F80NQO8BRF1 A NS SOA RRSIG NSEC3PARAM
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+2      .       IN      OPT     32768   
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='hiddencryptokeys.org.', qtype=DNSKEY
diff --git a/regression-tests/tests/cryptokeys/expected_result.nsec3 b/regression-tests/tests/cryptokeys/expected_result.nsec3
new file mode 100644 (file)
index 0000000..af5aa67
--- /dev/null
@@ -0,0 +1,14 @@
+0      cryptokeys.org. IN      DNSKEY  3600    256 3 10 ...
+0      cryptokeys.org. IN      DNSKEY  3600    257 3 13 ...
+0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 13 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+0      cryptokeys.org. IN      RRSIG   3600    DNSKEY 14 2 3600 [expiry] [inception] [keytag] cryptokeys.org. ...
+2      .       IN      OPT     32768   
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='cryptokeys.org.', qtype=DNSKEY
+1      hiddencryptokeys.org.   IN      RRSIG   3600    SOA 13 2 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+1      hiddencryptokeys.org.   IN      SOA     3600    cryptokeys.ds9a.nl. ahu.ds9a.nl. 2009071301 14400 3600 604800 3600
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      NSEC3   3600    1 [flags] 1 abcd VD844E5OI5854H79FNAA0F80NQO8BRF0 A NS SOA RRSIG NSEC3PARAM
+1      vd844e5oi5854h79fnaa0f80nqo8brf0.hiddencryptokeys.org.  IN      RRSIG   3600    NSEC3 13 3 3600 [expiry] [inception] [keytag] hiddencryptokeys.org. ...
+2      .       IN      OPT     32768   
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='hiddencryptokeys.org.', qtype=DNSKEY
diff --git a/regression-tests/tests/cryptokeys/skip-drill b/regression-tests/tests/cryptokeys/skip-drill
new file mode 100644 (file)
index 0000000..e69de29
index 3208dd5736efb3488c5c7fd9eee578e1c20d91c0..d6bc288f286990efc9dceadf4f09a308410276e7 100644 (file)
@@ -1 +1 @@
-Check NSECx response for wildcards no data asnwers (mode 2)
+Check NSECx response for wildcards no data answers (mode 2)
index bd1d7db5735f680498a68d954357c91179215e2f..45bac28c402b7b9c670801b1e8dcbf97e509acca 100644 (file)
@@ -1 +1 @@
-Check NSECx response for wildcard asnwers (mode 3)
+Check NSECx response for wildcard answers (mode 3)
index e57cbd01828dab9a09da4c6f01b1e82d005b42ce..81e9fc564e68adf543a92bc903fe6e46455f9b84 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/env bash
-for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | grep -v '^\(cryptokeys.org\|example.com\|nztest.com\|insecure.dnssec-parent.com\)$')
+for zone in $(grep 'zone ' named.conf  | cut -f2 -d\" | grep -v '^\(hiddencryptokeys.org\|cryptokeys.org\|example.com\|nztest.com\|insecure.dnssec-parent.com\)$')
 do
        TFILE=$(mktemp tmp.XXXXXXXXXX)
        drill -p $port axfr $zone @$nameserver | ldns-read-zone -z -u CDS -u CDNSKEY > $TFILE
diff --git a/regression-tests/zones/hiddencryptokeys.org b/regression-tests/zones/hiddencryptokeys.org
new file mode 100644 (file)
index 0000000..d84d77d
--- /dev/null
@@ -0,0 +1,10 @@
+hiddencryptokeys.org.  3600    IN SOA  cryptokeys.ds9a.nl. ahu.ds9a.nl. (
+                                           2009071301 ; serial
+                                           14400       ; refresh (2 hours 30 minutes)
+                                           3600        ; retry (7 minutes 30 seconds)
+                                           604800     ; expire (1 week)
+                                           3600       ; minimum (7 minutes 30 seconds)
+                                           )
+                                   3600        NS      cryptokeys.ds9a.nl.
+                                   3600        NS      cryptokeys.ds9a.nl.
+                               3600    A       212.123.148.70